Create and manage API keys
Your account identity is your wallet address. Everything else hangs off it, including two kinds of credential: sk- keys that call inference and spend your balance, and mk- management keys that read account state and administer sk- keys. This page covers creating them, using them, rotating them, and revoking them.
The two credentials
Section titled “The two credentials”| API key | Management key | |
|---|---|---|
| Prefix | sk- |
mk- |
| Used for | Inference endpoints | /v1/account/* and /v1/api-keys |
| Billed | Yes, against your deposit | Never |
| Scopes | No scope concept | account:read, keys:read, keys:create, keys:manage |
| Expiry | Valid until revoked | Does not expire; rotate deliberately |
Both travel in the same header:
Authorization: Bearer sk-<YOUR_API_KEY>An mk- key cannot create or manage other mk- keys. Management keys are issued only from a signed-in wallet session in the Console.
Create a key in the Console
Section titled “Create a key in the Console”-
Open the Console at https://pc.0g.ai/ and go to Dashboard → API.
-
Enter a name. Name the key for the place it will run —
prod-api,staging,agent-worker,local-dev— so that revoking one key does not mean guessing what breaks. -
Optionally open Advanced settings and set a spending limit. Recommended for any key that runs unattended, since a limit bounds what a runaway loop or a leaked key can spend.
-
Click Create.
The secret is shown once. The Router stores only a hash of it and cannot display it again; if you lose it, revoke the key and create another.
Management keys live under Dashboard → Management, created the same way from the wallet session.
Manage keys through the API
Section titled “Manage keys through the API”Key administration needs an mk- key with the matching scope. Export both credentials separately, because they have different blast radii:
export ZG_API_KEY="<YOUR_API_KEY>"export ZG_MANAGEMENT_KEY="<YOUR_MANAGEMENT_KEY>"| Call | Scope |
|---|---|
GET /v1/api-keys |
keys:read |
POST /v1/api-keys |
keys:create |
PATCH /v1/api-keys/:id |
keys:manage |
DELETE /v1/api-keys/:id |
keys:manage |
List keys
Section titled “List keys”curl https://router-api.0g.ai/v1/api-keys \ --fail-with-body \ --max-time 30 \ -H "Authorization: Bearer $ZG_MANAGEMENT_KEY"const managementKey = process.env.ZG_MANAGEMENT_KEY;if (!managementKey) throw new Error("ZG_MANAGEMENT_KEY is not set");
const baseUrl = process.env.ZG_BASE_URL ?? "https://router-api.0g.ai/v1";
const res = await fetch(`${baseUrl}/api-keys`, { headers: { Authorization: `Bearer ${managementKey}` }, signal: AbortSignal.timeout(30_000),});
if (!res.ok) { const body = await res.json(); throw new Error(`Router error ${res.status} ${body.error?.code}: ${body.request_id}`);}
console.log(await res.json());import os
import httpx
management_key = os.environ["ZG_MANAGEMENT_KEY"]base_url = os.environ.get("ZG_BASE_URL", "https://router-api.0g.ai/v1")
res = httpx.get( f"{base_url}/api-keys", headers={"Authorization": f"Bearer {management_key}"}, timeout=30.0,)res.raise_for_status()print(res.json())Create a key
Section titled “Create a key”POST /v1/api-keys accepts a name, and optionally a trust_mode that is enforced on every request made with the key.
curl https://router-api.0g.ai/v1/api-keys \ --fail-with-body \ --max-time 30 \ -H "Content-Type: application/json" \ -H "Authorization: Bearer $ZG_MANAGEMENT_KEY" \ -d '{ "name": "agent-worker", "trust_mode": "private" }'const managementKey = process.env.ZG_MANAGEMENT_KEY;if (!managementKey) throw new Error("ZG_MANAGEMENT_KEY is not set");
const baseUrl = process.env.ZG_BASE_URL ?? "https://router-api.0g.ai/v1";
const res = await fetch(`${baseUrl}/api-keys`, { method: "POST", headers: { "Content-Type": "application/json", Authorization: `Bearer ${managementKey}`, }, body: JSON.stringify({ name: "agent-worker", trust_mode: "private" }), signal: AbortSignal.timeout(30_000),});
const created = await res.json();if (!res.ok) throw new Error(`Router error ${res.status} ${created.error?.code}`);
// The secret is returned once, here. Write it to your secret store now.console.log(created);import os
import httpx
management_key = os.environ["ZG_MANAGEMENT_KEY"]base_url = os.environ.get("ZG_BASE_URL", "https://router-api.0g.ai/v1")
res = httpx.post( f"{base_url}/api-keys", headers={"Authorization": f"Bearer {management_key}"}, json={"name": "agent-worker", "trust_mode": "private"}, timeout=30.0,)res.raise_for_status()
# The secret is returned once, here. Write it to your secret store now.print(res.json())A key-level trust_mode applies to every request that key makes, and enforcement does not depend on each caller remembering to send a header. Verified against the live API with a private key:
| What the caller does | What happens |
|---|---|
| Sends no trust-mode header | The key’s tier applies. A model with no TeeML provider returns 503 no_provider_for_trust_mode |
| Sends a header matching the key | Served normally |
Sends a weaker header, such as standard |
403 with code: trust_mode_mismatch and the message trust mode mismatch: key=private, request=standard |
The third row is the one worth knowing before you deploy: a mismatched header is a hard failure, not a silently ignored preference. Code that sets standard globally and expects a private key to override it will fail every request instead of quietly complying. See Trust modes.
A create call returns the key object with one extra field — key, the full secret, present only in this response:
{ "key_id": "502cd61b-b364-4849-a5a3-a9dbce3755b6", "key": "sk-…", "key_preview": "sk-29fea…", "name": "docs-f16-temp", "trust_mode": "private", "status": "active", "revoked": false, "credit_limit": null, "used": "0", "currency": "0g", "reset_period": "never", "expires_at": null, "allowed_models": [], "allowed_providers": [], "created_at": "2026-07-20T08:45:34.06Z"}Live response, 2026-07-20, with the secret redacted. Revoking returns {"message": "API key revoked", "key_id": "…"}.
Update or revoke a key
Section titled “Update or revoke a key”PATCH /v1/api-keys/:id updates an existing key; DELETE /v1/api-keys/:id revokes it.
curl -X PATCH https://router-api.0g.ai/v1/api-keys/<KEY_ID> \ --fail-with-body \ --max-time 30 \ -H "Content-Type: application/json" \ -H "Authorization: Bearer $ZG_MANAGEMENT_KEY" \ -d '{"name": "agent-worker-eu"}'curl -X DELETE https://router-api.0g.ai/v1/api-keys/<KEY_ID> \ --fail-with-body \ --max-time 30 \ -H "Authorization: Bearer $ZG_MANAGEMENT_KEY"Revocation is immediate and applies everywhere, with no grace period. Verified against the live API: the next request using a revoked key returns 401 with code: invalid_api_key — the same code an unknown key produces, so a deployment that starts failing this way has either the wrong key or a revoked one, and your own records are what tell them apart.
Listing keys is read-only and safe to run now. GET /v1/api-keys with an mk- key returns, per key:
{ "key_id": "…", "key_preview": "sk-…", "name": "docs-f16", "status": "active", "revoked": false, "trust_mode": "", "credit_limit": "0", "used": "0", "currency": "0g", "reset_period": "", "expires_at": null, "allowed_models": null, "allowed_providers": null, "created_at": "…"}Live response fields, 2026-07-20. Three are worth knowing: trust_mode is the per-key tier enforcement described above, credit_limit with used is the spending cap you can set when creating a key, and key_preview is how you match a key in the Console against one in your secret store without revealing it.
Rotate without downtime
Section titled “Rotate without downtime”Nothing forces you to hold only one valid key at a time, so a rotation is an overlap, not a cutover.
-
Create the replacement key, in the Console or through
POST /v1/api-keys. Copy the secret; it is shown once. -
Write it to your secret store and deploy. Both keys are valid, so nothing is interrupted.
-
Confirm the new key is actually serving traffic before going further. For an
sk-key, look for requests under it in Dashboard → Activity. For anmk-key, check thatlast_used_athas moved. -
Revoke the old key.
Step 3 before step 4 is the whole procedure. Revoking first turns a routine rotation into an outage, and a deployment that silently kept reading the old value from a cached environment will not tell you until requests start failing.
Audit what a management key has been doing
Section titled “Audit what a management key has been doing”Each mk- key records two fields on successful authentication:
| Field | Meaning |
|---|---|
last_used_at |
Timestamp of the most recent successful use |
last_source_ip |
Source address of that use |
Both are coalesced to at most one write per key per 60 seconds, so a burst of calls updates them once rather than each time. Read them on a schedule: a management key untouched for months is a key you can revoke, and an unfamiliar source address is a reason to rotate now.
Storing keys
Section titled “Storing keys”- Keys come from the environment, never from source.
ZG_API_KEYfor inference,ZG_MANAGEMENT_KEYfor account and key administration. - Two variables, not one shared name. A single name invites shipping the management key to a runtime that only needed inference.
- No key reaches a browser or a mobile client. Proxy those calls through your own backend; bundler prefixes such as
NEXT_PUBLIC_inline the value into the client bundle. - One key per environment and per deployment, so revoking one cannot take down the others.
- Give each
mk-key the minimum scope. A balance alert needsaccount:readand nothing more.
Common errors
Section titled “Common errors”| Status | code |
Cause | Fix |
|---|---|---|---|
| 401 | missing_authorization |
No Authorization header arrived |
Confirm the variable is exported in the process that made the call |
| 401 | invalid_api_key |
The key does not exist or was truncated when copied | Re-copy the secret, or create a replacement |
| 401 | invalid_api_key |
The key was revoked, or never existed | Deploy the replacement key everywhere |
| 403 | insufficient_scope |
A sk- key called an account endpoint, or the mk- key lacks the scope |
Use an mk- key carrying the required scope |
| 404 | api_key_not_found |
The key ID in the path does not exist | List keys first and use the returned ID |
Full table with retry guidance: Error codes.
Next steps
Section titled “Next steps”- Harden for production — the rest of the pre-launch checklist
- API & Authentication — every endpoint and the credential it accepts
- Trust modes — what a per-key
trust_modeactually enforces