/v1 API is authenticated with an API key sent as a bearer token:
Key types
There are two kinds of workspace key, created on the API keys page:
Embed a runtime key in products. Keep management keys for CI, scripts and
the SDK’s authoring commands. A runtime key calling a configuration endpoint
gets a
403 with an explanation.
Expiration and spend limits
Every key can be given an expiration when it is created; an expired key is rejected like a revoked one (401). A runtime key can also carry a spend
limit in USD, optionally reset daily, weekly or monthly (UTC). Spend is the
sum of the model cost of runs started with that key; runs whose provider
reports no usage count as $0. Once the limit is reached, runs return 402
with a message saying when the counter resets.
A machine-readable OpenAPI 3.1 spec covers
every endpoint with operationIds for function calling. Prefer calling agents
from an MCP client? See the MCP server.
Rate limits
Requests are rate limited per API key. When you exceed the limit the API returns429 Too Many Requests with a Retry-After header (seconds). Back off and retry.
Endpoints
Agents & runs
GET /v1/agents,POST /v1/agents,PATCH /v1/agents/{id}({ name?, description? }),DELETE /v1/agents/{id}GET /v1/agents/{id}/versions(the agent’s current graph),POST /v1/agents/{id}/versionsGET /v1/agents/{id}/runs,POST /v1/agents/{id}/runs({ input, thread_id?, context? }, streams SSE)GET /v1/runs,GET /v1/runs/{id}(?include_model_calls=trueadds onellm_callstep per model call)GET /v1/runs/{id}/otel: the run as OpenTelemetry trace data (OTLP/JSON, GenAI conventions)GET /v1/runs/{id}/screenshots,GET /v1/runs/{id}/screenshots/{seq}(PNG): what the browser tool saw, one per actionGET /v1/tools,POST /v1/tools,PATCH /v1/tools/{id}({ description?, builtin?, http?, mcp? })GET|POST /v1/browser/profiles,DELETE /v1/browser/profiles/{ref},POST /v1/browser/profiles/{ref}/clear: saved sign-ins for the browser toolGET|POST /v1/browser/credentials,DELETE /v1/browser/credentials/{ref}: portal logins. A password goes in once and is never returned
context on POST /v1/agents/{id}/runs is a small map of string facts your
application already knows and the agent should not ask for, such as a wallet
address or an account id:
a-z A-Z 0-9 _ . -; values are strings of at most 200 characters. When a
message on thread_id answers a paused run, send the same context again so
the continuation keeps it.
Create a built-in tool with POST /v1/tools:
browser, decide, wait_until, verify_plan, plan_write, ask_human,
knowledge_search, memory_search, memory_write, list_skills, use_skill,
calculator. The browser key takes its settings in the same block:
{ "kind": "builtin", "builtin": { "key": "browser", "browser": { "allowedDomains": ["portal.example.com"], "approvals": "consequential" } } }. To add one to an agent, GET its versions to read the current
graph, push the tool id onto a node’s toolRefs, and POST it back with
publish: true.
POST /v1/runs/{runId}/answer with { answer } continues a run that paused to ask a
human. It returns 202; the outcome shows up on GET /v1/runs/{runId}. A paused run’s
interrupt field carries the question.
Schedules
GET /v1/schedules?agent=,POST /v1/schedulesGET /v1/schedules/{id},PATCH /v1/schedules/{id},DELETE /v1/schedules/{id}
POST takes { agent, name, input, frequency, hour?, minute?, weekday?, timezone?, enabled? }.
frequency is hourly, daily or weekly; weekday is 0 (Monday) to 6 (Sunday).
PATCH accepts any of those fields, including enabled to pause and resume.
API keys
GET /v1/api-keys?usage=true,POST /v1/api-keys,DELETE /v1/api-keys/{id}(revoke)
POST takes { name, kind?, expires_in_days?, spend_limit_usd?, spend_reset_period? }
and returns the full key once under key.
Knowledge
GET /v1/knowledge,POST /v1/knowledge,PATCH /v1/knowledge/{kb}({ name?, description? }),DELETE /v1/knowledge/{kb}GET /v1/knowledge/{kb}/documents,POST /v1/knowledge/{kb}/documents,PATCH /v1/knowledge/{kb}/documents/{doc}({ title }),DELETE /v1/knowledge/{kb}/documents/{doc}POST /v1/knowledge/search
Memory
GET /v1/memory,POST /v1/memory,PATCH /v1/memory/{id}({ content }, re-embedded),DELETE /v1/memory/{id}POST /v1/memory/search
Providers
GET /v1/providers,POST /v1/providers,DELETE /v1/providers/{id|label}
POST takes { provider, label, api_key, base_url? }
where provider is openrouter, openai, openai_compatible or typesafe.
Keys are encrypted on write and never returned.
Deployments (hosted apps)
GET /v1/deployments,POST /v1/deployments,GET /v1/deployments/availability?name=GET /v1/deployments/{ref},PATCH /v1/deployments/{ref},DELETE /v1/deployments/{ref}GET /v1/deployments/{ref}/users,POST /v1/deployments/{ref}/users/{uid}/block,DELETE /v1/deployments/{ref}/users/{uid}/blockGET /v1/deployments/{ref}/analytics?days=30,GET /v1/deployments/{ref}/channelsPOST /v1/deployments/{ref}/assets(multipartkind=logo|hero,file)GET /v1/deployments/{ref}/domain,PUT /v1/deployments/{ref}/domain,POST /v1/deployments/{ref}/domain/verify,DELETE /v1/deployments/{ref}/domain
{ref} is a deployment id or its subdomain. POST takes
{ agent, subdomain?, branding?, auth_mode?, status? }; the agent must be published,
auth_mode is public or require_login, status is draft, live or disabled.
branding is { title?, tagline?, logoUrl?, heroImageUrl?, accentColor?, starterPrompts? }
and PATCH replaces it whole.
Skills & subagents
GET /v1/skills,POST /v1/skills({ name, body, description?, enabled? }, upsert by name)GET /v1/skills/{ref},PATCH /v1/skills/{ref}({ name?, description?, body?, enabled? }),DELETE /v1/skills/{ref}GET /v1/subagents,POST /v1/subagents({ agent }, id or name),DELETE /v1/subagents/{ref}(tool id or agent name)
name is lowercase letters, digits and dashes. A subagent is a subagent tool row;
add its id to a node’s toolRefs and push a version to attach it to a parent.
Channels & integrations
GET /v1/channels?agent=|deployment=,POST /v1/channels,PATCH /v1/channels/{id}({ enabled }),DELETE /v1/channels/{id}GET /v1/integrations,POST /v1/integrations({ connector, secret?, label? }),DELETE /v1/integrations/{id|label|slug}
POST /v1/channels takes { agent | deployment, kind, slack_channel_id?, signing_secret?, slack_bot_token?, telegram_bot_token?, whatsapp_phone_number_id?, whatsapp_waba_id?, whatsapp_access_token?, whatsapp_app_secret? }. Tokens and secrets are encrypted on write and
never returned; a Telegram bot’s webhook is registered for you and its URL comes back as
webhook_url. A WhatsApp channel is checked with Meta when it is created and comes back with
webhook_url and whatsapp_verify_token to paste into your Meta app’s webhook settings.
API-key connectors are probed before they are saved; OAuth connectors connect from the dashboard.
WhatsApp on Cruq AI’s number
POST /v1/whatsapp/link-codes({ agent }) returns{ code, expires_at, number, wa_link }. Sendlink <code>tonumberwithin 10 minutes; the agent then runs as the key’s user. Keys without a user get 400.GET /v1/whatsapp/links?agent=,DELETE /v1/whatsapp/links/{id}
Members & voice calls
GET /v1/members,POST /v1/members/invites({ email, role? }),DELETE /v1/members/invites/{id|email}PATCH /v1/members/{user_id|email}({ role }),DELETE /v1/members/{user_id|email}GET /v1/voice-calls?limit=,GET /v1/voice-calls/{id}(with transcript),GET /v1/voice-calls/{id}/recording(WAV)
link as well as sending the email.
Guardrails & prompts
GET /v1/guardrails,POST /v1/guardrails,PATCH /v1/guardrails/{name}({ enabled }),DELETE /v1/guardrails/{name}GET /v1/prompts,POST /v1/prompts,PATCH /v1/prompts/{name}({ name?, description?, template?, model? }; a rename keeps the version),DELETE /v1/prompts/{name}
Evals
GET /v1/evals/suites,POST /v1/evals/suitesGET /v1/evals/suites/{ref},PATCH /v1/evals/suites/{ref},DELETE /v1/evals/suites/{ref}GET /v1/evals/suites/{ref}/cases,POST /v1/evals/suites/{ref}/casesGET /v1/evals/suites/{ref}/suggestions(?limit=,?days=,?agent=): cases picked from the agent’s recent runs. Nothing is savedPATCH /v1/evals/cases/{id}({ name?, input?, scorers?, weight? }),DELETE /v1/evals/cases/{id}GET /v1/evals/experiments,POST /v1/evals/experimentsGET /v1/evals/experiments/{id},PATCH /v1/evals/experiments/{id},DELETE /v1/evals/experiments/{id}GET /v1/evals/experiments/{id}/labels,POST /v1/evals/results/{id}/label({ score: 1-5, note? }): score judged results yourself; the experiment’sjudge_agreementsays how far the judge agrees
409 with the matching ids instead of picking one.
GET /v1/evals/experiments/{id} returns the experiment and its leaderboard;
add ?results=true for the full case x variant matrix. Aggregates are computed
on read, so polling a running experiment returns its partial ranking.
DELETE on an experiment cancels it: pending cells are skipped, and cells
already mid-run are allowed to finish.
See Evals for scorer shapes and worked examples.
Run metering
Every run carries a nestedusage object. Fields are nullable, and null means
unknown - some providers report no usage block, which is not the same as a run
that cost nothing. cost_usd is a string because it is an exact decimal.
source = "eval" and are excluded from GET /v1/runs by
default.
