API Keys
How to create an API key in Settings, the role permission it needs, the x-api-key header, when keys expire, and how to delete or rotate a key.
API keys authenticate Model Context Protocol (MCP) and REST clients against your Customermates CRM. Create one key per client, store it in an environment variable, and delete it when the device or the teammate that held it goes away.
How do I create an API key?
Open API keys in Settings and click Add. The Add an API key dialog offers two routes:
- Standard API key: enter a Name that says where the key is used, for example
Claude Desktop, personal laptop(up to 255 characters), optionally pick a date under Expires in, and click Save. The key is shown once under API key created successfully; copy it before you close the dialog. - Quick connections: pick Claude, ChatGPT & Codex, Cursor or Gemini, then the setup that matches where you use it. For a local setup (Claude Code or Desktop, then Claude Code or Claude Desktop; Codex CLI or IDE; Cursor or Gemini), click Create API key: the dialog creates the key and shows that client's setup snippet with the key already filled in. Claude account and ChatGPT App connect through OAuth instead and create no key. Click Done when you are finished.
A key created by Quick connections is named after its client (Claude Code, Claude Desktop, Codex, Cursor or Gemini) and expires 365 days after creation; you cannot choose another name or date there. The dialog shows the expiry below the setup snippet. The AI step of the onboarding wizard creates its keys the same way. A key's name and expiry cannot be changed after it is created.
Keys belong to the user who created them and inherit that user's permissions. Each key also belongs to the workspace it was created in and only ever acts there, including keys created before one login could belong to several workspaces; see Workspaces.
Who can create and see API keys?
The API keys page follows the API & Webhooks row of your role, which a member with Edit on Users & Roles sets under Roles:
- Read access All shows API keys in Settings and lists your own keys. With None, the entry is hidden and the page cannot be opened.
- Create adds the Add button, and Delete lets you delete keys. Without them, you can see keys you already have but cannot create or delete any.
A new role starts with no checkbox ticked and Read access None, so out of the box only members with the built-in Admin role can create API keys. The same row also opens Webhooks and its deliveries, so granting it for API keys also lets the member see every webhook of the workspace and, with Create, Edit or Delete, change them.
Everyone sees only their own keys, and only those of the current workspace; switch workspaces to see or delete a key of another one. Nobody, including the Admin role, can list or delete another member's keys. No assistant or MCP tool creates, lists or deletes API keys; they are managed only on the API keys page, and the hosted assistant can at most open it for you.
The custom connector for Claude and ChatGPT signs in over OAuth and needs no API key. Like every MCP connection, it needs Read access All on API & Webhooks (MCP endpoint). See Connect with a custom connector.
Which permission does each API key action need?
Every way of managing keys checks the API & Webhooks row of your role: creating a key needs Create, changing one Edit, deleting one Delete, and reading or listing your keys Read access All. A member whose Status is Inactive cannot manage keys. Keys are managed only on the API keys page; the underlying /api/auth/api-key/ endpoints answer 404 to direct requests, so every key belongs to the workspace it was created in. These checks cover managing keys only; what a call made with a key may do follows its owner's role, see What permissions does a key have.
How do I use a key, and which header does it go in?
Send it in the x-api-key header on every request, here to the MCP endpoint <BASE_URL>/api/v1/mcp, where <BASE_URL> is the address you open Customermates at:
curl -H "x-api-key: $CUSTOMERMATES_API_KEY" \
-H "Content-Type: application/json" \
-H "Accept: application/json, text/event-stream" \
<BASE_URL>/api/v1/mcp \
-d '{"jsonrpc":"2.0","id":1,"method":"tools/list"}'The Accept: application/json, text/event-stream header is required by the MCP streamable HTTP transport; without it the endpoint returns 406 Not Acceptable. For MCP clients the key goes into the client's configuration, see CLI and editor clients and ChatGPT with an API key.
The REST API takes the same header. This call lists the record types you can access:
curl -H "x-api-key: $CUSTOMERMATES_API_KEY" \
-H "Content-Type: application/json" \
<BASE_URL>/api/v1/model/discover \
-d '{}'Every REST operation, with its request and response, is in the API reference.
What does a failed REST call return?
Every REST endpoint answers a refused call with the same JSON body: an error object holding the kind and the issues that MCP returns in _meta.failure (see when a call is refused).
{
"error": {
"kind": "not_found",
"issues": [{ "code": "custom", "path": [], "message": "Webhook ID not found or not accessible.", "customCode": "webhookNotFound" }]
}
}The HTTP status follows error.kind: validation 400, authentication 401, authorization 403, not_found 404, conflict 409, unavailable 422 and rate_limit 429. Each issue names the field path it belongs to and, where one applies, a stable customCode. error.retryable is true when a messaging provider refused a call that can succeed later unchanged; a 429 without it can be retried as well, see the next section. Branch on kind and customCode rather than on message, which is text for people. An unexpected server error answers 500 without this body.
What should a client do when a call returns 429?
rate_limit (HTTP 429) means the call could not run now and can succeed later unchanged. A messaging channel at its provider's limit answers with customCode unipileRateLimit and says when to try again (messaging rate limits). When Customermates itself is overloaded, for example when a database transaction or the connection pool times out, or a write waits more than a few seconds behind another change to the workspace such as a large import, the call answers 429 without a customCode and with the message The service is busy. Retry the request shortly.. MCP tools return the same rate_limit kind in _meta.failure, and the app shows that the workspace is busy so you can try again.
A REST 429 carries a Retry-After header with the seconds to wait: honour it first and retry no earlier. Without one, retry after a pause that grows with each attempt, for example 1, 2, 4 and 8 seconds plus a little random jitter, and give up after a few attempts. Do not retry in a tight loop or send more calls in parallel, which keeps the service busy. Send a write again with the same idempotencyKey, so a change that went through is not applied twice.
How long does a key last, and do keys expire?
A Standard API key expires only if you pick a date under Expires in; the default, Never expires, keeps it valid until you delete it. A date must lie between 1 and 365 days ahead. A key from Quick connections (a quick connection key), including those from the onboarding wizard, is valid for 365 days: it always expires 365 days after creation. Expiry cannot be extended: before it passes, create a new key and update the client. Each key card shows Created at, Expires at (Never when unset) and Last used.
How do I revoke, delete or rotate a key?
On API keys, click the key's card and choose Delete; after you confirm, the key stops working immediately. Deleting needs Delete on API & Webhooks. There is no separate rotate or revoke action: to rotate, create a new key, update every client that used the old one, then delete the old key.
Rotate when a device that held the key is lost or compromised, when a teammate with access leaves, or when you suspect the key leaked. One key per client lets you delete one client without breaking the others; the audit log still names the user, not the key. Keep keys in an environment variable or a password manager, never in a config file you commit.
Only the owner can delete a key. To stop the keys of a member who leaves, open Members, open the member, set Status to Inactive and click Save. This needs Edit and read access All on Users & Roles; you cannot deactivate yourself or the last active Admin. Every call with that member's keys that reads or changes workspace data is then refused. Taking Read access All on API & Webhooks away from their role also stops their keys, for MCP and REST alike; see What permissions does a key have. Remove from workspace on Members also revokes the member's keys and MCP connections for this workspace at once (How do I approve, edit, deactivate or remove a member?), and a member who leaves a workspace themselves loses their keys for it at once (leaving a workspace).
What is the key format, and how long can a key name be?
A key's Name is 1 to 255 characters long. Keys are 64 characters of ASCII letters (a-z A-Z), generated by the better-auth API key plugin and stored hashed. The full key is shown only at creation and cannot be displayed again.
What permissions does a key have, and can I restrict its scope to read-only?
Everything its owner can do in the key's workspace, and nothing more. There is no per-key permission scoping, so a key cannot be limited to read-only access. Every call is checked against the owner's current role, so a change to that role, for example moving the member from the Admin role to a custom role with fewer rights, applies to their keys on the next call. If the owner's Status is Inactive, every call with their keys that reads or changes workspace data is refused.
A key, like an MCP OAuth token, also works only while its owner's role has Read access All on API & Webhooks, checked on every call, including for tokens refreshed later. Without it, MCP answers 403 with error: insufficient_permissions, and REST answers 403 with kind authorization and customCode insufficientPermissions. Signed-in use of the web app is not affected.
On Customermates Cloud, a workspace that is still waiting for its first plan, such as an additional workspace created without a trial, refuses its keys and OAuth tokens: MCP answers 403 with error: plan_required, and REST answers 403 with kind authorization and customCode workspacePlanRequired. Creating a key needs a usable plan as well. Once an admin chooses a plan, the existing keys work again. A workspace whose trial ended or whose paid subscription lapsed keeps its existing keys and connections working until its members are set Inactive, see What happens when the trial ends?.
Next
- Quickstart: first end-to-end run.
- MCP overview: what a key unlocks.