API
Authentication
How to authenticate against the ProductOS REST API: the in-app session for browser code, API tokens for everything else.
Every endpoint under /api/v1/* is workspace-scoped and requires authentication. ProductOS supports three auth paths, all of which resolve to the same user, workspace and role:
- Session cookie: set automatically by the in-app login flow (
/login). Used by the browser, no extra config. - Supabase JWT: in-app code already holds a session; external scripts use an API token.
- API token: long-lived
pos_live_*token issued from Settings → API Tokens. Best for third-party tools, CI jobs, and external automations.
Which workspace. An API token belongs to the workspace it was issued in and always acts there; to script another workspace, issue a token in it. A browser session acts in the workspace of the page making the request (the app sends it in the x-productos-workspace header); a request that names a workspace you don't belong to, or none, acts in the one you used last.
Picking a method
| Caller | Recommended auth |
|---|---|
| Browser, in-app code | Session cookie (automatic) |
| Internal script, even one run by a signed-in person | API token (a script has no browser session) |
| External tool, CI, customer-built integration | API token |
| Your product sending feedback or NPS scores from a server | Feedback API secret key (sk_live_), see Feedback API |
| Your product sending feedback from the browser | Publishable key (pk_live_), same as the widget |
| Inbound provider webhooks | Provider HMAC (verified by ProductOS, you don't send a bearer) |
Using an API token
- Open Settings → API Tokens.
- Click New token (issuing tokens needs the
adminorownerrole). Pick a name (e.g. "n8n production") and the role the token should act as (defaults tocommenter; capped at your own role). An expiry can be set when issuing through the API (expires_at); the dialog issues non-expiring tokens. - Copy the
pos_live_xxxxxxxxxxxxxstring. This is the only time it's shown: store it in your secret manager immediately.
Send it as a bearer header:
curl 'https://productos.ca/api/v1/feedback?limit=10' \
-H "Authorization: Bearer pos_live_xxxxxxxxxxxxx"There is only one API token prefix, pos_live_. ProductOS does not have a sandbox / test-key environment. (Feedback intake keys, sk_live_ and pk_live_, are a separate, narrower kind: they can only submit feedback. See Feedback API.)
Capability is role-based, not scope-based
Tokens carry two things:
- A coarse scope set (
read,write,admin), defaults to["read", "write"]. Scopes are recorded on the token; what a request may do is decided by role. - A role at issuance (
viewer | commenter | member | admin | owner), defaults tocommenter.
The effective role is the more restrictive of:
- The token's stored role
- The token owner's current workspace role
The REST API and the MCP endpoint both check this on every request, so a token never outranks its owner, and a token whose owner has left the workspace stops working. ProductOS also revokes any token that outranks its owner the moment a role is lowered, and every token when the owner leaves.
There are no per-entity scope strings like feedback:read or features:write. The model is the workspace role matrix.
Webhook authentication
- Outbound webhooks from ProductOS are limited to outbound webhook providers (Slack, Microsoft Teams, Discord, Zapier, Make, n8n) configured under Settings → Integrations. Each posts to the URL you provided. There is no general-purpose outbound-webhook signature scheme; the planned design is in Webhooks.
- Inbound provider webhooks (GitHub, Linear, etc.) are signed by their respective providers and verified by ProductOS using each provider's documented scheme. You never authenticate inbound integrations with a bearer.
Common errors
| Code | Meaning | Fix |
|---|---|---|
401 unauthorized | Missing, expired, or malformed token | Re-fetch the token; verify the Bearer prefix; confirm the token wasn't revoked or auto-revoked by a role change |
403 forbidden | Token owner's role lacks the required capability | Re-issue from a higher-role user or have an admin promote the owner |
404 not_found | Resource doesn't exist or token's workspace doesn't have access | Verify the ID; confirm the token is for the right workspace |
429 rate_limited | Too many requests in the rate-limit window | Back off; respect retryAfterSeconds in the response body |
Next
- Feedback reference: fields, filters, examples.
- Workspaces and roles: the full capability matrix that tokens compose against.
- MCP server: for Claude Code session integration.