The Public API is bearer-token, tenant-scoped REST. Base path: /api/public/v1/. User-facing setup steps and the endpoint catalog are in the user API guide. This page is the operational view.
Key model

Every API key:
- Is tenant-scoped. A key authenticates as the tenant, not as an individual user.
- Has a prefix (the first 12 characters, e.g.
fck_live_a1b2) shown in the UI so you can identify it without ever exposing the full token. - Is stored as a one-way hash. The full token is shown once at creation and never again — even an admin with full database access cannot recover it.
- Carries a set of scopes (
issues:read,issues:write,patches:read,patches:write,integrations:read,webhooks:read,webhooks:write). - Has a name (your label), a created by user, and created, last used, and revoked timestamps.
Revocation is non-destructive: the key stops working immediately but the record stays for audit. Authenticating with a revoked key returns 401 with code: invalid_token.
Authentication errors
| Situation | HTTP | code |
|---|---|---|
No Authorization header | 401 | missing_token |
| Unknown or revoked key | 401 | invalid_token |
| Valid key, wrong scope for the endpoint | 403 | insufficient_scope |
| Over the per-key rate limit | 429 | rate_limited |
Rate limits
Default: 60 requests per minute per key, with bursts of up to 60 and a sustained rate of 1 request / second after the burst is exhausted. Hitting the limit returns 429 with a Retry-After header and code: rate_limited.
Limits are tracked per key, not per tenant. Two keys in the same tenant don't share a budget, so one runaway integration can't starve another. Contact support if you need a higher tier for a specific key.
Scopes
There are 7 scopes:
| Scope | Endpoints |
|---|---|
issues:read | GET /issues, GET /issues/:key |
issues:write | POST /issues |
patches:read | GET /patches, GET /patches/:id |
patches:write | reserved — no endpoints today |
integrations:read | GET /integrations/status |
webhooks:read | GET /webhooks |
webhooks:write | POST /webhooks |
A key without any scope can authenticate but every authorized endpoint returns 403. Useful as a canary: "tell me when this key is being used somewhere I didn't expect".
Audit
Every API request — success and every kind of failure — lands in the audit log:
event—public_api.usedon success,public_api.auth_failedon every failure mode.tenant— the tenant the key belongs to (or empty if the token didn't resolve).targetKind: "api_key",targetId— which key was used (or empty for an unresolved bearer).fields.reason— on failures:missing_token,invalid_token,missing_scope,rate_limited.fields.scope— the scope the endpoint required.fields.path— the request path.
Why audit failures too: an authenticated API is your external attack surface. A scanner trying random tokens or hammering a scope it shouldn't have is a leading indicator of compromise, and the audit log is the durable record you can review and alert on.
OpenAPI
The OpenAPI 3.1 spec is served at /api/public/v1/openapi.json. Point any OpenAPI-aware client (Postman, Insomnia, code generators) at that URL to get an up-to-date description of every endpoint, request shape, and response shape.
Operational guidance
- One key per integration. Easier rotation, easier audit, easier scope.
- Don't generate a key with all scopes "just in case". Limit blast radius.
- Rotate on a fixed cadence. 90 days is reasonable for high-traffic keys; longer is fine for low-traffic ones if you have audit alerts.
- Wire the `request_id` from API responses into your client logs. When an API call fails, support can correlate by request id; sharing yours saves a round-trip.
- Prefer webhooks to polling. A poll loop on
GET /issuesis allowed but wasteful — subscribe an outbound webhook forissue.*events and react instead.
FAQ
Is the Public API the same as the dashboard's internal API? No. The dashboard uses internal routes that require a logged-in session and are not stable. The Public API at /api/public/v1/... is what we commit to — versioned, OpenAPI-described, and breaking changes go through deprecation notices.
Can a key access more than one tenant? Never. By design.
Why is there an `integrations:read` scope but no `integrations:write`? Today the public API exposes the read-only health snapshot. Mutating integrations is a UI flow because it requires multi-step OAuth handshakes that don't fit cleanly under bearer-token write semantics.
What's the SLA? Availability targets and incident comms are part of your subscription agreement, not the docs. Reach out via /contact for the operational details.