FixControl/Documentation

User Guide

Public API

Tenant-scoped REST API for issues, patches, integrations, and webhooks. Bearer-token authentication.

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

  1. Open Settings → Integrations → Public API.
  2. Click Create key.
  3. Give it a name (so you remember what it's for) and pick scopes — see below.
  4. 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.

ScopeEndpoints
issues:readGET /api/public/v1/issues, GET /api/public/v1/issues/:key
issues:writePOST /api/public/v1/issues
patches:readGET /api/public/v1/patches, GET /api/public/v1/patches/:id
patches:writereserved for future write actions on patches — no endpoints today
integrations:readGET /api/public/v1/integrations/status
webhooks:readGET /api/public/v1/webhooks
webhooks:writePOST /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 → PublicIssue

PublicIssue 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
→ PublicPatch

PublicPatch 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.

Statuscode examples
400missing_field, invalid_field, invalid_url, invalid_events, invalid_body
401missing_token, invalid_token
403insufficient_scope
404not_found
429rate_limited (with Retry-After header)
5xxinternal_error

OpenAPI

The OpenAPI 3.1 spec is served at:

GET /api/public/v1/openapi.json

It'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-After header 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.

Anything unclear or wrong?Let us know →

FixControl is a trade name of FixControl B.V. i.o.