# How to run a headless agent or CI job with a CoreSpeed API key (/blog/api-keys-for-headless-agents-and-ci)

![Illustration: A small friendly robot with a round camera-eye head works alone at a long workbench with no person in sight.](/blog/api-keys-for-headless-agents-and-ci/hero.webp)

A headless agent or CI job authenticates to `https://api.corespeed.io/mcp` with an API key instead of a browser sign-in. A member key (`sk-cs-`) acts as you. An agent key (`sk-csa-`) acts as an agent principal and reaches the organization's shared connections only. Send it as `Authorization: Bearer`, read it from an environment variable, and cap its monthly spend.

**A key instead of a browser**

1. **Choose the key kind** (sk-cs- acts as you; sk-csa- is an agent of its own) — Unattended work gets an agent key.
2. **Create it with a monthly cap** (Dashboard, API keys, or cs keys create) — The secret prints once. Keys are per environment.
3. **Read it from CORESPEED\_API\_KEY** (the entry stores the variable, never the key) — Remove any older browser entry first; duplicates collide.
4. **Verify with memory\_\_list\_memory** (free; a 200 without isError proves the key)
5. **Expect manage\_\_whoami to refuse** (jwt\_session\_required: session-gated tools) — Still listed for a key; presence is not permission.
6. **Branch on isError** (key\_spend\_limit\_exceeded, payment\_required) — Both arrive as 200; neither clears on retry.

A key replaces the browser; an environment variable replaces the config file.

## Which key should a headless job use? \[#which-key-should-a-headless-job-use]

| Credential          | Who is calling                                 | Reaches                                                              | Use it for                                                          |
| ------------------- | ---------------------------------------------- | -------------------------------------------------------------------- | ------------------------------------------------------------------- |
| `sk-cs-` member key | The member who created it                      | That member's private connections and the organization's shared ones | A job that should act as you, such as CI on your own repository     |
| `sk-csa-` agent key | An agent principal with an identity of its own | The organization's shared connections only                           | An unattended agent you run elsewhere, or a nightly job you operate |

An agent is the organization's third principal kind. Any member can create one in Dashboard → Settings or with `manage__agents_create` and becomes its owner: the member who answers for it and who, with org admins, may manage it. Ownership is accountability. The agent inherits neither the owner's connections nor the owner's role.

Give unattended work an agent key. The activity trail then names the agent. Suspending it stops every request with `403 agent_suspended` while its keys stay intact. Retiring it revokes the keys for good while the record survives for attribution. The full model is on the [Authentication](/docs/authentication) page.

## How do you create the key? \[#how-do-you-create-the-key]

1. Member key: open Dashboard → API keys and create one, or run `cs keys create production` from the CLI, which prints the `sk-cs-` secret once. A signed-in agent can also mint one with the `manage__keys_*` tools.
2. Agent key: create the agent first, then mint a key for it from the dashboard or with `manage__agents_key_create`. Only the owner or an org admin can.
3. Set a monthly spend cap in credits on the key. Keys belong to an organization, live until revoked, rotated or expired, and are per environment.

## How do you put the key into the client? \[#how-do-you-put-the-key-into-the-client]

4. Store the key in an environment variable named `CORESPEED_API_KEY` wherever the job runs. Let the server entry read the variable, so the key itself is never written into a config file:

```bash
# Claude Code: single quotes, so the entry stores the variable, not the key
claude mcp add corespeed https://api.corespeed.io/mcp --transport http --scope user \
  --header 'Authorization: Bearer ${CORESPEED_API_KEY}'
# Codex
codex mcp add corespeed --url https://api.corespeed.io/mcp --bearer-token-env-var CORESPEED_API_KEY
```

A client that reads an `mcpServers` map takes the same entry as the browser form plus a `headers` object whose `Authorization` value is `Bearer` followed by the key. The header forms are `Authorization: Bearer` for any credential and `x-api-key` for keys only.

Never print, log or commit the key. In Claude Code, `claude mcp list` hides headers; `claude mcp get` prints them, key included, so avoid it in transcripts. If the machine already had a browser entry for `corespeed`, remove it first with `claude mcp remove corespeed --scope user` or `codex mcp remove corespeed`. Duplicate registrations collide on tool names.

## How do you verify a key works? \[#how-do-you-verify-a-key-works]

5. Call `memory__list_memory`. It is free, and a `200` without `isError` proves the key authenticates.
6. Expect `manage__whoami` to refuse. It answers `200` with `isError: true` and code `jwt_session_required`, because `manage__whoami`, `manage__keys_*`, `manage__agents_*`, `manage__accounts_*` and `manage__switch_org` run only under a member session. Those tools still appear in `tools/list` for a key. Presence is not permission. `manage__remote_list` runs for anyone.

A `401` means the key is unknown, revoked or expired; replace it and do not retry. A `403` means the key's agent is suspended; resume the agent, since a new key would be refused the same way. A `503` means the key authority was unreachable and CoreSpeed failed closed rather than trust an unverified key; retry with backoff.

## What happens when the cap is reached? \[#what-happens-when-the-cap-is-reached]

Two boundaries stop metered calls before a tool runs, and both arrive on `/mcp` as `200` with `isError: true`:

* `key_spend_limit_exceeded`: this key's monthly cap is reached. Metered calls with it stop until the month rolls over or the cap is raised.
* `payment_required`: the organization wallet is past its billing threshold. Adding credit clears it.

Through either hold, discovery (`tools/list`), account reads and key management keep working. Caps are request-boundary guardrails rather than reservations, so an action already in flight can finish above the remaining amount. Neither code clears on retry. The retry rules for every code are in [Error handling](/docs/errors); the ledger and caps are on the [Billing](/docs/billing) page.

Branch on `result.isError` before you read any result. Treating a `200` as success is the most common integration bug.

Every call with a key is attributed through the key's owning identity in Dashboard → Activity: the member for an `sk-cs-` key, the agent for an `sk-csa-` key.

## FAQ \[#faq]

**Can the `cs` CLI take the API key?** No. The CLI runs under a browser sign-in and takes no key. Mint the key once with `cs keys create` and send it directly from the job; the CLI is not part of the job. See the [CLI](/docs/cli) page.

**Does an agent key reach my private Notion?** No. An agent principal reaches the organization's shared connections only. Share the connection, or use a member key.

**Should I rotate the key when a tool answers `needs_reauth`?** No. One connected account needs a member to reauthorize it in Dashboard → Connectors. The key is fine.

**How do I rotate a key?** `cs keys rotate` with the key id, or Dashboard → API keys. Update the environment variable where the job runs.