Skip to content
fwozenstart free

docs / reference

REST API

Get a key, call the API, connect an MCP client, and read every public route. Generated from the same route table the OpenAPI document is generated from.

complete · updated 1 september 2026

The REST API is in the free tier, rate-limited, with scoped tokens and a published OpenAPI document. Everything the dashboard does, the API does: it is the same /v1 routes the dashboard calls, authenticated with an fwz_ key instead of a session. There is no second code path.

  • The rendered reference — every operation with its request and response schema.
  • Error codes — what each code in the envelope means, and what to do about it. Every non-2xx body carries a docsUrl pointing at its own anchor there.
  • openapi.json — the document both of the above are built from.

Get a key

  1. Sign in and open Settings → API keys. Minting a key needs the owner or admin role.
  2. Create key, give it a name, and tick the narrowest scopes that do the job: freeze:read for a deploy gate, freeze:write to freeze and thaw. Scopes are fixed at creation and can never be widened.
  3. Pick an expiry. A key declared as an agent key must expire within 90 days.
  4. Copy the secret. It is shown once. Then export FWOZEN_TOKEN=fwz_live_… and send it as Authorization: Bearer $FWOZEN_TOKEN.

A key is organisation-wide and never user-scoped, so the scope list is the authorization. Two routes it can never call are minting another key and setting enforcement up: both are session-only and answer 403 credential_type_not_allowed, never a 401, so a script can tell the two apart. Rotating a key issues a new secret and keeps the old one working for a grace window, which is what makes a rotation something you can roll through CI without a deploy.

Calling it

Is this repository frozen?
curl -s "https://api.fwozen.com/v1/freeze-state?repo=acme/api-server" \
  -H "Authorization: Bearer $FWOZEN_TOKEN"
  • JSON in, JSON out. Send Content-Type: application/json on anything with a body. curl -d declares a form body on its own and the API answers 400 unsupported_content_type.
  • Idempotency. Idempotency-Key on a write makes a retry safe. The same key with a different body is a 409, so use one key per intent.
  • Pagination. Cursor-based. Follow the cursor the response gives you rather than building one.
  • Rate limits. Read RateLimit-Remaining and RateLimit-Reset off every credentialled response and honour Retry-After on a 429. No ceiling is published here, because the numbers are operational settings and a figure in prose would be a promise nobody made.
  • Unknown fields. /v1 is additive-only: new response fields ship without notice, so ignore the ones you do not know and treat an unfamiliar enum value as opaque.

Connect an MCP client

The same routes are a streamable-HTTP MCP server at https://api.fwozen.com/mcp. It takes the same two credentials the REST API does, and there is no MCP-specific token to go looking for. The header path is the one that works everywhere:

Claude Code, with an API key
claude mcp add --transport http fwozen https://api.fwozen.com/mcp \
  --header "Authorization: Bearer $FWOZEN_TOKEN"

Clients that read a config file take the same two lines:

mcpServers
{
  "mcpServers": {
    "fwozen": {
      "type": "http",
      "url": "https://api.fwozen.com/mcp",
      "headers": { "Authorization": "Bearer fwz_live_…" }
    }
  }
}

There is an OAuth path too, for clients that support client id metadata documents: point the client at the same URL with no header and it will be sent to the consent page. Dynamic client registration is deliberately not offered, so a client that can only do DCR has to use the header instead. A request with no credential answers 401 with a WWW-Authenticate header naming /.well-known/oauth-protected-resource, which is the discovery chain an MCP client follows on its own.

The tools are thin adapters over the routes below and carry the same authorization: a key that cannot call a route cannot call its tool. Reads and writes are never mixed in one tool, fwozen_enable_repos takes an explicit repository list, and fwozen_end_freeze is marked destructive and refuses without confirm: true.

Every public route

Generated from the same route table the OpenAPI document and llms-full.txt are generated from. auth says which credential a route takes: key is an fwz_ API key, session is a signed-in session or a device-flow token, both is either, and none takes no credential at all. Where scopes are listed, a key needs any one of them.

