API documentation
Everything in ProductOS is available over a versioned REST API. The machine-readable spec lives at /api/v1/openapi.json.
Authentication
Create an API key in Settings → API keys and send it as a bearer token. Rate limits are per-workspace and depend on your plan (60–1200 requests/minute).
curl https://your-domain.com/api/v1/features \
-H "Authorization: Bearer pos_live_..."Resources
GET/POST /api/v1/features— roadmap items with RICE fields;GET/PATCH /api/v1/features/:idGET/POST /api/v1/feedback— customer feedback (deduped byexternalId)GET/POST /api/v1/tasks— execution tasksGET /api/v1/okrs— objectives with computed progressPOST /api/v1/events— product analytics ingestion (single or batch)
Boards
Boards are the collaborative canvas: a board holds items (STICKY, SHAPE, TEXT, FRAME, CONNECTOR) positioned at x/y and stacked by z. An item's color is a palette slot name — yellow, green, blue, pink, purple, orange, gray or white — not a hex value. Items can carry a featureId or feedbackId to link them back to roadmap and research records in the same workspace.
GET/POST /api/v1/boards— list (paginated,?search=) and create boardsGET/PATCH/DELETE /api/v1/boards/:id— a board with all of its items; delete cascades to items and votesGET/POST /api/v1/boards/:id/items— paginated items (back-to-front byz) and item creation
Example: build a retro board
# 1. create a board
curl -X POST https://your-domain.com/api/v1/boards \
-H "Authorization: Bearer pos_live_..." \
-H "Content-Type: application/json" \
-d '{"name": "Q3 retro", "template": "retro"}'
# => {"data": {"id": "brd_123", "name": "Q3 retro", ...}}
# 2. drop two stickies onto it
curl -X POST https://your-domain.com/api/v1/boards/brd_123/items \
-H "Authorization: Bearer pos_live_..." \
-H "Content-Type: application/json" \
-d '{"type": "STICKY", "x": 120, "y": 80, "text": "Shipped SSO", "color": "green"}'
curl -X POST https://your-domain.com/api/v1/boards/brd_123/items \
-H "Authorization: Bearer pos_live_..." \
-H "Content-Type: application/json" \
-d '{"type": "STICKY", "x": 320, "y": 80, "text": "Onboarding still slow", "color": "pink"}'Boards also export from the browser via /api/export/board/<id>?format=svg|json. That endpoint is session-authenticated (your signed-in workspace membership), not an API-key endpoint — svg returns a self-contained picture of the canvas and json (the default) returns the board plus its items, both as file downloads.
Example: pipe feedback from your app
curl -X POST https://your-domain.com/api/v1/feedback \
-H "Authorization: Bearer pos_live_..." \
-H "Content-Type: application/json" \
-d '{"title": "Need SSO", "customerEmail": "jane@acme.com", "sentiment": 1}'Example: track product usage
curl -X POST https://your-domain.com/api/v1/events \
-H "Authorization: Bearer pos_live_..." \
-H "Content-Type: application/json" \
-d '{"events": [{"name": "feature.used", "userKey": "u_123", "properties": {"feature": "export"}}]}'Webhooks
Subscribe endpoints in Settings → Webhooks to events like feature.status_changed, feedback.created and sprint.completed. Deliveries are retried with exponential backoff and signed with HMAC-SHA256 in the X-ProductOS-Signature header (t=<unix>,v1=<hex> over `${t}.${body}`):
import { createHmac, timingSafeEqual } from "crypto";
function verify(secret: string, body: string, header: string) {
const { t, v1 } = Object.fromEntries(header.split(",").map(p => p.split("=")));
const expected = createHmac("sha256", secret).update(`${t}.${body}`).digest("hex");
return timingSafeEqual(Buffer.from(expected), Buffer.from(v1));
}