The FixControl Public API lets you read and act on FixControl from your own tools — a custom dashboard, a PM script, an internal automation. The base path is /api/public/v1/ on your FixControl host.
Creating an API key
- Open Settings → Integrations → Public API.
- Click Create key.
- Give it a name (so you remember what it's for) and pick scopes — see below.
- Click Create. The full token is shown once. Store it in your secret manager immediately.
The UI keeps the prefix (fck_live_a1b2…) and the creation time so you can identify the key later, but the full token is not recoverable. If you lose it, revoke and create a new one.
Scopes
A key carries one or more scopes. Pick the smallest set that does the job.
| Scope | Endpoints |
|---|---|
issues:read | GET /api/public/v1/issues, GET /api/public/v1/issues/:key |
issues:write | POST /api/public/v1/issues |
patches:read | GET /api/public/v1/patches, GET /api/public/v1/patches/:id |
patches:write | reserved for future write actions on patches — no endpoints today |
integrations:read | GET /api/public/v1/integrations/status |
webhooks:read | GET /api/public/v1/webhooks |
webhooks:write | POST /api/public/v1/webhooks |
A key without any scopes can authenticate but every authorized endpoint returns 403 with code: insufficient_scope.
Authentication
Every request needs an Authorization: Bearer <token> header. The token is bound to a single tenant — there is no separate tenant query parameter or body field; tenant is forced from the key.
curl https://<your-host>/api/public/v1/issues \
-H "Authorization: Bearer fck_live_a1b2c3d4..."A missing token returns 401 with code: missing_token. An invalid or revoked token returns 401 with code: invalid_token. A valid token without the right scope returns 403 with code: insufficient_scope.
Rate limits
Each key has a token-bucket budget of 60 requests per minute (1 token per second sustained, with a burst of 60). The bucket is per-key, so two keys in the same tenant don't share a budget.
When the bucket is empty you receive 429 Too Many Requests with Retry-After in seconds and code: rate_limited. If you need a higher limit for a legitimate use case, contact us.
Endpoints
Issues
# List recent issues for the calling key's tenant
GET /api/public/v1/issues
→ { "items": PublicIssue[] }
# Fetch one issue by key
GET /api/public/v1/issues/:key
→ PublicIssue
# Create an issue
POST /api/public/v1/issues
{
"title": "Checkout fails on mobile",
"kind": "BUG", // BUG | FEATURE | SUPPORT
"priority": "high", // critical | high | medium | low
"reporter": "alex@acme.com",
"summary": "Optional short summary",
"body": "Optional long description",
"customer": "Optional customer name",
"flags": ["payment"], // up to 32 string flags
"assignee": "Optional internal id"
}
→ 201 Created → PublicIssuePublicIssue shape:
{
"key": "iss_01HV...",
"tenant": "acme",
"kind": "BUG",
"title": "Checkout fails on mobile",
"summary": "...",
"body": "...",
"priority": "high",
"status": "intake",
"readiness": "investigate",
"confidence": 0.82,
"flags": ["payment"],
"reporter": "alex@acme.com",
"assignee": null,
"customer": null,
"createdAt": "2026-05-03T10:14:22.512Z",
"updatedAt": "2026-05-03T10:14:22.512Z",
"externalTracker": "jira",
"externalKey": "ACME-1234",
"externalUrl": "https://acme.atlassian.net/browse/ACME-1234",
"externalStatus": "In Progress"
}Patches
# List patches for the calling key's tenant (newest first)
GET /api/public/v1/patches?limit=100 # limit: 1..200
→ { "items": PublicPatch[] }
# Fetch one patch
GET /api/public/v1/patches/:id
→ PublicPatchPublicPatch carries patch id, workspace id, version, status, file count, additions/deletions, file list, and timestamps.
Integrations
GET /api/public/v1/integrations/status
→ { "items": [
{ "id": "...", "type": "github", "name": "...", "status": "connected",
"provider": "github", "authKind": "app",
"createdAt": "...", "lastError": null },
...
] }No tokens, refresh tokens, installation ids, or recipient routing rules cross the wire — only the public-safe summary.
Webhooks
# List outbound webhooks
GET /api/public/v1/webhooks
→ { "items": PublicWebhook[] }
# Create one. Returns the plaintext secret EXACTLY ONCE.
POST /api/public/v1/webhooks
{
"name": "Billing notifier",
"url": "https://hooks.acme.com/fc",
"events": ["issue.created", "patch.approved"],
"enabled": true
}
→ 201 Created → { "webhook": PublicWebhook, "secret": "ws_AB3cD..." }Valid event values: issue.created, issue.updated, patch.ready, patch.approved, patch.failed, pr.created, integration.error. See the Webhooks guide for the delivery format and signature scheme.
Error responses
Every error returns JSON in this shape:
{ "error": "Human-readable message", "code": "stable_machine_code" }The HTTP status carries the category; code is the stable identifier for client logic.
| Status | code examples |
|---|---|
| 400 | missing_field, invalid_field, invalid_url, invalid_events, invalid_body |
| 401 | missing_token, invalid_token |
| 403 | insufficient_scope |
| 404 | not_found |
| 429 | rate_limited (with Retry-After header) |
| 5xx | internal_error |
OpenAPI
The OpenAPI 3.1 spec is served at:
GET /api/public/v1/openapi.jsonIt's the source of truth — point your code generator at it. The docs follow the spec; if anything diverges, file an issue.
Best practices
- Treat the token like a password. Never commit it; never print it in logs.
- Use one key per integration. Easier to rotate, easier to audit, easier to scope.
- Rotate periodically. Create the new key, switch your client, then revoke the old one.
- Prefer webhooks over polling. Subscribe an outbound webhook instead of polling
GET /issues— it's both cheaper and more reliable. - Handle 429s with backoff. Honour the
Retry-Afterheader instead of retrying immediately.
FAQ
Is the API the same as the dashboard's internal API? No. The dashboard talks to internal routes that require a session cookie and are not stable. The Public API at /api/public/v1/... is what we commit to — versioned and OpenAPI-described.
Can a key access more than one tenant? No. Keys are tenant-scoped by design. Cross-tenant work needs a key per tenant.
What if a key leaks? Revoke it immediately from Settings → Integrations → Public API. Keys are checked on every request — nothing is cached — so requests with a revoked key are rejected with 401 from the moment you revoke. Then check the audit log for activity by that key.