18790 and no access token. With STUDIO_ACCESS_TOKEN set, add -b 'clawboo_access=<token>' to each curl. Every route is on the general rate limit; the routes marked sensitive also count against the stricter sensitive limit (see the REST API overview). Errors use { "error": "<message>" }; a 400 for a body that fails validation adds details, the validator’s form and field errors.
Local connectors run as child processes of the Clawboo server, as your user, with no sandbox. The server does not tell callers apart: any request that passes the access gate can connect a connector or create a grant. See Security before you open Clawboo to other machines.
Routes
Connectors
The routes that look a connector up by slug (connect, authorize and the two config routes) search the built-in catalog first, then your custom connectors.path-suggestions reads the catalog only.
What is stored where. A credential goes to Clawboo’s encrypted vault under connector:<slug>:<KEY> and is never returned. A launch argument is a plain setting, connector:<slug>:argument, so it can be shown back to you. An OAuth sign-in keeps its client registration and tokens in the vault under connector-oauth-client:<slug> and connector-oauth-tokens:<slug>.
At startup, the server reconnects every connector you left connected, as long as it still has what it needs (the same check POST /api/connectors/connect makes). A Disconnect keeps a connector down across restarts. On a machine with no saved choice for them, the server also starts the Playwright and Chrome DevTools browser connectors.
GET /api/connectors
Lists the connectors running now. A connector that is defined but not connected is not listed.
Responses
200 OK:
skipped lists the tools Clawboo could not offer, each with a reason, such as duplicate-name. An entry named (inventory) with the reason tool-list-truncated means the server’s tool list could not be read to the end.
500 Internal Server Error: { "error": "<message>" }, redacted.
Example
GET /api/connectors/configured
One request for the whole shelf: which connectors, built-in and custom, already have what they ask for. Presence only; no value or per-connector detail comes back.
Responses
200 OK:
slugs lists every connector whose configuration is complete: required credentials stored, the launch argument present when one is required, and, for a remote connector that signs in with OAuth, a sign-in on file. supplied lists every connector you actually gave something to, meaning a stored credential or a stored argument. A connector that asks for nothing is in slugs but not in supplied.
500 Internal Server Error: { "error": "<message>" }, redacted.
Example
GET /api/connectors/path-suggestions
Real paths to offer as one-click choices for a connector that takes a path argument. Each one is checked on disk before it is offered, so the list can be empty.
- Query params:
slug(required): a built-in connector that takes a path, such asfilesystemorsqlite.
sqlite, the server searches up to two folders deep from the folder you started clawboo in, skipping hidden entries and node_modules and never following links. It stops looking once it has 25 .db, .sqlite or .sqlite3 files, sorts them, and offers the first five, each labelled with its file name. For any other slug it offers, in order, Where you started clawboo, then Documents, Desktop and Downloads in your home folder, keeping those that exist. Either way the list holds at most five paths, with no repeats.
The folder you started clawboo in comes from CLAWBOO_LAUNCH_DIR, which the CLI sets; a development server uses its own working directory.
Responses
200 OK:
404 Not Found: the slug is missing, is not a built-in connector, or names one that takes no path:
500 Internal Server Error: { "error": "<message>" }, redacted.
Example
POST /api/connectors/connect
Sensitive. Starts one connector: it spawns the local process, or opens the session to a remote server, and returns the tools the connector offers. Connecting a connector that is already running returns that session. A request that arrives while a connect for the same slug is under way waits for it instead of starting a second process.
Before it starts anything, the server checks, against the vault and the stored settings, that the connector has what it needs. Anything missing answers 422 with a reason:
When several apply, the response names one. For an OAuth connector, an expired token that can be refreshed is refreshed during this check.
- Request body:
Responses
200 OK: the connector is up:
400 Bad Request: { "error": "invalid body", "details": { ... } }.
404 Not Found: no built-in or custom connector has that slug:
422 Unprocessable Entity: refused, with the reason from the table and a sentence for people:
502 Bad Gateway: the process or the handshake failed. A failure that never reached the other end is retried first, up to three attempts with a doubling delay; a refusal from the other end is not. error names the connector and the likely cause, or reads <Name> did not start. when none matches. Likely causes include a missing npx or uvx, a Node.js that is too old, a package or version that is not published, a rejected key, a missing scope, a timeout, an unreachable host and a missing file. detail is the original message, redacted:
502, never a 500.
Example
POST /api/connectors/:slug/disconnect
Sensitive. Stops a running connector and records that you want it stopped, so it stays down after a restart. The intent is recorded first, so even a 404 has marked an existing connector as disconnected. If a connect for the same slug is under way, the route waits for it to finish before it decides.
Responses
200 OK: { "ok": true }.
404 Not Found: nothing with that slug was running:
500 Internal Server Error: stopping it failed, { "error": "<message>" }, redacted.
Example
POST /api/connectors/:slug/authorize
Sensitive. Starts an OAuth sign-in for a remote connector and returns the URL to open. The server opens nothing itself, because it may not be on the machine with the browser. No credential of yours is involved: Clawboo finds the provider’s authorization server and registers itself with it. The provider redirects back to a short-lived listener on http://127.0.0.1:<port>/callback, never to a route under /api. The port is any free port, except that the port of an earlier registration for this connector is tried first, so that registration can be reused.
Starting a sign-in cancels any earlier pending one for the same connector, so at most one is pending per connector. The discovery and registration steps are tried up to three times in all, with a delay that starts at 300 ms and doubles, but only for failures that never reached the provider.
Responses
200 OK:
400 Bad Request: a local connector has nothing to sign in to:
404 Not Found: { "error": "no such connector" }.
502 Bad Gateway: every other failure, which is almost always at the provider. Both messages name the connector:
Example
POST /api/connectors/:slug/authorize/await
Waits until the sign-in started by POST /api/connectors/:slug/authorize finishes, so the browser can open the URL first and wait afterwards. The listener gives up 5 minutes after the sign-in started, so this request returns within about 5 minutes. Two callers waiting on the same sign-in get the same answer.
200 means the provider’s code was exchanged and the tokens are stored, not only that the redirect arrived. Everything else is a 400.
Responses
200 OK:
400 Bad Request: the sign-in did not complete. The message keeps its Error: prefix:
A failure in the token exchange comes back here too, with that exchange’s own message.
Example
DELETE /api/connectors/:slug/authorize
Sensitive. Signs a connector out, in this order:
- Cancels a sign-in that is still pending, so it cannot finish and store new tokens.
- Stops the connector if it is running, and marks it disconnected.
- Deletes the stored client registration and tokens.
200 for any slug, whether or not anything was stored or running.
Responses
200 OK: { "ok": true }.
500 Internal Server Error: stopping the connector or deleting the tokens failed, { "error": "<message>" }, redacted.
Example
GET /api/connectors/:slug/config
Everything a connector needs before it can run, and whether you have supplied it. Credentials come back as a present flag only; the launch argument comes back in full, so you can check which folder or file a connector was given.
Responses
200 OK:
authorized is the OAuth question. Only a remote connector that signs in with OAuth can answer false; every other connector reports true. satisfied is true when every required credential is stored, a required launch argument is present, and authorized is true. A connector that declares an argument without requiring it is satisfied without one.
404 Not Found: { "error": "no such connector" }.
500 Internal Server Error: { "error": "<message>" }, redacted.
Example
PUT /api/connectors/:slug/config
Sensitive. Stores the credentials and the launch argument for one connector. These are your own credentials for the connector itself, entered once; a Composio key goes through PUT /api/connectors/composio/key instead.
The slug is checked before the body, so an unknown slug is a 404 even when the body is also invalid. The whole body is then checked before anything is written, so a rejected request stores nothing.
- Request body: both fields are optional, and an empty object returns the current state unchanged.
Each credential value is cleaned the way the dashboard’s field cleans a paste. Surrounding whitespace is trimmed, a leading
Bearer, Token or Basic is removed, and matching quotes or backticks around the value are unwrapped, repeatedly. A value that cleans down to nothing clears that credential.
Responses
200 OK: the state after the write, in the same shape GET returns, so a secret sent here cannot be read back.
400 Bad Request: { "error": "invalid body", "details": ... } for a body that fails validation, including a badly formed key or a value over the limit.
400 Bad Request: a well-formed key that this connector does not declare:
404 Not Found: { "error": "no such connector" }.
500 Internal Server Error: { "error": "<message>" }, redacted.
Example
Composio
Composio is the broker Clawboo uses for apps it cannot sign in to directly, such as Gmail or Slack. These routes are about your Composio account and which of its apps you have linked. The apps’ own tokens are exchanged at Composio and never reach Clawboo. The credential is a Composio project API key, the one that starts withak_ on your Composio project settings page. Clawboo keeps it in the encrypted vault under COMPOSIO_API_KEY, and no response returns it: the only fact any route reports about it is hasKey. One install is one Composio user (the fixed id clawboo-local).
GET /api/connectors/composio
Whether a key is stored, and which brokered apps are linked. With a key stored, the server asks Composio for the linked apps when its cached answer is more than 60 seconds old.
Responses
200 OK, no key stored:
200 OK, a key is stored:
connected holds Clawboo’s slugs for the linked apps. known is false until Composio has answered at least once, so an empty connected with known: false means “not known yet”, not “nothing linked”. keyRejected is true when Composio refused the stored key (401 or 403) on its last use.
502 Bad Gateway: reading the linked apps failed; the message is redacted and cut to 200 characters:
Example
PUT /api/connectors/composio/key
Sensitive. Stores your Composio API key. The paste is unwrapped first, so COMPOSIO_API_KEY=ak_..., an export line and a quoted value all work. The key is then tried against Composio: a key Composio refuses is not stored, and a key that could not be checked because Composio was unreachable is stored with verified: false. Storing a key clears the cached list of linked apps.
- Request body:
{ "apiKey": string }, at most 512 characters.
Responses
200 OK:
400 Bad Request: the body has no usable apiKey string: { "error": "invalid body" }.
400 Bad Request: the paste is not a project key (ak_ followed by at least ten more letters, digits, _ or -). The message is one of:
Paste your Composio key.(an empty value)That is a Composio login key. The one needed here starts with ak_ and is on your project settings page.(auak_key)That does not look like a Composio key. It starts with ak_.(anything else)
400 Bad Request: Composio refused the key, so nothing was stored:
500 Internal Server Error: { "error": "<message>" }, redacted and cut to 200 characters.
Example
DELETE /api/connectors/composio/key
Sensitive. Forgets the stored key and clears the cached list of linked apps. The app links stay at Composio, but Clawboo cannot reach them without a key. Answers 200 whether or not a key was stored.
Responses
200 OK: { "ok": true, "hasKey": false }.
500 Internal Server Error: { "error": "<message>" }, redacted and cut to 200 characters.
Example
POST /api/connectors/composio/apps/:slug/authorize
Sensitive. Starts linking one app through Composio and returns Composio’s consent URL for the browser to open. The stored key is checked before the slug, so an unknown app with no key stored answers 409, not 404. The route refreshes the linked-app list first, so an app that is already linked returns without starting a second consent.
- Path params:
slug, Clawboo’s slug for the app, for examplegmail,slackorgoogle-calendar(not Composio’s own toolkit name, which for the last one isgooglecalendar).
Responses
200 OK, already linked: { "ok": true, "alreadyConnected": true }.
200 OK, a consent was started; open url in a browser:
409 Conflict: no Composio key is stored:
404 Not Found: { "error": "no brokered app named <slug>" }.
502 Bad Gateway: Composio refused or failed the request, or answered without a consent URL (then the trailing detail is absent):
500 Internal Server Error: { "error": "Could not connect this app. <message>" }, redacted and cut to 200 characters.
Example
Custom connectors
A custom connector is a local MCP server you point Clawboo at yourself: a command and its arguments. Definitions are stored in theconnectors:custom setting, not in the built-in catalog, and come back in the same shape as a catalog entry with provenance: 'custom'. Clawboo has not run or reviewed them, so it treats them at their riskiest. Every declared input is a secret, outbound access is unrestricted (egressAllow: ['*']), and all three risk flags are set: reads private data, takes in untrusted content, can send data out.
Credential values never go through these routes: declare the variable names here, then store the values with PUT /api/connectors/:slug/config.
GET /api/connectors/custom
Lists your custom connectors in storage order.
Responses
200 OK:
'Required by this server.'. Stored data that cannot be read never causes an error. A missing or malformed list reads as empty, and an entry without a string slug, a string command or an args array is left out.
500 Internal Server Error: { "error": "<message>" }, redacted.
Example
POST /api/connectors/custom
Sensitive, because the command and args it stores become a real process when you connect it. Saving a slug that is already a custom connector replaces it and moves it to the end of the list.
- Request body:
Responses
200 OK:
400 Bad Request: { "error": "invalid body", "details": ... }.
409 Conflict: the slug belongs to a built-in connector, which a custom entry may not replace:
500 Internal Server Error: { "error": "<message>" }, redacted.
Example
DELETE /api/connectors/custom/:slug
Sensitive. Removes a custom connector. It stops the connector first, if it is running, so no process is left behind with nothing that knows how to stop it. The stop runs on every call, so a 404 means only that no stored definition had that slug.
Responses
200 OK: { "ok": true }.
404 Not Found: { "error": "no such custom connector" }.
500 Internal Server Error: stopping it or saving the list failed, { "error": "<message>" }, redacted.
Example
Grants
A grant says that a subject (one agent, one team, or every agent) may use a capability (a connector, a tool or a skill) in a given mode, under a given approval policy. Clawboo’s connector gate reads grants before a connector tool runs, and the graphs draw your grants as edges. A grant’sorigin is owner when it records what an agent’s own runtime already attaches, and operator when a person shared the capability. Only operator grants are drawn with a Detach control.
These routes read and write the local database only, and need no credential.
Some calls ask whatever the policy says, for example in a run that combines private data, untrusted content and a way to send data out. Under
always, a standing rule from an earlier Always answer does not skip the prompt, and the prompt cannot be remembered (neverRemember). Under the other policies a standing allow rule runs the tool without asking; revoking the grant deletes those rules. A tool whose required mode ranks above the grant’s mode is denied. A stored value that cannot be read falls back to its safest reading: mode reads as read, state as suspended, approvalPolicy as always, an unreadable toolAllow as [] and an unreadable toolDeny as ['*'].
GET /api/grants
Lists grants, newest grantedAt first, with no limit.
- Query params:
subjectId(optional): only grants whose subject id equals it, whatever thesubjectKind. Aglobalgrant has no subject id, so it never matches. An empty or repeatedsubjectIdis ignored and the full list returned.
Responses
200 OK: { ok: true, grants: GrantRow[] }.
500 Internal Server Error: { "error": "<message>" }, redacted.
Example
POST /api/grants
Creates a grant, or updates the existing grant for the same subject and capability. An update sets state back to active, clears revokedAt and revokedReason, and moves grantedAt to now. Grants made here have origin: 'operator', and updating an owner grant turns it into an operator one. The server has no caller identity, so a new grant records grantedBy: null.
- Request body:
Responses
200 OK: { ok: true, grant: GrantRow }.
400 Bad Request: { "error": "invalid body", "details": ... }. The two cross-field rules (“one of connectorId or capabilityId is required” and “subjectId is required unless subjectKind is global”) appear in details.formErrors.
500 Internal Server Error: { "error": "<message>" }, redacted.
Example
POST /api/grants/:id/revoke
Revokes a grant and deletes the standing rules recorded against it, so a remembered Always cannot outlive the grant. Only an active grant changes state; for a grant in any other state the route still answers 200 with the grant as it stands. The standing rules are deleted whatever the state.
- Request body: optional,
{ "reason": string | null }(up to 200 characters). A missing or invalid body is not an error:reasonis thennull. The dashboard’s Detach sends no body.
Responses
200 OK: the grant as it now stands, { ok: true, grant: GrantRow }.
404 Not Found: { "error": "grant not found" }.
500 Internal Server Error: { "error": "<message>" }, redacted.
Example
POST /api/grants/:id/resume
Undoes a revoke within 15 seconds of revokedAt, the window behind the dashboard’s Undo. It sets the grant back to active and clears revokedAt and revokedReason, but the standing rules the revoke deleted stay deleted. An active grant comes back unchanged. Takes no body.
Responses
200 OK: the resumed, or already active, grant: { ok: true, grant: GrantRow }.
409 Conflict: the grant exists but cannot be resumed: the 15 seconds have passed, it has no revokedAt, or it is proposed, suspended or expired:
404 Not Found: { "error": "grant not found" }.
500 Internal Server Error: { "error": "<message>" }, redacted.
Example
apps/web/server/api/connectors.ts, composio.ts and grants.ts; request schemas in packages/db/src/grants/connectorSchemas.ts and packages/db/src/grants/schemas.ts; the refusal reasons in packages/connector-catalog/src/connectable.ts; the grant decision in packages/governance/src/grants/decide.ts.
Related
- Connect an MCP server: the Connectors panel over these routes
- Capabilities API: connectors and grants as capability records
- Tools and MCP API: tool approvals and the audit trail
- What agents can do: what agents can reach, and what is not sandboxed
- Security: the access token and exposing Clawboo beyond this machine