Produktly API (1.0.0)
Download OpenAPI specification:Download
The Produktly REST API gives you programmatic access to your changelogs, roadmaps, feedback widgets, NPS widgets, tags, and analytics. It exposes the same functionality as the MCP server, over plain HTTP.
All endpoints (except GET /openapi.json) require an API key passed as a Bearer token.
- Sign in to Produktly and open Settings → API keys.
- Generate a new key and copy it. The same key works for both REST and MCP.
- Send it on every request:
GET /api/v1/changelogs HTTP/1.1
Host: api.produktly.com
Authorization: Bearer <your-api-key>
Missing or invalid keys return 401 unauthorized. Treat the key like a password — anyone with it can read and write your Produktly data.
List endpoints return an envelope with a data array and pagination metadata:
{
"data": [ { "id": 1, "name": "..." } ],
"pagination": { "total": 42, "limit": 50, "offset": 0 }
}
Single-resource endpoints (e.g. GET /roadmaps/{id}) return the resource directly, no envelope.
List endpoints accept limit (default 50, max 100) and offset (default 0) query parameters. Date filters (startDate, endDate) compose with pagination where supported.
Errors come back in a consistent shape:
{
"error": {
"code": "invalid_tag_names",
"message": "Unknown tag names: foo. Use GET /v1/tags to see available tags.",
"details": { "missing": ["foo"] }
}
}
Common codes:
unauthorized(401) — missing or invalid API keynot_found(404) — resource doesn't exist or doesn't belong to your companyvalidation_error(400) — invalid request parametersrate_limit_exceeded(429) — see Rate limits belowinternal_error(500) — server-side problem; safe to retry
Every API key is limited to 60 requests per minute. Every response includes:
X-RateLimit-Limit: total allowed in the windowX-RateLimit-Remaining: remaining quotaX-RateLimit-Reset: Unix seconds when the bucket refills
When you exceed the limit you'll get 429 rate_limit_exceeded with a Retry-After header. Back off and retry.
List posts in a changelog
Authorizations:
path Parameters
| changelogId required | integer > 0 |
query Parameters
| limit | integer ( 0 .. 100 ] Default: 50 |
| offset | integer or null >= 0 Default: 0 |
| startDate | string |
| endDate | string |
Responses
Response samples
- 200
- 401
- 404
- 429
{- "data": [
- {
- "id": 0,
- "title": "string",
- "description": "string",
- "date": "2019-08-24T14:15:22Z",
- "active": true,
- "tags": [
- {
- "id": 0,
- "name": "string",
- "backgroundColor": "string",
- "textColor": "string"
}
]
}
], - "pagination": {
- "total": 0,
- "limit": 0,
- "offset": 0
}
}Create a changelog post
Authorizations:
path Parameters
| changelogId required | integer > 0 |
Request Body schema: application/json
| title required | string non-empty |
| description | string |
| date | string |
| active | boolean |
| tagNames | Array of strings |
Responses
Request samples
- Payload
{- "title": "string",
- "description": "string",
- "date": "string",
- "active": true,
- "tagNames": [
- "string"
]
}Response samples
- 201
- 400
- 401
- 404
- 429
{- "id": 0,
- "title": "string",
- "description": "string",
- "date": "2019-08-24T14:15:22Z",
- "active": true,
- "tags": [
- {
- "id": 0,
- "name": "string",
- "backgroundColor": "string",
- "textColor": "string"
}
]
}Update a changelog post
Authorizations:
path Parameters
| changelogId required | integer > 0 |
| postId required | integer > 0 |
Request Body schema: application/json
| title | string |
| description | string |
| date | string |
| active | boolean |
| tagNames | Array of strings |
Responses
Request samples
- Payload
{- "title": "string",
- "description": "string",
- "date": "string",
- "active": true,
- "tagNames": [
- "string"
]
}Response samples
- 200
- 400
- 401
- 404
- 429
{- "id": 0,
- "title": "string",
- "description": "string",
- "date": "2019-08-24T14:15:22Z",
- "active": true,
- "tags": [
- {
- "id": 0,
- "name": "string",
- "backgroundColor": "string",
- "textColor": "string"
}
]
}List feedback responses
Authorizations:
path Parameters
| widgetId required | integer > 0 |
query Parameters
| limit | integer ( 0 .. 100 ] Default: 50 |
| offset | integer or null >= 0 Default: 0 |
| startDate | string |
| endDate | string |
Responses
Response samples
- 200
- 401
- 429
{- "data": [
- {
- "id": 0,
- "type": "string",
- "optionName": "string",
- "emoji": "string",
- "message": "string",
- "email": "string",
- "name": "string",
- "fromUrl": "string",
- "createdAt": "2019-08-24T14:15:22Z"
}
], - "pagination": {
- "total": 0,
- "limit": 0,
- "offset": 0
}
}Response samples
- 200
- 401
- 429
{- "data": [
- {
- "id": 0,
- "name": "string",
- "publicName": "string",
- "active": true,
- "customDomain": "string",
- "publicId": "string",
- "sections": [
- {
- "id": 0,
- "name": "string",
- "items": [
- {
- "id": 0,
- "name": "string",
- "description": "string",
- "votesCount": 0,
- "updatedAt": "2019-08-24T14:15:22Z",
- "tag": {
- "id": 0,
- "name": "string",
- "backgroundColor": "string",
- "textColor": "string"
}
}
]
}
]
}
], - "pagination": {
- "total": 0,
- "limit": 0,
- "offset": 0
}
}Get roadmap with sections and items
Authorizations:
path Parameters
| roadmapId required | integer > 0 |
query Parameters
| startDate | string |
| endDate | string |
| latestCount | integer ( 0 .. 100 ] |
| limit | integer ( 0 .. 100 ] |
| query | string |
| tagNames | Array of strings |
Responses
Response samples
- 200
- 401
- 404
- 429
{- "id": 0,
- "name": "string",
- "publicName": "string",
- "active": true,
- "customDomain": "string",
- "publicId": "string",
- "sections": [
- {
- "id": 0,
- "name": "string",
- "items": [
- {
- "id": 0,
- "name": "string",
- "description": "string",
- "votesCount": 0,
- "updatedAt": "2019-08-24T14:15:22Z",
- "tag": {
- "id": 0,
- "name": "string",
- "backgroundColor": "string",
- "textColor": "string"
}
}
]
}
]
}Get NPS score breakdown
Authorizations:
path Parameters
| widgetId required | integer > 0 |
query Parameters
| startDate | string |
| endDate | string |
Responses
Response samples
- 200
- 401
- 429
{- "npsScore": 0,
- "totalResponses": 0,
- "promoters": {
- "count": 0,
- "percentage": 0
}, - "passives": {
- "count": 0,
- "percentage": 0
}, - "detractors": {
- "count": 0,
- "percentage": 0
}
}List NPS responses
Authorizations:
path Parameters
| widgetId required | integer > 0 |
query Parameters
| limit | integer ( 0 .. 100 ] Default: 50 |
| offset | integer or null >= 0 Default: 0 |
| startDate | string |
| endDate | string |
Responses
Response samples
- 200
- 401
- 429
{- "data": [
- {
- "id": 0,
- "rating": "string",
- "comment": "string",
- "email": "string",
- "name": "string",
- "userId": "string",
- "url": "string",
- "language": "string",
- "createdAt": "2019-08-24T14:15:22Z"
}
], - "pagination": {
- "total": 0,
- "limit": 0,
- "offset": 0
}
}Widget stats by entity type
Authorizations:
path Parameters
| entityType required | string Enum: "tour" "checklist" "smartTip" "announcement" "changelog" "npsWidget" "roadmap" |
query Parameters
| startDate | string |
| endDate | string |
| entityId | integer > 0 |
Responses
Response samples
- 200
- 400
- 401
- 429
{- "entityType": "string",
- "period": {
- "start": "string",
- "end": "string"
}, - "widgets": [
- {
- "entityId": 0,
- "name": "string",
- "archivedAt": "2019-08-24T14:15:22Z",
- "events": {
- "property1": {
- "count": 0,
- "uniqueUsers": 0
}, - "property2": {
- "count": 0,
- "uniqueUsers": 0
}
}, - "totalEvents": 0,
- "totalUniqueUsers": 0
}
]
}Company-wide stats summary
Authorizations:
query Parameters
| startDate | string |
| endDate | string |
Responses
Response samples
- 200
- 400
- 401
- 429
{- "period": {
- "start": "string",
- "end": "string",
- "days": 0,
- "granularity": "string"
}, - "previousPeriod": {
- "start": "string",
- "end": "string"
}, - "features": [
- {
- "type": "string",
- "label": "string",
- "primary": {
- "label": "string",
- "count": 0,
- "previousCount": 0,
- "changePct": 0
}, - "secondary": {
- "label": "string",
- "value": 0,
- "previousValue": 0,
- "changePp": 0
}, - "events": {
- "property1": {
- "count": 0,
- "uniqueUsers": 0,
- "previousCount": 0,
- "previousUniqueUsers": 0
}, - "property2": {
- "count": 0,
- "uniqueUsers": 0,
- "previousCount": 0,
- "previousUniqueUsers": 0
}
}, - "trend": [
- {
- "period": "string",
- "count": 0
}
]
}
]
}