Conventions

The rules every endpoint shares. The endpoint reference lists each operation, with the scope it needs and its request and answer fields.

Base URL and authentication

Every endpoint is under https://api.forgeling.io/v1. Requests and answers are JSON (Content-Type: application/json), over HTTPS only: plain HTTP is not served, and not redirected. Authenticate every request with an API key as a bearer token:

curl https://api.forgeling.io/v1/app \
  -H "Authorization: Bearer $FORGELING_API_KEY"

GET /v1/app works with any valid key. It answers your app's tier, its quotas and current usage, and the calling key's scopes and restrictions, so it is a good first call for checking a key.

Every answer carries a Request-Id header (req_…). Include it when you ask Forgeling about a request.

API keys

An API key has the form fgk_<kid>_<secret>: the prefix fgk_, a 16-character key id in lower-case Crockford base32, _, and a 43-character base64url secret (256 bits). Forgeling stores only the key id and a hash of the secret. The fixed prefix is registered with secret scanners.

Missing, malformed, revoked and expired keys all get the same answer, 401 invalid_api_key.

Scopes

A key carries a list of scopes, and each operation needs one of them; the reference names it. GET /v1/app needs none.

Scope Grants
workspaces:read Reading workspaces.
workspaces:write Creating, changing, suspending, resuming and deleting workspaces.
credentials:read Reading a workspace's model credential: its type, status and last four characters, never the key.
credentials:write Storing and deleting a workspace's model credential.
bundles:read Reading bundles and their versions.
bundles:write Publishing and yanking bundle versions.
runs:read Reading runs and turns.
runs:write Starting, changing, completing, cancelling and deleting runs; sending and cancelling turns; run secrets.
approvals:read Reading approvals.
approvals:write Deciding approvals.
events:read Reading events: a run's history, its live stream and the app-wide feed.
files:read Reading output files and downloading their content.
webhooks:read Reading webhook endpoints, the delivery log and the credential-refresh URL.
webhooks:write Creating, changing, testing and deleting webhook endpoints, rotating their secrets and retrying deliveries. It also covers the credential-refresh URL, /v1/app/credential_refresh.
usage:read Reading usage.

A write scope does not imply read. A key with runs:write alone can start a run but cannot read it back; give it runs:read as well. Give each key only the scopes its code uses.

Restricted keys

A key may be restricted to some workspaces, by workspace_ids or by an external_id_prefix (one key for your development workspaces and another for production, say). Such a key gets 404 on /v1/webhook_endpoints*, /v1/webhook_deliveries* and /v1/app/credential_refresh*, which are app-level. List routes and GET /v1/events return only its workspaces (GET /v1/events leaves out app-level events such as bundle.* and ping). Bundles belong to the whole app, so bundle routes work as for any key. Creating a workspace whose external_id is outside the key's prefix, or with a key restricted to workspace_ids, answers 404. A resource outside its restriction answers 404, never 403.

An app has at most 20 active keys.

Errors

An error answer has a 4xx or 5xx status and this body:

