Authentication
Every authenticated request carries a bearer token:
Authorization: Bearer zw_<env>_<prefix>_<secret>
There are two kinds of credential, and which you use depends on whether the action is a service or a person:
- API keys (
zw_…) — workspace service accounts, for CI and production. The format below. - Personal access tokens (
zw_pat_…) — you, across your workspaces, for the CLI and MCP.
Both go in the same Authorization: Bearer header. The rest of this page is the API key format; an API key is workspace-scoped — the workspace is intrinsic to it, so endpoints have no :slug in the URL.
| Segment | Example | Notes |
|---|---|---|
| Namespace | zw | Constant. Identifies the credential as a ZeroWidth key. |
| Environment | live or test | Distinguishes production from sandbox keys at a glance. (test is reserved for the future sandbox surface.) |
| Prefix | abc12345 | 8-character non-secret identifier. Safe to log, paste in support tickets, display in your secret manager. |
| Secret | …43 random chars… | The actual credential. Never log this. Shown once when the key is minted — we can't retrieve it for you afterward, so store it safely. |
The full string is the credential — splitting it doesn't help, both halves are needed to authenticate.
Examples
curl https://api.zerowidth.ai/1.0/flows/$FLOW_ID/runs \
-H "Authorization: Bearer $ZW_API_KEY" \
-H "Content-Type: application/json" \
-d '{ "input": { "kind": "form", "values": { "data": "hello" } } }'const res = await fetch(
`https://api.zerowidth.ai/1.0/flows/${flowId}/runs`,
{
method: "POST",
headers: {
Authorization: `Bearer ${process.env.ZW_API_KEY}`,
"Content-Type": "application/json",
},
body: JSON.stringify({
input: { kind: "form", values: { data: "hello" } },
}),
},
)
const result = await res.json()import os, requests
res = requests.post(
f"https://api.zerowidth.ai/1.0/flows/{flow_id}/runs",
headers={
"Authorization": f"Bearer {os.environ['ZW_API_KEY']}",
"Content-Type": "application/json",
},
json={"input": {"kind": "form", "values": {"data": "hello"}}},
)
result = res.json()Next: API keys covers minting, scoping, and budgeting keys; personal access tokens covers the zw_pat_… user tokens for the CLI and MCP.
Troubleshooting
401 auth_invalid
The key is unknown, revoked, or mistyped — all funnel into the same response. Check you copied the whole key (both halves are needed), that it isn't revoked in the dashboard, and that there's no trailing whitespace.
401 auth_missing
The header isn't in the Authorization: Bearer <key> shape. Confirm the header name and the Bearer prefix, including the space.
403 forbidden on a Caliper endpoint
The Caliper eval endpoints accept only Workspace-wide keys, not Personal keys. Mint a workspace key with the right scope.
I lost the key / can't find the secret
The secret is shown exactly once at mint and can't be retrieved. Revoke the old key and mint a new one.