Organizations

  • GET /v1/me Current user and organization auth: session
  • GET /v1/me/organizations Organizations the current user belongs to auth: session
  • GET /v1/onboarding Onboarding state auth: session
  • POST /v1/onboarding/bootstrap Create the organization for the signed-in user (idempotent) auth: session
  • GET /v1/github/install-url GitHub App installation URL carrying a signed state nonce auth: session
  • GET /v1/installations/status Poll a GitHub App installation minted by GET /v1/github/install-url auth: both · scopes: repo:read
  • POST /v1/installations/claim Claim a GitHub App installation for this organization auth: session
  • GET /v1/orgs/current Get the organization auth: both · scopes: repo:read
  • PATCH /v1/orgs/current Update organization settings auth: session
  • GET /v1/members List members auth: session
  • PATCH /v1/members/{userId} Change a member's role auth: session
  • DELETE /v1/members/{userId} Remove a member auth: session
  • GET /v1/invitations List invitations auth: session
  • POST /v1/invitations Invite a member auth: session
  • DELETE /v1/invitations/{invitationId} Revoke an invitation auth: session
  • GET /v1/invitations/pending Invitations addressed to you auth: session
  • GET /v1/invitations/preview Read an invitation from its token auth: session
  • POST /v1/invitations/{invitationId}/accept Accept an invitation auth: session
  • GET /v1/invitations/{invitationId}/link Mint the accept link for a pending invitation auth: session
  • GET /v1/overview Dashboard rollup auth: both · scopes: freeze:read
  • GET /v1/attention Things needing attention auth: both · scopes: freeze:read

Repositories

  • GET /v1/repos List repositories auth: both · scopes: repo:read
  • POST /v1/repos/sync Re-sync the repository mirror from GitHub auth: both · scopes: repo:write
  • POST /v1/repos/enable Enable repositories auth: both · scopes: repo:write
  • POST /v1/repos/disable Disable repositories auth: both · scopes: repo:write
  • POST /v1/repos/preview-glob Preview which repositories a glob matches auth: both · scopes: repo:read
  • GET /v1/repos/{repoId} Get a repository auth: both · scopes: repo:read
  • PATCH /v1/repos/{repoId} Update repository settings auth: both · scopes: repo:write
  • GET /v1/repos/{repoId}/pulls List tracked pull requests of a repository auth: both · scopes: freeze:read
  • GET /v1/pulls/lookup Resolve a PR from a URL, owner/repo#123, or a number auth: both · scopes: freeze:read

Branches

  • GET /v1/repos/{repoId}/branches List watched branches of a repository auth: both · scopes: repo:read
  • GET /v1/repos/{repoId}/remote-branches List branches live from GitHub (cached 60s, budget-gated) auth: both · scopes: repo:read
  • POST /v1/repos/{repoId}/branches Watch a branch auth: both · scopes: repo:write
  • GET /v1/branches List watched branches auth: both · scopes: repo:read
  • GET /v1/branches/{branchId} Get a watched branch auth: both · scopes: repo:read
  • PATCH /v1/branches/{branchId} Update watched-branch settings auth: both · scopes: repo:write
  • DELETE /v1/branches/{branchId} Stop watching a branch auth: both · scopes: repo:write

Enforcement

  • GET /v1/enforcement Enforcement traffic light across the organization auth: both · scopes: repo:read
  • GET /v1/enforcement/branches/{branchId} Enforcement detail for a branch auth: both · scopes: repo:read
  • GET /v1/enforcement/branches/{branchId}/snippets gh CLI, Terraform and ruleset JSON snippets for a branch auth: both · scopes: repo:read
  • POST /v1/enforcement/verify Queue an enforcement probe auth: both · scopes: repo:write
  • POST /v1/enforcement/setup Plan the native GitHub rule, or have Fwozen apply it auth: session
  • POST /v1/enforcement/seed-check Write the current fwozen verdict to every enforcement-relevant head auth: session

