Skip to main content

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, analytics, and identified users. It exposes the same functionality as the MCP server, over plain HTTP.

Base URL

https://api.produktly.com/api/v1

Authentication

All endpoints (except GET /openapi.json) require an API key passed as a Bearer token.

  1. Sign in to Produktly and open Settings → API keys.
  2. Generate a new key and copy it. The same key works for both REST and MCP.
  3. 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.

Response format

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.

Pagination

List endpoints accept limit (default 50, max 100) and offset (default 0) query parameters. Date filters (startDate, endDate) compose with pagination where supported.

Errors

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 key
  • not_found (404) — resource doesn't exist or doesn't belong to your company
  • validation_error (400) — invalid request parameters
  • rate_limit_exceeded (429) — see Rate limits below
  • internal_error (500) — server-side problem; safe to retry

Rate limits

Every API key is limited to 60 requests per minute. The /users endpoints have a separate bucket of 300 requests per minute to support backend syncs. Every response includes:

  • X-RateLimit-Limit: total allowed in the window
  • X-RateLimit-Remaining: remaining quota
  • X-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.

Changelogs

List changelogs

Authorizations:
BearerAuth

Responses

Response samples

Content type
application/json
{
  • "data": [
    ],
  • "pagination": {
    }
}

List posts in a changelog

Authorizations:
BearerAuth
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

Content type
application/json
{
  • "data": [
    ],
  • "pagination": {
    }
}

Create a changelog post

Authorizations:
BearerAuth
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

Content type
application/json
{
  • "title": "string",
  • "description": "string",
  • "date": "string",
  • "active": true,
  • "tagNames": [
    ]
}

Response samples

Content type
application/json
{
  • "id": 0,
  • "title": "string",
  • "description": "string",
  • "date": "2019-08-24T14:15:22Z",
  • "active": true,
  • "tags": [
    ]
}

Update a changelog post

Authorizations:
BearerAuth
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

Content type
application/json
{
  • "title": "string",
  • "description": "string",
  • "date": "string",
  • "active": true,
  • "tagNames": [
    ]
}

Response samples

Content type
application/json
{
  • "id": 0,
  • "title": "string",
  • "description": "string",
  • "date": "2019-08-24T14:15:22Z",
  • "active": true,
  • "tags": [
    ]
}

Feedback Widgets

List feedback widgets

Authorizations:
BearerAuth

Responses

Response samples

Content type
application/json
{
  • "data": [
    ],
  • "pagination": {
    }
}

List feedback responses

Authorizations:
BearerAuth
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

Content type
application/json
{
  • "data": [
    ],
  • "pagination": {
    }
}

Roadmaps

List roadmaps

Authorizations:
BearerAuth

Responses

Response samples

Content type
application/json
{
  • "data": [
    ],
  • "pagination": {
    }
}

Get roadmap with sections and items

Authorizations:
BearerAuth
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

Content type
application/json
{
  • "id": 0,
  • "name": "string",
  • "publicName": "string",
  • "active": true,
  • "customDomain": "string",
  • "publicId": "string",
  • "sections": [
    ]
}

NPS Widgets

List NPS widgets

Authorizations:
BearerAuth

Responses

Response samples

Content type
application/json
{
  • "data": [
    ],
  • "pagination": {
    }
}

Get NPS score breakdown

Authorizations:
BearerAuth
path Parameters
widgetId
required
integer > 0
query Parameters
startDate
string
endDate
string

Responses

Response samples

Content type
application/json
{
  • "npsScore": 0,
  • "totalResponses": 0,
  • "promoters": {
    },
  • "passives": {
    },
  • "detractors": {
    }
}

List NPS responses

Authorizations:
BearerAuth
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

Content type
application/json
{
  • "data": [
    ],
  • "pagination": {
    }
}

Stats

Widget stats by entity type

Authorizations:
BearerAuth
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

Content type
application/json
{
  • "entityType": "string",
  • "period": {
    },
  • "widgets": [
    ]
}

Company-wide stats summary

Authorizations:
BearerAuth
query Parameters
startDate
string
endDate
string

Responses

Response samples

Content type
application/json
{
  • "period": {
    },
  • "previousPeriod": {
    },
  • "features": [
    ]
}

Users

Get an identified user

Returns the user's attributes and timestamps. metadata can be null for users identified client-side without attributes.

Authorizations:
BearerAuth
path Parameters
userId
required
string non-empty
Example: user_123

The user's external ID — the same value you pass to window.Produktly.identifyUser(). URL-encode it.

Responses

Response samples

Content type
application/json
{
  • "userId": "user_123",
  • "metadata": {
    },
  • "createdAt": "2026-07-01T09:00:00.000Z",
  • "updatedAt": "2026-07-15T09:00:00.000Z"
}

Create or replace an identified user

Server-side equivalent of identifyUser() with replace semantics: creates the user if it doesn't exist (201), otherwise replaces its entire metadata object (200). Prefer PATCH for scheduled syncs so you don't clobber attributes set client-side.

Authorizations:
BearerAuth
path Parameters
userId
required
string non-empty
Example: user_123

The user's external ID — the same value you pass to window.Produktly.identifyUser(). URL-encode it.

Request Body schema: application/json
required
object

User attributes as a JSON object (nested values allowed, max 32 KB serialized). Replaces the entire stored metadata object.

property name*
additional property
any

Responses

Request samples

Content type
application/json
{
  • "metadata": {
    }
}

Response samples

Content type
application/json
{
  • "userId": "user_123",
  • "metadata": {
    },
  • "createdAt": "2026-07-01T09:00:00.000Z",
  • "updatedAt": "2026-07-15T09:00:00.000Z"
}

Merge attributes into an identified user

Shallow-merges the given attributes into the user's metadata (RFC 7386 style at the top level): keys overwrite, null removes a key, omitted keys are kept. Creates the user if it doesn't exist (201).

Authorizations:
BearerAuth
path Parameters
userId
required
string non-empty
Example: user_123

The user's external ID — the same value you pass to window.Produktly.identifyUser(). URL-encode it.

Request Body schema: application/json
required
object

Attributes to merge (shallow, top level): keys overwrite, a key set to null is removed, omitted keys are kept, nested objects replace wholesale. Merged result max 32 KB serialized.

property name*
additional property
any

Responses

Request samples

Content type
application/json
{
  • "metadata": {
    }
}

Response samples

Content type
application/json
{
  • "userId": "user_123",
  • "metadata": {
    },
  • "createdAt": "2026-07-01T09:00:00.000Z",
  • "updatedAt": "2026-07-15T09:00:00.000Z"
}

Delete an identified user

Hard-deletes the identified user. Analytics events are kept (they reference the external ID as a plain string).

Authorizations:
BearerAuth
path Parameters
userId
required
string non-empty
Example: user_123

The user's external ID — the same value you pass to window.Produktly.identifyUser(). URL-encode it.

Responses

Response samples

Content type
application/json
{
  • "error": {
    }
}

Tags

List tags

Authorizations:
BearerAuth

Responses

Response samples

Content type
application/json
{
  • "data": [
    ],
  • "pagination": {
    }
}