{"error": {"type": "permission", "code": "missing_scope", "message": "This key does not have the runs:write scope.", "request_id": "req_01M6H2C4Q8ZV3N7RT5WXKB9JDE", "details": {"required_scope": "runs:write"}}}
Status Type Codes
400 invalid_request malformed_json: the body is not valid JSON. idempotency_key_required: a POST …/runs or POST …/turns without an Idempotency-Key. unsupported_version: a Forgeling-Version other than 2026-11-01.
401 authentication invalid_api_key: the key is missing, malformed, revoked or expired.
403 permission missing_scope: the key lacks the operation's scope (details.required_scope). approval_required: your app is not approved yet, so it cannot start runs.
404 not_found not_found: no such route, or a resource that does not exist, belongs to another app, was deleted or is outside the key's restrictions.
409 conflict suspended: the app or the workspace is suspended. idempotency_conflict: an Idempotency-Key reused with a different body. credential_missing: the workspace has no valid model credential. quota_exceeded: a quota is used up (details.quota). run_not_active: the run has ended or is completing. turn_not_cancellable: the turn has already ended. bundle_version_exists: that version is already published. approval_not_pending: a decision on an approval that is no longer pending.
422 invalid_request validation_failed: the body breaks a rule (details.errors, a list of {path, message}). unsupported_credential_type: a model credential type other than anthropic_api_key. credential_invalid: Anthropic refused the model key. tier_limit: a value above your tier's maximum (details.field, details.max). bundle_version_unavailable: the bundle version is not ready, or is yanked. repo_host_unsupported: the repository is not on github.com. repo_policy_ack_required: a repository set to allow_unprotected without repo_policy_ack, the record of its admin's opt-in.
429 rate_limit rate_limited: too many requests, or too many open event streams; wait as long as Retry-After says.
500, 502, 503 api_error internal: something failed on Forgeling's side. unavailable: Forgeling cannot serve the request right now; try again shortly.

A request that fails several checks gets the first of these: a bad key (401), the rate limit (429), an unknown route (404), a missing scope (403), a resource the key cannot reach (404), a suspended app or workspace (409). A resource you cannot reach answers 404, never 403, so an answer never tells you whether another app's resource exists.

Retry a 429 after Retry-After, and a 500, 502 or 503 with backoff; send a retried POST with the same Idempotency-Key. Any other 4xx fails the same way until the request changes.

Idempotency

Idempotency-Key is required on POST …/runs and POST …/turns and accepted on every other POST. Use a value from your own system that names the action, such as pipeline-<id>, not a fresh random value on each attempt: a retry must carry the same key.

Pagination

List endpoints take ?after= and ?limit= (default 20, max 100) and answer:

{"object": "list", "data": [], "has_more": false, "next_after": null}

While has_more is true, pass next_after back as after to get the next page:

curl "https://api.forgeling.io/v1/workspaces?limit=100&after=$NEXT_AFTER" \
  -H "Authorization: Bearer $FORGELING_API_KEY"

Treat next_after as an opaque cursor and pass it back unchanged; never build one yourself. One list differs: the after of GET /v1/runs/{run}/events is a stream.seq, so you can read a run's history from the last event you applied (Order and reconcile).

Ids, timestamps and money

Rate limits

Requests are limited per key, as a rate with a burst; the values for each tier are on the limits page, and GET /v1/app returns yours in quotas. A request over the limit gets 429 rate_limited with a Retry-After header, in seconds. Wait that long before the next request.

Versioning

The major version is in the path. /v1 changes only by adding: new endpoints, fields, event types, enum values and error codes.

A dated Forgeling-Version header selects the version of the API's behaviour. There is one date so far, Forgeling-Version: 2026-11-01; any other value is 400 unsupported_version. Your app is pinned to the date of its first call, and each webhook endpoint to the api_version it was created with, so deliveries keep the shape your receiver was written for.

A breaking change ships only under a new date or as /v2. An old version stays supported for at least 12 months, and its end is announced at least 6 months ahead, with Deprecation and Sunset headers on its answers, a changelog entry and an email to your app's members.

Beta features sit behind the Forgeling-Beta header, a comma-separated list of features, such as Forgeling-Beta: runtime-next.

The runtime. Runs pin the Claude Code CLI through their run image, which a run keeps for its whole life. A new run image whose CLI changes how tool rules match is announced at least 30 days ahead. You can try it early by starting a run with "runtime": "next" and the header Forgeling-Beta: runtime-next, and keep the previous image for 90 days after the switch with "runtime": "previous". Runtime pinning has the details.

Unknown fields

Clients must ignore unknown fields, event types and enum values. Additions arrive within /v1 without a new version, so read the fields you know and skip the rest, and handle an error code you do not recognise by its type and HTTP status.