# Connectors

Give agents in your sandboxes the tools and APIs they use. A connector is a service your sandboxes reach, such as GitHub, Linear, an MCP server or the Anthropic API. isolance keeps its keys and adds the right one to a sandbox's requests after they leave the sandbox, so no key ever enters it: code inside can't print, log or leak a key it never sees.

A connector holds two kinds of account at once:

- **Your workspace's account**: one account, such as your team's GitHub sign-in or your company's Anthropic key. Sandboxes started without a user use it.
- **Each user's own account**: your own users, the people who use your app, each connect their account once. A sandbox started for a user uses only that user's account.

Your users are named by your own ID for each one, such as `user_123` or an email. They aren't members of your workspace and need no isolance account.

Each connector also has a **Reaches** setting that picks the sandboxes it reaches by their labels. A new connector reaches every sandbox.

This guide covers the console, the CLI and the API. The CLI examples assume you've set up `caas` (see [The CLI](/docs/cli)); the API examples send an API key as `$CAAS_API_KEY` (see [The API](/docs/api)).

## Add a connector

Only owners add, change and remove connectors. In the [console](https://console.isolance.com/), open **Connectors** in the sidebar. With no connectors yet, the page is the catalog itself; afterwards, choose **Add connector**. Each add opens the connector's page, ready for you to connect an account.

### Pick a tool

Under **Pick a tool**, choose a tile. The connector is added at once.

| Tool | What it reaches | Each user connects by | Your workspace connects by |
| --- | --- | --- | --- |
| Linear, Notion, Sentry, Atlassian, Cloudflare, Supabase, Airtable, Canva | Their MCP servers | Signing in | Signing in, or pasting a key |
| GitHub | Its MCP server, the GitHub API and Git over HTTPS | Signing in | Signing in, pasting a key, or installing your own GitHub App |
| Stripe | Its MCP server | Pasting a key | Pasting a key |
| Anthropic | The Claude API | Pasting a key | Pasting a key |
| OpenAI | The OpenAI API | Pasting a key | Pasting a key |
| Google Gemini | The Gemini API | Pasting a key | Pasting a key |
| Slack | The Slack Web API | Pasting their user token | Pasting a bot token |
| Codex (ChatGPT) | Codex's requests to `chatgpt.com`, on each user's ChatGPT plan or your workspace's | Entering a code at OpenAI | Entering a code at OpenAI |
| Browser | A web browser for the AI tools in your sandboxes, at `browser.caas.sh` | Nothing to connect: isolance sets up each user's own | Nothing to connect: it's built into isolance |
| Logins | Your users' sign-ins to websites, used by the browser and by command-line tools | Their logins, which your app adds, or they sign in to by link | Your workspace's own logins |
| Browserbase | Your Browserbase account, which runs the browsers of the logins you set to it | — | Pasting a key |

For the tools users sign in to, isolance registers itself with the tool as `caas.sh`: whoever signs in approves isolance's access on the tool's own page. GitHub signs in through isolance's GitHub App. Codex (ChatGPT) holds ChatGPT sign-ins, each made by signing in to ChatGPT on OpenAI's page and entering a code there: each user's own, which Codex in their sandboxes runs on, and your workspace's, for sandboxes without a user. Codex then runs on that ChatGPT plan instead of an OpenAI API key. A user gets their code the first time a sandbox of theirs needs it (see [Sign users in with a code](#sign-users-in-with-a-code)).

The Browser, Logins and Browserbase are for your users' work on the web. The Browser is built into isolance, with nothing to connect: it gives the AI tools in your sandboxes a browser, and each user's sandboxes their own. Logins holds each user's sign-ins to websites, and your workspace's, which the browser signs in with, so the AI never sees a password, code or passkey; your app adds them, or sends each user a link to sign in. Browserbase, connected with your workspace's key, runs the browsers of the logins you set to it on your own Browserbase account, and reaches no sandbox. See [Browser](/docs/browser) and [Logins](/docs/logins).

A tool isolance can't serve yet isn't shown in the console. The CLI and the API list every tool, with whether it's available:

```sh
caas connectors catalog
caas connectors add github
```

```sh
curl https://api.caas.sh/v1/connectors \
  -H "Authorization: Bearer $CAAS_API_KEY" \
  -H "Content-Type: application/json" \
  -d '{ "catalog": "anthropic" }'
```

### Add any MCP server

Under **Or add any MCP server**, paste the server's address from the tool's docs into **MCP server URL** and choose **Add**. The address must be `https://` on a public host, without a port, a user name and password, or a `#` part. isolance asks the server how its users sign in, then:

- **It registers clients itself:** isolance registers there and adds the connector. Accounts sign in.
- **It needs no sign-in:** the connector is added, and there's nothing to connect. Every sandbox it reaches can use it.
- **It takes only apps registered with it:** you [use your own app](#use-your-own-app).
- **isolance can't sign its users in:** the page says why. If the tool's docs say its MCP server also accepts an API key or token, choose **Use access keys**, and accounts connect with a key.

The connector's ID comes from the address (`acme` for `mcp.acme.com`, with `-2` and so on added while taken) and its name from the ID. Choose **Rename** on its page to change the name.

```sh
caas connectors add --mcp https://mcp.acme.com/mcp
caas connectors add --mcp https://mcp.acme.com/mcp --keys
```

With the API, `POST /v1/connectors/discover` with `{ "mcpUrl": "…" }` says what an address would get and keeps nothing; `POST /v1/connectors` with `{ "mcpUrl": "…" }` adds it, with `"auth": "key"` for keys.

### Add an API

Under **Or add an API**, enter the API's **Host**, such as `api.example.com`. List one host per line; `*.example.com` covers example.com and every name under it. Then pick **How the key is sent**:

- **As a bearer token (most APIs):** `Authorization: Bearer <key>`.
- **As a password, with a username:** HTTP Basic authentication. Enter the **Username** the API expects.
- **In a header of its own:** enter the **Header name**, such as `x-api-key`. The key goes in it as it is.

Choose **Add API**. Its page opens with the **Key** field of the **Workspace account** card ready. Its name comes from the host; rename it there, and set a [variable](#what-a-sandbox-sees) if your code reads the key from one.

```sh
caas connectors add --api --host api.example.com --env EXAMPLE_API_KEY
caas connectors add --api --host api.example.com --header x-api-key --scheme raw
caas connectors add --api --host git.example.com --scheme basic --username deploy
```

With the API:

```json
{ "api": { "hosts": [{ "host": "api.example.com", "header": "x-api-key", "scheme": "raw" }] }, "id": "example", "envVar": "EXAMPLE_API_KEY" }
```

Each host has a `header` (`authorization` unless you name another) and a `scheme`: `bearer` (the default), `basic` (it needs a `username`) or `raw`. A connector has up to 16 hosts, each once, and each host belongs to one connector: a host that equals, covers or is covered by another connector's host is refused. A header that routes or frames a request, such as `host` or `content-length`, is refused.

## Connect your workspace's account

The connector's page has a **Workspace account** card: "Sandboxes started without a user use this account." Owners see its controls; everyone else sees its status. The card offers the first way the connector takes:

- **Sign in to `Name`** (**Sign in with GitHub** for GitHub): you sign in on the tool's own page in this tab, and come back to the connector's page, which finishes connecting.
- **Install `slug`**: for GitHub with [your own GitHub App](#use-your-own-app), you install the app on the account it should act for.
- **Sign in with ChatGPT**: for Codex, the page shows a code in large type with **Open OpenAI's sign-in page**. Sign in there with the ChatGPT account your workspace should use, and enter the code. The card says it's connected as soon as isolance's next check sees it, at the interval OpenAI sets; reload the page while the code still works, and the same code comes back.
- **Key**: paste the key and choose **Connect**. It's ready at once.

Where a tool signs in, **Paste a key instead** shows the **Key** field, and GitHub with your own app also offers **Install your GitHub App**.

After the first connect, the card says "Sandboxes without a user get `Name` from their next start." A running sandbox keeps what it started with until it restarts.

With the CLI, keys come from standard input, and the CLI waits until the account is connected (unless you pass `--detach`). A sign-in prints a page to open, and it finishes in the console:

```sh
caas connectors account anthropic --api-key < anthropic-key.txt
caas connectors account github --sign-in
caas connectors account github --install
caas connectors account codex --device
```

With the API, `POST /v1/connectors/<id>/account` with `{ "method": "key", "key": "…" }`, or `{ "method": "sign_in" }`, `{ "method": "install" }` or `{ "method": "device" }`. A sign-in or install answers `action` with the `url` to open. A code answers `signIn`, with the `code`, OpenAI's page as `url`, `expiresAt` and `pollAfterMs`, how long until isolance next checks: read the connector again after it, and its account's `signIn.status` is `connected` once the code is entered. Only this answer carries the code; asking again while it still works answers the same code. The first connect answers `201`; later ones `200`.

| Console | API `status` | Meaning |
| --- | --- | --- |
| Not connected | no `account` | Sandboxes without a user get nothing from this connector. |
| Signing in | `pending` | A sign-in, install or code isn't finished yet. |
| Connected | `ready`, `renewing` | Sandboxes without a user get the account. |
| Needs reconnecting | `reauthentication_required`, `expired` | A renewal failed for good, or a sign-in ended before it finished. Choose **Reconnect**. |
| Unavailable | `unavailable` | The account can't be used, or its status can't be read right now. |

## Connect each user's account

The connector's page has an **Each user's account** card for every tool that takes a sign-in, a key or a code: "Sandboxes started for a user use only that user's account."

### Connect my account

Choose **Connect my account** (also in the connector's row menu on **Connectors**) to connect your own account, with your console email as the user ID. Sign in on the tool's page, or paste your key, and you come back to the connector's page, which shows **Your account** with its status. For Codex (ChatGPT), your code shows in place: enter it on OpenAI's sign-in page. Where sandboxes can take a user, **Start a sandbox for me** opens **New sandbox** with you as its user.

### Send connect links

A connect link lets one user connect one connector. In the console, choose **Make a connect link** on the connector's page (or in its row's menu, or on the user's page), enter the user's ID, and copy the link. From your app, make one with `POST /v1/users/<user>/connections`, the user's ID URL-encoded in the path:

```sh
curl https://api.caas.sh/v1/users/user_123/connections \
  -H "Authorization: Bearer $CAAS_API_KEY" \
  -H "Content-Type: application/json" \
  -d '{ "connector": "linear", "returnUrl": "https://app.acme.com/settings" }'
```

The answer, `201`, is the connection (`"status": "invited"` for a user who hasn't connected) and its `connectUrl`, `https://console.caas.sh/connect/…`. Send the user there:

1. The page names the tool and your user chooses **Continue**, or pastes their key for a tool that takes keys.
2. They sign in on the tool's own page and approve. isolance never sees their password.
3. They come back to your `returnUrl` with `connector` and `status` added to its query: `?connector=linear&status=connected`, or `status=failed` if they cancelled or the sign-in didn't finish. Without a `returnUrl`, the page tells them they can close the tab.

For Codex (ChatGPT), the page shows the user's code instead, with **Open OpenAI's sign-in page**: they sign in to ChatGPT there and enter the code, and the page says when they're connected and sends them to your `returnUrl` with `status=connected`. If the code expires first, **Get a new code** gives them another. The page shows the code they already have, from a sandbox or the console, while it still works.

What to know:

- `returnUrl` is an `https://` URL, or `http://` on `localhost` or `127.0.0.1` while you test, of up to 1,024 characters.
- A link works until it's used, a newer link for the same user and connector replaces it, or the connection is removed. Once a user chooses **Continue**, they have 15 minutes to finish signing in; a sign-in that fails leaves the link working, so they can try again.
- A user must finish in the browser where they chose **Continue**. A Codex (ChatGPT) link works in any browser, such as on their phone and then their laptop, and it's used up as soon as they're signed in, however they got their code. A new code never replaces a link you sent.
- Connecting again replaces a user's token. Until the new sign-in finishes, the connection they have keeps working.

### Sign users in with a code

Codex (ChatGPT)'s users sign in with a code: they open OpenAI's sign-in page, sign in to ChatGPT with their own account, and enter the code shown to them. A user gets their code:

- from a sandbox's start, when a sandbox started for them needs it: its create, start or restart answers the code in `signIn`, and `caas sandboxes create --user USER` prints it;
- from their connect link's page;
- from **Sign in** on their page in the console, or **Connect my account** for your own;
- with `caas users sign-in USER`, or from your app:

```sh
curl -X POST https://api.caas.sh/v1/users/user_123/connections/codex/sign-in \
  -H "Authorization: Bearer $CAAS_API_KEY"
```

The answer, `200`, is `{ "connection", "signIn" }`: `signIn` has the `code` to enter, OpenAI's page as `url`, `expiresAt` and `pollAfterMs`. Read the connection again after `pollAfterMs`, then after each answer's, until `signIn.status` is `connected`:

| `signIn.status` | Means |
| --- | --- |
| `starting` | The code is on its way: read again after `pollAfterMs`. |
| `pending` | Show `code` and `url`. The code works until `expiresAt`. |
| `connected` | The user signed in, and their sandboxes' requests carry their sign-in. |
| `expired` | The code ran out before anyone entered it. The next sandbox, sign-in or link gives a new one. |
| `failed` | The sign-in didn't finish, such as one denied at OpenAI; `reason` says why. A code that couldn't start reads `failed` too, and the sandbox is created all the same. |

What to know:

- A user has one code at a time. While it still works, every way of asking shows the same code; once it expired, they get a new one.
- isolance checks with OpenAI at the interval OpenAI sets, whether or not anyone is reading, and `pollAfterMs` says how long until its next check. Reading sooner gets the same answer.
- On a user who's signed in, the same route signs them in again: their current sign-in keeps working until they enter the new code, which then replaces it. If a new code can't start yet, `signIn` reads `expired` with `expiresAt` saying when it can. The route never replaces a connect link you sent.
- Only those who could start a code see it: owners, operators and API keys with `sandbox:write`, in the answers that start codes and when they read one user or one connection, and whoever holds the user's link, on its page. Everyone else, and every list, gets `signIn` with `code` and `url` set to `null`. Whoever enters a code signs the user in with their own ChatGPT account, so show it only to the user it's for.

### Give your users a key you hold

Your workspace's own key never reaches a sandbox started for a user. To give each user a key your backend holds, such as your company's Anthropic key on a tool that takes keys, send it user by user:

```json
{ "connector": "anthropic", "key": "sk-ant-…" }
```

The answer is the connection, `ready` at once, with `connectUrl` `null`. Send it again to replace a user's key. A key is up to 16 KiB.

### Status and usage

The **Users** tab of **Connectors** lists your users, each with a dot per connection, this month's requests, sandbox time and browser time, and how many sites they're signed out of. A user's page shows their connection to each connector and their [logins](/docs/logins), with **Make a connect link** or **Disconnect**, and for Codex (ChatGPT) **Sign in** or **Sign in again**; a connector's page lists its users.

| Console | API `status` | Meaning |
| --- | --- | --- |
| Link not used yet | `invited` | The user has a connect link and hasn't connected. |
| Signing in | `pending` | They chose **Continue**, or have a code to enter, and haven't finished. |
| Connected | `ready`, `renewing` | Their sandboxes' requests carry their account. |
| Needs to reconnect | `reauthentication_required` | Their token can't be renewed. Send them a new link. |
| Sign-in not finished | `expired` | The sign-in ended before they finished. Send them a new link. |
| Unavailable | `unavailable` | The connection can't be used, or its status can't be read right now. |

For Codex (ChatGPT), a user whose first code ran out shows **Not signed in**, and a user with a code under way shows **Waiting for a sign-in**, even while signing in again keeps them connected. With the API, a connection's `signIn` is its newest code, and each user in `GET /v1/users` has `signIns`, where each code under way stands and when to read again, never the code.

`GET /v1/users/<user>/usage?from=2026-10-01&to=2026-10-31` answers a user's usage per UTC day, from the first day of the current UTC month to today unless you say:

```json
{
  "user": "user_123",
  "from": "2026-10-01",
  "to": "2026-10-31",
  "days": [{ "date": "2026-10-02", "runningSeconds": 5400, "browserSeconds": 620, "requests": { "linear": 42 } }],
  "total": { "runningSeconds": 5400, "browserSeconds": 620, "requests": 42 }
}
```

- `runningSeconds` is how long the user's sandboxes ran, split at UTC midnight.
- `browserSeconds` is how long their browsers ran on isolance's own browsers, from each one's open to its end, split at UTC midnight (see [Browser time](/docs/browser#browser-time)).
- `requests` counts the HTTP requests their sandboxes sent with their own accounts, by connector. They're counted from the reports of the network boundary that adds the key, which can miss some, so take them as a lower bound. They're requests, not tool calls.

## Whose account a sandbox uses

- **A sandbox with a user** (its `user`, set when you create it) uses that user's account for every connector that reaches it and takes a sign-in, a key or a code, whether or not the user has connected: for Codex (ChatGPT), the user's own ChatGPT sign-in. Until they connect, its requests to that tool are refused on their way out. Once they connect, the next request carries their account, with no restart. After a disconnect, requests are refused again from the next one.
- **A sandbox without a user** uses your workspace's account, and only for connectors that have one. A connector without a workspace account gives that sandbox nothing at all.
- **A tool with no sign-in** reaches every sandbox it reaches, user or not.
- **The Browser** reaches sandboxes with and without a user: a user's sandboxes get that user's own access to it and sign in with their logins, and a sandbox without a user gets your workspace's.
- **Logins** gives a sandbox its user's logins, or your workspace's when it has no user. **Browserbase** reaches no sandbox.

There's no fallback either way: a user's sandbox never gets your workspace's account, and a sandbox without a user never gets a user's.

To start a sandbox for a user, enter the user's ID under **User** in **New sandbox** (it's under **3. Network**); where the field isn't offered, keys can't be added where the sandbox would run. With the CLI, pass `--user USER`. With the API, set `user` when you create it; it can't change later:

```sh
curl https://api.caas.sh/v1/sandboxes \
  -H "Authorization: Bearer $CAAS_API_KEY" \
  -H "Content-Type: application/json" \
  -d '{ "image": "default", "user": "user_123" }'
```

## Reaches

A connector's **Reaches** setting is the labels a sandbox must have, with these values, to get it. Empty, the default, reaches every sandbox of your workspace: MCP servers' sandboxes too. So a connector with a workspace account and an empty Reaches gives that account to every sandbox started without a user.

On the connector's page, choose **Change** beside **Reaches**, enter one `key=value` per line, and choose **Save**. A sandbox gets the connector only when it has every one of these labels.

Besides a sandbox's own labels, isolance adds:

- `caas.sh/user=<user>` to a sandbox with a user, so `caas.sh/user=user_123` picks one user's sandboxes;
- `caas.sh/mcp-server=<server ID>` to a sandbox hosting an MCP server.

```sh
caas connectors update github --reach team=web
caas connectors update github --reach-all
caas sandboxes create --label team=web
```

With the API, `"selector": { "team": "web" }` on `POST` or `PATCH /v1/connectors/<id>`; `{}` reaches every sandbox. A sandbox's `labels` are set on its create or `PATCH`; in **New sandbox**, the **Labels** field shows while a connector's Reaches names a label of your own.

Reaches picks sandboxes; it isn't a security boundary. Anyone who may create or change a sandbox can give it the labels a connector asks for.

## What a sandbox sees

The key itself never enters the sandbox. Instead:

- **Its hosts are allowed,** beside the sandbox's own outbound hosts, even when those are an empty list (see [Outbound access](/docs/sandboxes#outbound-access)).
- **HTTPS only.** isolance adds the key to HTTPS requests to the connector's hosts, on every path, in its header. A plain `http://` request is refused.
- **The real value wins.** The key replaces whatever the sandbox sent in that header, and isolance redacts it as `[REDACTED]` from the response's headers and body.
- **Variables hold a placeholder.** Every process starts with the connector's variables set to `caas-egress-placeholder-key`, so a tool that won't start without a key starts, and isolance puts the real key in its place on the way out. A placeholder replaces a sandbox variable of the same name.

| Connector | Variables |
| --- | --- |
| GitHub | `GH_TOKEN`, `GITHUB_TOKEN` |
| Anthropic | `ANTHROPIC_API_KEY` |
| OpenAI | `OPENAI_API_KEY` |
| Google Gemini | `GEMINI_API_KEY`, `GOOGLE_API_KEY` |
| Slack | `SLACK_BOT_TOKEN` |

Any connector that takes a sign-in or key, but Codex (ChatGPT), whose sign-in is no key, can add one more: on its page, choose **Set variable** beside **Variable** (`caas connectors update ID --env NAME`, or `"envVar"` in the API). Leave it empty to clear it. Variables a sandbox needs as they are, such as `PATH` and `HOME`, and names that start with `CAAS_` are never used.

The sandbox's page lists its connectors, and the hosts each adds keys on, under **Connectors** on its **Network** card. With the API, a sandbox's `connectors` says the same, and whose account each uses.

## When changes apply

| Change | Effect on a running sandbox |
| --- | --- |
| A connector added or removed, its Reaches or Variable changed | At the sandbox's next start |
| The sandbox's labels changed | At its next start |
| Your workspace's account connected for the first time, or disconnected | Sandboxes without a user gain or lose the connector at their next start; a disconnect refuses their requests from the next one |
| A key replaced, an account reconnected, a token refreshed | At the next request |
| A user connects, signs in again or disconnects | At the next request |
| A connector renamed | At once, in what pages show |

Until its next start, a running sandbox keeps the connectors, hosts and variables it started with, and its `pendingRestart` lists `connectors`. Restart it to apply the change now: `caas sandboxes restart <sandbox>`.

A sandbox runs with connectors only where isolance can add keys to its requests: with `provider` `auto`, the default, isolance places it there when it gets any. A sandbox on a provider you name that can't add keys starts with no connectors, and its log says so.

## Use your own app

Some MCP servers sign in only through an app registered with them, such as Slack's, and an API's accounts sign in only through your own OAuth app. On the connector's page, **Sign-in** says how its accounts sign in: through isolance, through your app, with GitHub, or with a key. **Use your own app** shows its card:

1. Register an OAuth app with the tool, and set its redirect or callback URL to the **Callback URL** the card shows:

   ```text
   https://api.caas.sh/v1/credential-interactions/callback
   ```

2. For an API, pick the **Provider** (Google, GitLab or Linear), or **Other** with its **Authorize URL**, **Token URL**, how the **Client secret goes** and what **Permissions** are **separated by**.
3. Paste its **Client ID** and **Client secret**, and the **Permissions** to ask for. The secret is never shown again.

On an API, your own app changes how accounts connect (sign-in instead of a key), so it's offered only while the connector has no accounts. **Replace app details** rotates the secret or changes the app; accounts that signed in through the old app connect again. **Stop using your app** goes back to keys.

For GitHub, **Use your own GitHub App** lets your workspace's account install your GitHub App instead of signing in: create the app on GitHub with the permissions it needs, set its **Callback URL** and **Setup URL** to the callback URL above, generate a private key, and enter its **Client ID**, **App slug** (as in `github.com/apps/<slug>`) and **Private key**. Your users keep signing in through isolance's GitHub App.

```sh
caas connectors app example --preset google --client-id 1234.apps.googleusercontent.com --scope https://www.googleapis.com/auth/drive.readonly < client-secret.txt
caas connectors github-app github --client-id Iv23liAbCdEf012345 --slug acme-bot < private-key.pem
caas connectors app example --remove
```

With the API, `app` (`{ "clientId", "clientSecret", "scopes" }`, and for an API `preset` or `authorizeUrl` and `tokenUrl`) and `githubApp` (`{ "clientId", "slug", "privateKey" }`) on `POST` or `PATCH /v1/connectors/<id>`; `null` removes one.

## Reconnect, replace or remove

- **Replace a key:** **Replace key** on the **Workspace account** card, or `caas connectors account ID --api-key` again. The next request gets the new key.
- **Reconnect a sign-in:** **Reconnect** (**Sign in again** for Codex), or `caas connectors account ID --sign-in` (`--device` for Codex). The account keeps working until the new sign-in finishes. Connecting by another way, such as a key instead of a sign-in, replaces the account at once.
- **Sign a user in again** (Codex (ChatGPT)): **Sign in again** on the user's page or in a connector's users list, `caas users sign-in USER`, or `POST /v1/users/<user>/connections/<connector>/sign-in`. Their sign-in keeps working until they enter the new code.
- **Disconnect your workspace's account:** **Disconnect**, or `caas connectors account ID --disconnect`. Sandboxes started without a user get their requests to the tool refused from now on.
- **Disconnect a user:** **Disconnect** in a connector's users list or on the user's page, `caas users disconnect USER --connector ID`, or `DELETE /v1/users/<user>/connections/<connector>`.
- **Remove a user:** **Remove user**, or `DELETE /v1/users/<user>`. Every connection goes, with their logins, their browsers and their usage history.
- **Remove a connector:** **Remove connector** on its page, or `caas connectors remove ID`. Its workspace account and every user's connection go with it. If a step fails, it stays listed: remove it again to finish.

## Who can change what

| What | Who |
| --- | --- |
| See connectors, accounts, users, statuses and usage | Everyone in the workspace |
| Add, change and remove connectors; connect and disconnect the workspace's account, and see its code | Owners, in the console or with an owner's API key |
| Connect their own account, make connect links, send keys, get users' codes and see them, disconnect and remove users | Owners and operators |
| Create a sandbox without a user, or change a sandbox's labels | Owners and operators, checked afresh |

While a workspace is suspended, changes are refused. The audit log records `connector.create`, `connector.update`, `connector.delete`, `workspace_account.connect`, `workspace_account.disconnect`, `connection.link`, `connection.sign_in` (a code asked for a user), `connection.delete` and `end_user.delete`, and never a key, a client secret, a link or a code. Logins have their own records (see [Logins](/docs/logins#who-can-do-what)):

```sh
caas audit --action connector
caas audit --action workspace_account
```

## Limits

| Limit | Value |
| --- | --- |
| Connectors per workspace | 32 |
| Hosts per connector | 16 |
| Reaches labels per connector | 16, values up to 256 characters |
| Egress rules per sandbox | 16: one per way a connector's hosts take the key (GitHub has 2) |
| Connector hosts per sandbox | 32 (`*.example.com` counts twice) |
| A user's ID | 1 to 256 characters, without control characters |
| A key, client secret or private key | 16 KiB |
| Permissions an own app asks for | 64 |
| Time to finish a sign-in or install | 15 minutes |
| How long a code works | 15 minutes, or less when OpenAI gives it less |
| Codes under way | One per user and connector, and one for your workspace's account |
| A `returnUrl` | 1,024 characters |

A connector that doesn't fit a sandbox beside the connectors before it (they apply in ID order) is left out of that sandbox whole, and the sandbox's log names it.

## Use the API

Every path below is under `https://api.caas.sh/v1`. `GET /users` and `GET /connectors/<id>/connections` take `?limit=` (1 to 100, 50 by default) and answer `next`, which `?after=` takes to read on. Errors are as for every route (see [The API](/docs/api#errors)).

| Request | Who | Does |
| --- | --- | --- |
| `GET /connector-catalog` | `sandbox:read` | Lists the catalog's tools, each with `available`, `reason` and `added` |
| `POST /connectors/discover` | Owner | Says how an MCP server's accounts would connect, keeping nothing |
| `POST /connectors` | Owner | Adds a connector: `{ "catalog" }`, `{ "mcpUrl" }` or `{ "api": { "hosts" } }`, with `id`, `name`, `selector`, `envVar`, `app` and `githubApp` optional |
| `GET /connectors`, `GET /connectors/<id>` | `sandbox:read` | Lists connectors, or reads one, with its workspace account and how many users connected |
| `PATCH /connectors/<id>` | Owner | Changes its `name`, `selector`, `envVar`, `app` or `githubApp` |
| `DELETE /connectors/<id>` | Owner | Removes it with its accounts |
| `POST /connectors/<id>/account` | Owner | Connects the workspace's account: `{ "method", "key"? }`; a code's answer has it in `signIn` |
| `POST /connectors/<id>/account/callback` | Owner | Finishes a sign-in that came back to the console: `{ "session", "confirm" }` |
| `DELETE /connectors/<id>/account` | Owner | Disconnects the workspace's account |
| `GET /connectors/<id>/connections` | `sandbox:read` | Its users' connections, by user |
| `GET /users` | `sandbox:read` | Your users, each with their connections' statuses, their codes under way (`signIns`), how many `logins` they have and are `signedOut` of, and this month's `requests`, `runningSeconds` and `browserSeconds` |
| `GET /users/<user>` | `sandbox:read` | A user and their connections, each with its `signIn` |
| `DELETE /users/<user>` | `sandbox:write` | Removes a user, their connections, logins and usage, and closes their browsers |
| `GET /users/<user>/usage` | `sandbox:read` | Their usage per UTC day, `?from=` and `?to=` as `YYYY-MM-DD` |
| `POST /users/<user>/connections` | `sandbox:write` | A connect link: `{ "connector", "returnUrl"? }`, or `{ "connector", "key" }` for a key. For Codex (ChatGPT), only a link, whose page shows the code |
| `POST /users/<user>/connections/<connector>/sign-in` | `sandbox:write` | A code for the user: the one under way while it still works, else a new one; for a user who's signed in, a sign-in again |
| `GET /users/<user>/connections/<connector>` | `sandbox:read` | One connection, its status read afresh, with its `signIn` |
| `DELETE /users/<user>/connections/<connector>` | `sandbox:write` | Disconnects the user |
| `/users/<user>/logins…`, `/connectors/<id>/account/logins…` | `sandbox:read`, `sandbox:write` | A user's logins, and your workspace's: see [Logins](/docs/logins#use-the-api) |
