Skip to content

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.

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:

Terminal window
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.

  1. Open the Console at https://pc.0g.ai/ and go to Dashboard → API.

  2. 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.

  3. 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.

  4. 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.

Key administration needs an mk- key with the matching scope. Export both credentials separately, because they have different blast radii:

Terminal window
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
Terminal window
curl https://router-api.0g.ai/v1/api-keys \
--fail-with-body \
--max-time 30 \
-H "Authorization: Bearer $ZG_MANAGEMENT_KEY"

POST /v1/api-keys accepts a name, and optionally a trust_mode that is enforced on every request made with the key.

Terminal window
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"
}'

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": "…"}.

PATCH /v1/api-keys/:id updates an existing key; DELETE /v1/api-keys/:id revokes it.

Terminal window
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"}'
Terminal window
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.

Nothing forces you to hold only one valid key at a time, so a rotation is an overlap, not a cutover.

  1. Create the replacement key, in the Console or through POST /v1/api-keys. Copy the secret; it is shown once.

  2. Write it to your secret store and deploy. Both keys are valid, so nothing is interrupted.

  3. Confirm the new key is actually serving traffic before going further. For an sk- key, look for requests under it in Dashboard → Activity. For an mk- key, check that last_used_at has moved.

  4. 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.

  • Keys come from the environment, never from source. ZG_API_KEY for inference, ZG_MANAGEMENT_KEY for 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 needs account:read and nothing more.
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.