Managing API keys
An API key ties a request to an organization. It decides whose plan the tokens count against, which credit wallet pays for overflow, and which origins may use it. Keys are created and revoked in the Console under Settings → API Keys; today there is no endpoint for managing keys with another key. A read-and-update path through the Admin API is planned.
If you only need your first key, Get an API key is the short version. This page covers the full lifecycle: kinds of key, who sees what, origin rules, rotation, revocation and the errors each state produces.
Kinds of credential
The API reads one header, Authorization: Bearer <credential>. Several kinds of credential can go in it:
| Starts with | Where it comes from | Accepted by /v1/chat/completions |
|---|---|---|
sk_live_ | Private API Keys list in the Console | Yes. Use this for your own services. |
pk_live_ | Public API Keys list in the Console | No. Answered with 403 public_key_rejected. |
sk_syntic_ | Created for Syntic Code | Yes |
| Syntic Code sign-in token | Signing in to Syntic Code | Yes, while the sign-in session is active |
Private keys are created with permission to run inference. Public keys are deliberately limited to read access, which is why the API turns them away.
Create a key
Open the key list
Sign in to the Console, switch to the organization that should be billed, and open Settings → API Keys.
Choose the kind and a name
Create the key under Private API Keys. The name is required; pick one that says where the key will live, such as billing-worker-prod, so you can recognise it in the list and in the audit log later.
Optionally restrict origins
Add allowed origins only if the key will be used from browser pages you control. Leave the list empty for server-side use. See Allowed origins.
Confirm your second factor if asked
If you, or the organization, have multi-factor authentication turned on, the Console asks you to confirm it before the key is made.
Copy the secret
The full secret is shown once. Store it in your secret manager or deployment environment straight away.
Syntic keeps only a one-way fingerprint of the secret plus its first few characters. A secret that was not copied cannot be recovered; revoke the key and make a new one.
A private key looks like sk_live_ followed by 64 hexadecimal characters.
Use the key
Keep the key in an environment variable and send it as a bearer token.
export SYNTIC_API_KEY="sk_live_..."
curl -sS https://app.syntic.ai/v1/chat/completions \
-H "Authorization: Bearer $SYNTIC_API_KEY" \
-H "Content-Type: application/json" \
-d '{"model": "Amara Quick", "stream": false,
"messages": [{"role": "user", "content": "Name three prime numbers."}]}'What the key list shows
Each active key appears with:
| Column | Meaning |
|---|---|
| Name | The label you gave it when creating it |
| Kind | Private or public |
| Prefix | The first characters of the secret, enough to match a key to a leaked or logged value |
| Allowed origins | The origin restriction, if any |
| Created | When it was made |
| Last used | When it last authenticated a request |
| Uses | How many requests it has authenticated |
Revoked keys drop out of the list. Their creation and revocation stay in the audit log.
Who sees and manages which keys
| Role | Sees | Can create | Can revoke |
|---|---|---|---|
| Primary owner, Owner, Administrator | Every key in the organization | Yes | Any key |
| Developer | Only keys they created | Yes | Only their own |
A developer who tries to revoke someone else’s key gets a “not found or not permitted” refusal; the Console does not reveal whether the key exists. Custom roles are covered in Organizations & roles.
Allowed origins
An origin restriction is for keys used from a browser, where you want the key to work only on your own sites.
How entries are written
- An entry is a scheme, host and optional port:
https://app.example.com,http://localhost:3000. - A pasted full URL is cut down to its origin, so
https://app.example.com/settings?tab=1is stored ashttps://app.example.com. - A bare host such as
app.example.comis treated ashttps://app.example.com. - A leading
*.matches any subdomain:https://*.example.comacceptshttps://eu.example.comandhttps://a.b.example.com, but nothttps://example.com. Add the bare domain as its own entry if you need it. - Default ports are ignored, so
https://app.example.com:443andhttps://app.example.comare the same entry. Upper and lower case are treated alike in hosts and schemes. - An entry the Console cannot read as an origin is rejected when you save, with a message naming it.
How requests are checked
- If the list is empty, the key works from anywhere.
- Otherwise the API takes the request’s
Originheader, or the origin part ofRefererif there is noOrigin. - If neither is present, or the browser sent the opaque origin
null, the request is refused. - If the origin does not match an entry, the request is refused.
Refusals answer 403 origin_not_allowed. Server code, curl and most backend HTTP clients send no Origin, so a restricted key will not work from your backend. Keep a separate unrestricted key there.
An origin restriction limits where a key is accepted from; it does not hide the key. Anything shipped to a browser can be read by its user. Do not put a private key in front-end code unless you accept that its holder can spend your credits, and prefer routing calls through your own backend.
IP allowlists
An IP allowlist limits a key to requests coming from addresses you list: your office network, your servers’ egress addresses, your VPN. Unlike an origin allowlist, it works for server-side traffic and cannot be satisfied by forging a header, so it is the right control for backend keys.
Where it can be set
| Level | Applies to | Who sets it |
|---|---|---|
| Key | That key only | Anyone who can manage the key |
| Workspace | Every key in the workspace, plus workload identity tokens | Administrators and Owners |
When both are set, a request must match both lists.
How entries are written
- Single IPv4 or IPv6 addresses (
203.0.113.7,2001:db8::1) or CIDR ranges (203.0.113.0/24,2001:db8::/48). - Up to 100 entries per list. Ranges wider than
/8(IPv4) or/32(IPv6) are refused, so a list cannot accidentally allow most of the internet. - An empty list means no IP restriction.
How requests are checked
- The address used is the one that connected to Syntic’s edge.
X-Forwarded-Forand similar headers sent by the client are ignored. - If the address is not in the list, the request is refused with
403, typepermission, codeip_not_allowed. The message includes the address Syntic saw, so you can add the right egress IP. - Refusals are recorded as
api_key.ip_deniedaudit events, sampled to one per key per minute.
Set it in the Console on the key’s row under Network → IP allowlist, or for a workspace under Settings → Workspaces → (workspace) → Network.
Set it with the Admin API
curl -sS -X PATCH https://app.syntic.ai/v1/organizations/api_keys/apikey_01JB7C9E1G3J5L7N9Q1S \
-H "Authorization: Bearer $SYNTIC_ADMIN_KEY" \
-H "Content-Type: application/json" \
-H "syntic-beta: admin-2026-11-01" \
-d '{"allowed_ips": ["203.0.113.0/24", "198.51.100.12"]}'For workspaces, send {"allowed_ips": [...]} to PATCH /v1/organizations/workspaces/{workspace_id}. Full field lists are on API key management and Workspaces.
Before turning on an allowlist for a busy key, check the Last used from address shown on the key’s row (part of this feature) to be sure every caller’s egress address is on the list.
Build notes for the platform team: check the allowlist in the shared auth step right after the key is resolved, using the connecting address from the edge; cache parsed CIDR lists with the key record; extend the existing origin-allowlist module rather than adding a second one.
Managing keys with the Admin API
With an Admin API key (sk_admin_...), scripts will be able to list every key in the organization with its name, prefix, workspace, creator, allowed origins and IPs and last use; rename a key; change its network restrictions; and set it to inactive or back to active. Secrets are never returned. This makes it possible to audit keys on a schedule, for example disabling any key unused for 90 days.
Rotate a key
Rotate on a schedule, after someone with access leaves, or whenever a key may have been exposed.
- Create a new private key with a name that shows it is the successor, for example
billing-worker-prod-2026-10. - Deploy it alongside or in place of the old one.
- Watch the list until the new key shows recent Last used activity and the old key’s count stops rising.
- Revoke the old key.
Revoke a key
Revoke from the key’s row in Settings → API Keys. The change is immediate: the next request with that key fails with 401 revoked_api_key. There is no undo, so put the replacement in place first if anything still depends on the old key.
If a secret has leaked, revoke first and rotate afterwards. A few minutes of failed requests is cheaper than a stranger spending your credits.
Signed-in Syntic Code sessions
Settings → API Access lists the Syntic Code sign-ins connected to your account, with a Revoke button for each. Revoking ends that session’s access without touching your API keys. The same page shows this month’s token use against the plan, the usage-credit balance and a sample request you can paste into a terminal.
Errors caused by key state
| Status | error | What happened | What to do |
|---|---|---|---|
| 401 | unauthenticated | No Authorization header, or not a bearer token | Send Authorization: Bearer <key> |
| 401 | invalid_api_key | The value does not match any key | Check for truncation or a stray space or newline |
| 401 | revoked_api_key | The key was revoked | Switch to the replacement key |
| 401 | expired_api_key | The key is past its expiry date | Create a new key |
| 403 | public_key_rejected | A pk_live_ key was used | Use a private sk_live_ key |
| 403 | origin_not_allowed | The key has an origin list and the request’s origin is missing or not on it | Call from an allowed origin, or use an unrestricted key server-side |
| 403 | insufficient_scope | The key lacks permission to run inference | Use a key from the Private API Keys list |
| 403 | org_membership_required | The person who created the key is no longer an active member | Create a key under a current member |
| 403 | account_disabled | The key owner’s account is disabled | Contact an organization owner |
| 403 | org_unavailable | The key’s organization is not available | Contact an organization owner |
The body is the error envelope, for example:
{
"error": {
"type": "authentication_error",
"code": "revoked_api_key",
"message": "This API key was revoked. Create a new one in Settings → API keys.",
"param": null,
"request_id": "req_6f1c2a9e0b7d4c3a8e5f1b2d"
}
}insufficient_scope adds a required field naming the missing permission.
See Errors for every error the API can return.
Good practice
- One key per service and environment. Revoking one should never take down another.
- Never commit keys. Load them from the environment or a secret manager.
- Give keys names that point to their owner and place, so the audit log reads clearly.
- Keys keep working only while their creator is an active member. For long-lived production keys, create them under an account that will stay in the organization.
- Review the list now and then and revoke keys whose Last used date is old.