Documentation
CapstanAPI 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:
- While signed in, request an authorization with a
POSTto/bfc/device-authorizationscarrying{"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 adevice_code, auser_codeand averification_uri. - Open the
verification_uriin a browser signed in as the same person and press Approve. - Collect the token with a
POSTto/bfc/device/tokencarrying{"device_code":"<device_code>"}. Repeat until it stops answeringauthorization_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.