Create an API key
const url = 'https://api.hyperresearch.ai/v1/keys';const options = { method: 'POST', headers: {Authorization: 'Bearer <token>', 'Content-Type': 'application/json'}, body: '{"name":"example","prefix":"hr_live_","scopes":["runs:read","vault:read"],"expires_at":"2026-04-15T12:00:00Z","ip_allowlist":["example"]}'};
try { const response = await fetch(url, options); const data = await response.json(); console.log(data);} catch (error) { console.error(error);}curl --request POST \ --url https://api.hyperresearch.ai/v1/keys \ --header 'Authorization: Bearer <token>' \ --header 'Content-Type: application/json' \ --data '{ "name": "example", "prefix": "hr_live_", "scopes": [ "runs:read", "vault:read" ], "expires_at": "2026-04-15T12:00:00Z", "ip_allowlist": [ "example" ] }'The secret is returned exactly once. The key is always minted into the calling key’s workspace. Pass scopes to narrow what the key may do; omit it for the default set (runs:write, runs:read, vault:read, vault:write, verify). Add mcp explicitly for a key a headless agent will present to the MCP server; it is never in the default set and admin does not imply it. Add admin:read — alone, on a key named operator-diagnostics — for the operator diagnostic read (GET /v1/admin/runs*); it grants no write and no health, billing or backfill endpoint, and admin implies it. A key form should show the scopes before minting: production minted write access to runs and the vault from a form that named none of it (E2E3-14).
Authorizations
Section titled “Authorizations”Request Bodyrequired
Section titled “Request Bodyrequired”object
What this key may do. Omit it for the default set — runs:write, runs:read, vault:read, vault:write, verify — which is what every key minted before this field existed carries. answers:ask (opt-in, never default) makes billed Answers calls (/v1/answers*) from the prepaid wallet; pair it with mcp for the Answers MCP server. Valid values: runs:write, runs:read, vault:read, vault:write, verify, mcp, admin, admin:read, answers:ask. An unknown scope is a 400 on scopes, never a silently dropped field, and an empty array is refused: a key that can do nothing is a mistake. admin is owner-equivalent — it implies every other scope except mcp — so grant it deliberately. mcp lets this key open a session with the MCP server at https://mcp.hyperresearch.ai/mcp (Authorization: Bearer <key>), for a headless agent that has no browser for the OAuth flow. It is a front-door marker only: every tool still needs its own data scope, so pair it with vault:read / runs:read as needed. It is never granted by default and never implied by admin. admin:read is the operator diagnostic read (GET /v1/admin/runs*, ops/diagnosing-a-run.md): the one scope here that is not bounded by the key’s own workspace, and the one that grants no write anywhere and no health, billing or backfill endpoint. Grant it alone, on a key named operator-diagnostics. It is never in the default set; admin implies it, so granting both is redundant rather than wrong.
Example
[ "runs:read", "vault:read"]Responses
Section titled “ Responses ”Created
object
Example
{ "id": "key_01J9ZQ7V6H0000000000000000", "prefix": "hr_live_", "masked": "hr_live_7Qa1bC3dE5f9…", "scopes": [ "runs:write" ], "secret": "hr_live_7Qa1bC3dE5f9…"}Headers
Section titled “Headers”Example
"reads";q=60;w=60The quota policy this request was counted against (draft-ietf-httpapi-ratelimit-headers): "<bucket>";q=<requests>;w=<window seconds>, e.g. "reads";q=60;w=60. Buckets: reads 60/min, status 600/min, creates 10/min, writes 20/min per credential; public 300/min per network on keyless routes.
Example
"reads";r=57;t=41Remaining quota in the current window: "<bucket>";r=<remaining>;t=<seconds until reset>, e.g. "reads";r=57;t=41.
Requests allowed per window in this bucket (legacy form of RateLimit-Policy).
Requests left in this window (legacy form of RateLimit).
Unix time (seconds) when the window resets.
Which bucket counted the request: reads, status, creates, writes or public.
Invalid request
object
object
Example
{ "error": { "type": "invalid_request", "code": "missing_api_key", "request_id": "req_01J9ZQ7V6H0000000000000000" }}Authentication failed
object
object
Example
{ "error": { "type": "invalid_request", "code": "missing_api_key", "request_id": "req_01J9ZQ7V6H0000000000000000" }}Insufficient scope or plan
object
object
Example
{ "error": { "type": "invalid_request", "code": "missing_api_key", "request_id": "req_01J9ZQ7V6H0000000000000000" }}Not found
object
object
Example
{ "error": { "type": "invalid_request", "code": "missing_api_key", "request_id": "req_01J9ZQ7V6H0000000000000000" }}Rate limited or over quota
object
object
Example
{ "error": { "type": "invalid_request", "code": "missing_api_key", "request_id": "req_01J9ZQ7V6H0000000000000000" }}Headers
Section titled “Headers”Seconds to wait before retrying. Always present on a 429.
Example
"reads";q=60;w=60The quota policy this request was counted against (draft-ietf-httpapi-ratelimit-headers): "<bucket>";q=<requests>;w=<window seconds>, e.g. "reads";q=60;w=60. Buckets: reads 60/min, status 600/min, creates 10/min, writes 20/min per credential; public 300/min per network on keyless routes.
Example
"reads";r=57;t=41Remaining quota in the current window: "<bucket>";r=<remaining>;t=<seconds until reset>, e.g. "reads";r=57;t=41.
Requests allowed per window in this bucket (legacy form of RateLimit-Policy).
Requests left in this window (legacy form of RateLimit).
Unix time (seconds) when the window resets.
Which bucket counted the request: reads, status, creates, writes or public.
Internal error
object
object
Example
{ "error": { "type": "invalid_request", "code": "missing_api_key", "request_id": "req_01J9ZQ7V6H0000000000000000" }}