Freezes

  • POST /v1/scope/preview Resolve a freeze scope without creating a freeze auth: both · scopes: freeze:read
  • POST /v1/freezes Start a freeze auth: both · scopes: freeze:write
  • GET /v1/freezes List freezes auth: both · scopes: freeze:read
  • POST /v1/freezes/lift Lift every freeze matching a scope auth: both · scopes: freeze:write
  • GET /v1/freezes/{freezeId} Get a freeze auth: both · scopes: freeze:read
  • GET /v1/freezes/{freezeId}/targets List the materialized targets of a freeze auth: both · scopes: freeze:read
  • POST /v1/freezes/{freezeId}/lift Lift a freeze auth: both · scopes: freeze:write
  • POST /v1/freezes/{freezeId}/extend Extend a freeze auth: both · scopes: freeze:write
  • POST /v1/freezes/{freezeId}/thaw-override Unfreeze inside a scheduled window, suppressing only that freeze auth: both · scopes: freeze:write
  • POST /v1/freezes/{freezeId}/retry Retry failed fan-out targets auth: both · scopes: freeze:write
  • GET /v1/freeze-state Is this repository, branch or organization frozen? auth: both · scopes: freeze:read

Schedules

  • GET /v1/schedules List schedules auth: both · scopes: schedule:read
  • POST /v1/schedules Create a schedule auth: both · scopes: schedule:write
  • POST /v1/schedules/preview Preview the occurrences of an unsaved schedule draft auth: both · scopes: schedule:read
  • GET /v1/schedules/occurrences List planned and past occurrences in a time range auth: both · scopes: schedule:read
  • GET /v1/schedules/calendar Occurrences grouped by day for one month auth: both · scopes: schedule:read
  • POST /v1/schedules/import Import a holiday calendar as schedules auth: session
  • GET /v1/schedules/{scheduleId} Get a schedule auth: both · scopes: schedule:read
  • PATCH /v1/schedules/{scheduleId} Update a schedule and re-materialize its horizon auth: both · scopes: schedule:write
  • DELETE /v1/schedules/{scheduleId} Delete a schedule auth: both · scopes: schedule:write
  • GET /v1/schedules/{scheduleId}/preview Preview the next occurrences of a saved schedule auth: both · scopes: schedule:read
  • POST /v1/schedules/{scheduleId}/skip-next Skip the next planned occurrence auth: both · scopes: schedule:write
  • POST /v1/schedules/{scheduleId}/run-now Apply or lift a schedule's window immediately auth: session

Exceptions

  • GET /v1/exceptions List exceptions auth: both · scopes: exception:read
  • POST /v1/exceptions Grant a per-PR exception (one row per covering freeze) auth: both · scopes: exception:write · requires the pr_exceptions entitlement
  • GET /v1/exceptions/{exceptionId} Get an exception auth: both · scopes: exception:read
  • POST /v1/exceptions/{exceptionId}/revoke Revoke an exception (never entitlement-gated) auth: both · scopes: exception:write
  • GET /v1/exception-requests List pending exception requests auth: both · scopes: exception:read
  • POST /v1/exception-requests Request an exception for a pull request auth: both · scopes: exception:write · requires the pr_exceptions entitlement
  • POST /v1/exception-requests/{exceptionId}/decide Grant or deny an exception request auth: both · scopes: exception:write · requires the pr_exceptions entitlement

Audit

  • GET /v1/audit List audit entries (read-time retention filter) auth: both · scopes: audit:read
  • GET /v1/audit/export Stream the audit log as CSV (hard cap 500 000 rows) auth: both · scopes: audit:read · requires the audit_export entitlement
  • GET /v1/audit/actions The audit action vocabulary auth: both · scopes: audit:read

API keys

  • GET /v1/api-keys/scopes The API key scope vocabulary auth: session
  • GET /v1/api-keys List API keys auth: session
  • POST /v1/api-keys Create an API key auth: session
  • POST /v1/api-keys/{apiKeyId}/rotate Rotate an API key with a grace window auth: session
  • DELETE /v1/api-keys/{apiKeyId} Revoke an API key auth: session

Billing

  • GET /v1/plans The plan catalog auth: none
  • GET /v1/billing Billing summary auth: both · scopes: billing:read
  • GET /v1/billing/prices Price catalog auth: session
  • POST /v1/billing/checkout Create a Stripe Checkout session auth: both · scopes: billing:write
  • POST /v1/billing/portal Create a Stripe Customer Portal session auth: both · scopes: billing:write
  • POST /v1/billing/trial Start the 14-day no-card Business trial auth: both · scopes: billing:write
  • GET /v1/entitlements Resolved entitlements, limits and usage auth: both · scopes: repo:read, billing:read