Portal content API

Manage help articles, collections, changelog entries, and team roadmap items for an app. These are Customer Portal resources, separate from Engage campaigns and SDK ingest.

Dashboard routes require a WorkOS Bearer access token. Viewers can read; editors, admins, and owners can create, edit, and publish. Portal content must be enabled in the environment. Publication also requires a published company portal, an included app, and ready hosting.

Documents

Use one of these bases:

  • Help articles: /v1/apps/:appId/help/articles
  • Changelog entries: /v1/apps/:appId/changelog
Method and suffixPurpose
GET /List documents
POST /Create a private draft
GET /:documentIdRead the draft, published snapshot, revision, and publication readiness
PUT /:documentIdSave { revision, draft }
POST /:documentId/lifecycleApply { revision, action }
POST /:documentId/positionReorder a help article using { revision, direction }

For / rows, use the base itself. position is available for articles only. List filters include q, state (draft, published, archived), collectionId, category, cursor, and limit (default 20, maximum 50). Paginated results contain items and nextCursor; pass the returned cursor back with the same filters.

Creation accepts a UUID idempotencyKey and optional title, collectionId, and links. Reuse the creation key for a retry of the same operation.

POST /v1/apps/APP_ID/changelog
Authorization: Bearer WORKOS_ACCESS_TOKEN
Content-Type: application/json
 
{
  "idempotencyKey": "d0948d6e-65d2-4db1-9dba-e46670a4d8ef",
  "title": "Offline maps are ready",
  "links": []
}

Draft format and publication

draft is a complete structured object with title, summary, body, collectionId, category, releaseDate, version, and links. Both document kinds use this schema. Start from the object returned by GET and retain its required fields when saving.

The body is a validated content tree rooted at type: doc, rather than HTML or a Markdown string. It supports paragraphs, headings at levels 2 and 3, lists, quotes, code blocks, text marks, links, and app-scoped images.

{
  "type": "doc",
  "content": [
    {
      "type": "paragraph",
      "content": [{ "type": "text", "text": "Save your route before leaving Wi-Fi." }]
    }
  ]
}

links uses { kind: "request" | "roadmap", id: UUID } records from the same app. Articles need an active collection before publication. Changelog entries use collectionId: null, a category of new, improved, or fixed, and a YYYY-MM-DD release date no later than today. Future scheduled publication is unavailable.

Lifecycle actions are publish, unpublish, archive, and restore. Publishing copies the saved draft into the public snapshot. Later saves stay private until another publish. Restore returns archived content to a draft, and its stable public slug is retained.

Every save or lifecycle mutation uses the current revision. Use the revision returned by the last successful operation. On a conflict, fetch the current document, reconcile your edits, and submit its latest revision.

Help collections

Base: /v1/apps/:appId/help/collections.

Method and suffixBody or behavior
GET /Paginated list using the content query parameters
POST /{ title, description, idempotencyKey }
PATCH /:collectionId{ revision, title?, description?, archived? }
POST /:collectionId/position{ revision, direction }, where direction is up or down

Collections have revisions and immediate metadata updates. They do not use document publication actions. Archiving requires moving or archiving their active articles first. Public collection reads only expose non-archived collections with published articles.

Roadmap items

Base: /v1/apps/:appId/roadmap.

Method and suffixBody or behavior
GET /Required status; optional cursor, limit, archived
POST /{ title, description, status, requestIds, idempotencyKey }
GET /:itemIdRead one team item with its linked requests and revision
PUT /:itemId{ revision, title, description, requestIds }
POST /:itemId/lifecycle{ revision, action } using document lifecycle actions
POST /:itemId/statusMove the item and explicitly selected requests

Statuses are planned, in_progress, and shipped. A list can contain both request cards and team-item cards; inspect each card's kind. Team items have their own UUIDs and are not request IDs.

The status body is { revision, status, requests: [{ id, updatedAt }] }. Only those selected linked requests change status; include their current updatedAt values. Use requests: [] to move only the team item. Hidden requests cannot move this way. Request publication stays independent. Existing follower rules queue in-app SDK instructions; this operation does not send automatic native push.

Unlike documents, saving changes to an already published team item updates its live content immediately.

GET /v1/apps/:appId/portal-content/link-targets finds records to link. Supply kind (request or roadmap), optional q, ids (comma-separated UUIDs), cursor, and limit (maximum 100).

Upload images with POST /v1/apps/:appId/portal-content/assets/:kind/:documentId, where kind is article or changelog. Send multipart form data with a file part and UUID Idempotency-Key header. The document must belong to this app. Use the returned asset ID in an image content node. Read private images at GET /v1/apps/:appId/portal-content/assets/:assetId with dashboard authentication.

Public content

Public prefix: /v1/portal/:portalSlug/apps/:appSlug. No visitor session is needed to read published content.

GET /v1/portal/:portalSlug/apps/:appSlug/help/collections
GET /v1/portal/:portalSlug/apps/:appSlug/help/collections/:collectionId
GET /v1/portal/:portalSlug/apps/:appSlug/help/articles
GET /v1/portal/:portalSlug/apps/:appSlug/help/articles/:documentId
GET /v1/portal/:portalSlug/apps/:appSlug/changelog
GET /v1/portal/:portalSlug/apps/:appSlug/changelog/:documentId
GET /v1/portal/:portalSlug/apps/:appSlug/roadmap?status=planned
GET /v1/portal/:portalSlug/apps/:appSlug/roadmap/:itemId
GET /v1/portal/:portalSlug/apps/:appSlug/requests/:requestId/changelog
GET /v1/portal/:portalSlug/apps/:appSlug/roadmap/:itemId/changelog
GET /v1/portal/:portalSlug/apps/:appSlug/assets/:assetId

Article and changelog lists support q, collection/category filters, and cursor pagination. The roadmap requires a column status. Related-changelog lists accept cursor and limit. Public reads exclude drafts, archived records, and private links; media is served only when eligible for public access.

Canonical schemas are in packages/sdk-core/src/schemas/portal-content.ts; routes are in apps/api/src/routes/portal-content.ts.