Skip to content
Scalpels home

Scalpels is officially launching on October 31, 2026. Special early adopter pricing is available now.

← All documentation

Documentation

Capstan

API tokens

Updated

Capstan's two HTTP APIs never accept a password. They accept a bearer token issued for one specific job, called a purpose, plus a header naming the person the token belongs to.

Purpose Used for
capstan.artifact.ingest POST /api/v1/artifacts
capstan.postmaster.poll POST /api/v1/poll

A token is bound to its purpose, to this installation and to the person it was issued to. A token for one purpose is rejected on the other endpoint.

Use the device flow, not the credentials page

Your personal credentials page, /bfc/ui/credentials/personal, offers to issue a token for either purpose, but a token issued there is rejected with 401 by both APIs in the current release. Use it to see and revoke credentials, not to create them.

Tokens that work are issued by a device authorization: a program asks for one, you approve it in your browser, and the program collects it.

  • For Postmaster, you do not do this by hand. The installer on the Postmaster page runs the whole flow and stores the token on the agent machine. See Postmaster.
  • For artifact hosting, run the flow yourself:
  1. While signed in, request an authorization with a POST to /bfc/device-authorizations carrying {"app_purpose":"capstan.artifact.ingest","label":"my laptop"}. This needs your browser session and its CSRF token, so it is easiest from a script that signs in first. The response holds a device_code, a user_code and a verification_uri.
  2. Open the verification_uri in a browser signed in as the same person and press Approve.
  3. Collect the token with a POST to /bfc/device/token carrying {"device_code":"<device_code>"}. Repeat until it stops answering authorization_pending.

The final response is {"access_token":"tok_…","token_type":"Bearer","credential_id":"…","app_purpose":"…"}. The authorization expires ten minutes after step 1, and a device code can be exchanged only once. Capstan keeps only a hash of the token, so store it where your tool can read it; it cannot be shown again.

If you script step 1, the cookie file it uses holds a live login. Keep it out of any repository and delete it when you are done.

The actor id header

Every API call sends X-Capstan-Actor-ID with your numeric account id, alongside the token. Capstan compares the two and rejects a mismatch before doing anything else.

The id is not shown in the interface. The Postmaster installer writes it to ~/.config/capstan/<server-id>/actor-id on each agent machine. Otherwise, read it inside the environment with Tinker, replacing the email address with yours:

php artisan tinker --execute='echo ArtisanBuild\BuiltForCloud\User::query()->where("email","[email protected]")->value("id");'

Errors

Capstan's own errors share one shape, so a client can branch on error.code:

{"error":{"code":"unauthenticated","message":"Unauthenticated."}}
Status and code Meaning
401 unauthenticated The token is missing, wrong, for the other purpose, or does not belong to the actor id you sent.
422 validation_failed Bad input. The response carries a per-field errors object.
409 inbox_claimed Postmaster: another person already owns an inbox you advertised. error.inboxes lists them.
403 sender_not_owned Postmaster: you sent from an inbox you do not own. error.index names the message.
404 The capability is switched off, or the artifact is missing or expired.

Rate limit

Each person gets 60 API requests per minute, counted across both endpoints together. The 61st is rejected before Capstan's own code runs, so it is a plain 429 with Retry-After and X-RateLimit-* headers and no error.code. Handle it separately from the errors above.

A Postmaster agent polling once a minute uses one request a minute, so several agents belonging to one person share that budget with any artifact publishing that person does.