The HTTP API

Call the platform from any language. The SDKs are thin clients over these conventions.

Everything the SDKs do is a plain HTTPS call, so any language with an HTTP client can use the platform.

Authenticate

Send your API key (or a user's session token) as a bearer token:

curl https://api.noclick.com/v1/users \
  -H "Authorization: Bearer $NOCLICK_API_KEY"

Act for a user

Add the NoClick-User header with your id for the user, as it is:

curl -X POST https://api.noclick.com/v1/connections \
  -H "Authorization: Bearer $NOCLICK_API_KEY" \
  -H "NoClick-User: user_42" \
  -H "Content-Type: application/json" \
  -d '{"name": "gmail", "integration": "gmail", "reason": "to send your summaries"}'

In a path, the id is percent-encoded: GET /v1/users/user_42, GET /v1/users/auth0%7C123.

Integrations and operations

Any key with the tools or credentials:use scope reads the catalog of integrations:

MethodPathWhat it does
GET/v1/integrationsEvery integration: name, label, kind, subscription, connect_with, credential_types, operation_count and account_free_operation_count; q keeps the ones whose name or label contains it, kind (app or model) the ones of that kind
GET/v1/integrations/{name}One integration, with its operations
GET/v1/integrations/{name}/operationsIts operations, each with parameters, read_only, scopes and needs_account; q keeps the ones whose name or description contains it
GET/v1/integrations/{name}/operations/{operation}One operation, or 404 for a name the integration doesn't have
POST/v1/runRun one operation by its ref (ref, arguments, approval_id), acting for the NoClick-User user when you send one, else for your account
POST/v1/connections/{ref}/operations/{operation}/runRun an operation through a connection (arguments, approval_id); ref is the connection's id or name

account_free_operation_count is how many of an integration's operations need no account, which is 0 for most. An integration whose kind is model is a model provider (anthropic, openai, ...): it has no operations, and a connection to it is an agent's model_connection, never a tool. subscription says its connect link also offers signing in with the provider's plan. POST /v1/connections/{ref}/token refuses a model provider's connection with 403 permission_denied.

Running an operation takes a key with the credentials:use scope. POST /v1/run takes a ref, "name.operation", the same one an agent's tools take, and resolves it where the call acts:

  1. A connection named name runs it, in that connection's account.
  2. Else, when an integration is named name and the operation needs no account, it runs on NoClick's key with no connection.
  3. Else the call fails. An operation that needs an account answers 400 with invalid_request: create a connection named name. A name that is neither a connection nor an integration answers 404.
curl -X POST https://api.noclick.com/v1/run \
  -H "Authorization: Bearer $NOCLICK_API_KEY" \
  -H "Content-Type: application/json" \
  -d '{"ref": "exa.search", "arguments": {"query": "invoice automation"}}'
{"object": "operation_result", "ref": "exa.search", "integration": "exa", "operation": "search", "connection": null, "result": {"results": [...]}}

result is the operation's data, what the service answered. When the service refuses the call, the run answers 424 with upstream_error: the service's own words, and its HTTP status as upstream_status. A service that fails or doesn't answer gives 502 with upstream_error. connection names the connection that ran it, or is null on NoClick's key. On a pending connection, an operation with needs_account: false still runs on NoClick's key, and one that needs an account answers 424 with needs_connection. The same rules resolve the refs in an agent's, a toolkit's and a hosted MCP server's tools. POST /v1/toolkits/call with simulated: {world, scenario} (world a world_ id your code makes once per toolkit) resolves the call the same way, then a simulated world answers it instead of the account (Simulate your own tools). A connection's used_by lists the agents, hosted MCP servers and apps (kind agent, mcp_server, project) holding it.

Agents and harnesses

An agent's harness is sdk (the default) or a coding harness: claude_code, codex, opencode, hermes or openclaw. POST /v1/agents and PATCH /v1/agents/{id} take harness, model (the harness's own model; the harness's default when omitted, and after a harness change that sends no model) and model_connection, the name of a model provider's connection in the agent's scope. Every harness but sdk needs one; it may still be pending. They take timezone too (an IANA name, null clears it): every turn opens with the date and time there, else in the acting user's timezone (POST /v1/users and PATCH /v1/users/{id} take one), else UTC.

They take repos as well: up to 10 checkouts, each {"connection", "repository", "branch", "path", "push"} with connection a github connection of the agent's scope (it may be pending), repository its owner/name, path the folder under /workspace (the repository's name when omitted) and push a list of branch globs (omitted: read-only). They're stored as sent and the agent carries them as repos (null for a user reading an account agent). Each is cloned at a conversation's first turn through NoClick's git endpoint, which refuses a push to any ref the globs don't name; a turn whose checkout's connection isn't connected fails with 424 needs_connection (with its connect_url for a caller in the connection's scope). A repo_id entry (a NoClick repo) is in invite-only beta: without access, it answers 400.

MethodPathWhat it does
GET/v1/harnessesEvery harness: name, label, version (null for sdk), default_model, custom_models (whether model also takes ids it doesn't list), model_connection (required or optional), integrations (the model providers that serve it), streaming (tokens or final), shell (built_in or code_capability) and notes. Takes the agents scope
GET/v1/harnesses/{name}/modelsThe models a harness offers, its default first, each a harness_model with id (what model takes) and label. q keeps those whose id or label contains it (any case); limit (50, at most 200) and cursor page it. Takes the agents scope

A harness's model list comes from its providers' own catalogs, thousands of models on sdk, so it's searched rather than listed with the harness; a search after a few quiet minutes reads them afresh and can take several seconds.

A turn on a coding harness is an agent.message operation like any other, whose step says where it is while it runs: delivering while its sandbox starts and takes the message, then working. It's null on sdk and once the operation ends. A pending model connection fails the turn with 424 needs_connection, a revoked one with 424 credential_disconnected, and a key the provider refuses carries the provider's own words.

{"object": "operation", "id": "3b9e...", "kind": "agent.message", "status": "running", "step": "working", "result": null}

A wake-up gives one of an agent's conversations, the NoClick-User header's thread, a turn later; it takes the agents scope. See Wake it up later.

MethodPathWhat it does
POST/v1/agents/{id}/wakeups201 with a wakeup: conversation, note (1 to 4,000 characters) and exactly one of at (ISO 8601 with its offset, a minute to a year ahead), after_s (60 to 31,536,000) or when: {"operation_id"}, due when it reaches a terminal status, or {"event", "filters"}, due when the first matching event after it is dispatched (woken_by is then {"event"}); a 101st pending one answers 429 quota_exceeded
GET/v1/agents/{id}/wakeupsIts wake-ups, newest first; conversation, status (a comma list of pending, delivered, canceled, failed), user_id (all, none or a user's id: whose threads, as on other lists), limit and cursor
GET/v1/agents/{id}/wakeups/{wakeup_id}Read one
POST/v1/agents/{id}/wakeups/{wakeup_id}/cancelCancel a pending one (409 conflict once it was delivered, canceled or failed)

A channel's conversation (channel:<id>) takes turns only from the channel, so POST /v1/agents/{id}/messages and POST /v1/agents/{id}/wakeups refuse it with 400 invalid_request (field: "conversation"), and A2A and the AI API refuse it in their own error shapes. A delivered wake-up's operation_id is the agent.message operation its turn runs as, and woken_by says what woke it: {"at"} for a time, {"operation_id", "kind", "status", "result" | "error"} for an operation, {"update"} for a child's update. NoClick's own wake-ups have source update, answer (a connect link the agent asked for is done) or approval (a held call was decided).

Spawning, jobs and asking take the agents scope. See Coordinators and sub-agents.

MethodPathWhat it does
POST/v1/agents/{id}/childrenSpawn a child and start its task at once (task, updates, conversation, instructions, tools, budget_usd_per_month, ttl_s, model, harness, model_connection, name): 201 with the child and its job, an agent.task operation. updates lists "blocked", {"before": [patterns]} and {"every_s": n} (60 or more) beside "done"; a tool the parent lacks answers 400 invalid_request (missing), and a 21st live child or a 4th level 429 quota_exceeded
GET/v1/agents/{id}/jobsThe jobs it started, newest first: its children's tasks (agent.task) and the messages it sent with its agents tools (agent.message) (status, a comma list or live; kind; agent_id, the agent doing the work; conversation, its own whose turn each wakes; limit, cursor). A user reads its own; your account every user's
GET/v1/operations/{id}/updatesA job's updates, oldest first (limit, cursor); 404 for an operation that isn't a job
POST/v1/agents/{id}/askAsk it something without stopping it (question, conversation, interject): 202 with an agent.ask operation, whose result is an agent reply plus joined (the turn an interjected question joined). With interject and nothing running, 202 with the agent.message of an ordinary turn. An agent on a Claude or ChatGPT plan answers a snapshot ask with 409 not_available (reason: "plan_snapshot")

Each agent carries job_id, the task spawn started it with. A job's view adds started_by (its parent, or the agent that sent the message), conversation (the worker's: a child's is task), parent_conversation (the one its updates wake, or null), updates and latest_update.

In tools, "agents" gives an agent the tools to spawn, message, ask, check, list and cancel other agents in its own conversations, and "agents.spawn" and its siblings give one each; any other agents.<name> answers 400, and a connection can't be named agents. A message it sends is an agent.message job, so its reply is the job's done update. A job_update has id, job_id, agent_id, kind (done, blocked, before, every or message), text, tool, arguments, status (the job's then), result, error, waiting_on ({"kind": "connection" | "approval", "url", "operation_id"}), wakeup_id and created_at.

A test run takes the agents scope too:

MethodPathWhat it does
POST/v1/agents/{id}/rehearsalsA real turn against a simulated world (message, and scenario, up to 8,000 characters): 202 with an agent.rehearse operation whose result is reply, tool_calls (tool, arguments, result), transcript, scenario and cost_usd. An 11th queued or running run of the account answers 429 quota_exceeded; a model connection that can't run (pending, disconnected, or not for its model) is refused up front with the error its turn would fail with

An agent's memory takes the agents scope. Each call acts on one layer: for an account agent the account's (no NoClick-User) or the acting user's; a user's own agent has only its user's. user_id (none or a user's id, from a key acting for the whole account) names the layer instead of the header, without acting for the user, so a suspended user's memory still reads.

MethodPathWhat it does
GET/v1/agents/{id}/memoryThe layer's entries by name, without content. With q: the entries of the layer and (a user's layer of an account agent) the account's holding any of its words, or its text in a name or description, best first, with content (limit, 10, at most 100)
GET/v1/agents/{id}/memory/{name}One memory_entry: name, description, content, version, updated_by (agent, api or console), user_id (null for the account's), created_at, updated_at
PUT/v1/agents/{id}/memory/{name}Save it (content, 1 to 20,000 characters; description, up to 200, required for a new entry and kept when omitted; version, only over that version, else 409 conflict with current_version). A name is 1 to 64 of a-z 0-9 . _ -; a 501st entry in the layer answers 429 quota_exceeded
DELETE/v1/agents/{id}/memory/{name}Forget it (version as on PUT); 404 when there's none

Streaming, watching and sharing take the agents scope. See Sharing and streaming.

MethodPathWhat it does
POST/v1/agents/{id}/messages?stream=1The turn as server-sent events instead of its operation: text (text), tool_call (tool, arguments, held), tool_result (tool, result, is_error), then done (the turn's result: text, conversation, tool_calls, cost_usd, model_usage) or error ({"error": {...}}), each with operation_id. A refusal before the turn starts (a busy conversation, an expired agent) is an ordinary error response
GET/v1/agents/{id}/conversations/{key}/eventsOne conversation live, from now until it's deleted: message (role, text, operation_id: the turn that sent it, null for none), text, tool_call, tool_result and status (awaiting_human or active). user_id (none or a user's id) names whose thread, as on the conversation read
GET/v1/agents/{id}/shareIts public chat page, a share_link: url, enabled, agent_id, created_at (first shared) and rotated_at; 404 if it was never shared
POST/v1/agents/{id}/shareTurn the page on, made the first time, at the URL it has
PATCH/v1/agents/{id}/share{"enabled": false} turns it off (true back on)
POST/v1/agents/{id}/share/rotateA new URL; the old one stops at once. Whether it's on stays as it was
POST/v1/agents/{id}/share/messagesA visitor's turn, as on the page: text, visitor (16 to 40 of A-Z a-z 0-9 - _, the page's random id for the visitor) and conversation (default main). Waits up to 25 seconds and answers a share_reply: operation_id, conversation, status (running, succeeded or failed), text, and error when it failed; still running answers 202. Takes a publishable key that lists the agent, or the owner's key
GET/v1/agents/{id}/share/messages/{operation_id}That turn's share_reply, waiting up to wait_s (at most 25) for it to finish: only a turn the same key started on this page

Both streams send a : keepalive comment every 15 seconds. A turn's result carries model_usage (input_tokens, output_tokens, cached_tokens, usd) for an agent with a model_connection, else null; so does each conversation in GET /v1/agents/{id}/conversations. Only the agent's owner shares it: a user acting on an account agent gets 403.

Each agent carries ttl_s (the time to live it was made with), status (working while a turn is queued, running or held, idle, or expired past its ttl_s), last_active_at, running_turns and child_count; with NoClick-User set, an account agent's last four are null, and its activity filters never match it. GET /v1/agents lists expired agents for the 30 days they're kept, filters by status, active_since, parent_id, temporary (true or false) and harness, and sorts by order (created, the default, or last_active). An expired agent answers a message or a PATCH with 410 expired; DELETE removes it.

Triggers and channel subscriptions

An agent's triggers and its pause switch take the agents scope, a channel's subscriptions and schedules the channels scope, and the events catalog tools or credentials:use. See Triggers and Subscribe a channel to events.

MethodPathWhat it does
GET/v1/integrations/{name}/eventsThe app events an integration pushes, each an integration_event: name, description, filters (field to what it matches) and required_filters
GET/v1/agents/{id}/triggersIts schedules, webhook, app-event and NoClick-event triggers, then the inboxes (email) and numbers (call) that wake it; a user's call on an account agent is 403
POST/v1/agents/{id}/triggers201 with an agent_trigger. The body is {"kind": "webhook", "name", "message", "conversation"} or {"kind": "event", "connection", "event", "filters", "message", "conversation"} or {"kind": "platform", "type", "filters", "message", "conversation"} (a NoClick event: a type or * pattern, filters dotted paths into its data, conversation a template over the event) or {"kind": "cron", "cron", "tz", "message"} (five fields; tz an IANA name, UTC by default, or "user" on a user's agent) or {"kind": "queue", "queue", "batch_size", "concurrency", "message", "conversation"} (a queue of the agent's own scope by name; batch_size 1 to 25 messages a turn, concurrency 1 to 50 turns at once, conversation only with a batch_size of 1). A taken webhook name is 409 conflict, a 21st trigger 429 quota_exceeded
DELETE/v1/agents/{id}/triggers/{trigger_id}Remove one; an email or call one is 400, changed on its inbox or number
POST/v1/agents/{id}/pause, /resumeStop every trigger waking it (schedules skipped, webhooks 409, events, mail and calls ignored), or start them again, a trigger its failures stopped included; the agent, with paused
GET/v1/channels/{id}/triggersIts schedules, each an agent_trigger with channel_id
POST/v1/channels/{id}/triggers201: {"kind": "cron", "cron", "tz", "message"}, message required (it's posted in the room as the account); 20 per channel
DELETE/v1/channels/{id}/triggers/{trigger_id}Remove a schedule
POST/v1/channels/{id}/subscriptions201 with a subscription: source (an event, webhook or platform definition, without conversation), wake (null, "all" or member agent ids) and, for a webhook or a NoClick event, filters (dotted paths into its JSON body, or the event's data)
GET/v1/channels/{id}/subscriptionsIts subscriptions
GET/v1/channels/{id}/subscriptions/{subscription_id}Read one: its source, who it wakes, its status
DELETE/v1/channels/{id}/subscriptions/{subscription_id}Stop one; a webhook's URL then answers 410 gone

POST /v1/agents takes triggers, a list of the same definitions, checked before anything is made, and POST /v1/channels takes a channel's schedules the same way; PATCH /v1/channels/{id} changes an open channel's mode, max_agent_turns, max_mention_hops and budget_usd (null lifts the budget). An agent_trigger has id, agent_id, channel_id, kind, spec (its definition without kind), status (active, paused while its agent is, or error, with error saying why it hears nothing), url (a webhook's), next_run (a schedule's), last_fired_at and created_at. An agent has paused and trigger_kinds (null for a user reading an account agent) and timezone; a subscription has channel_id, source, wake, filters, status, error, url and last_event_at.

A POST to a webhook's url answers 202 with {"object": "trigger_delivery", "status", "operation_id", "conversation"} for an agent (status is pending while its conversation answers an earlier delivery, failed with an error when the account can't pay), and {"status": "posted", "message_id"} for a channel (200 filtered when its filters don't match). A NoClick-Conversation header picks an agent's conversation. The turn is an agent.message operation whose input has the event and the trigger (id, kind, title). A message a subscription posts has author.type app, its subscription_id and the event as it arrived.

Webhooks and events

These take the webhooks scope, and reading a secret webhooks:reveal, which * doesn't grant. An endpoint lives where the call acts: your account's receives every event, a user's (NoClick-User) only that user's. See Webhooks and events.

MethodPathWhat it does
GET/v1/webhooksList endpoints, newest first (url finds one; user_id)
POST/v1/webhooks201 with a webhook (url: https and public, events: types or * patterns, description); its secret is in this response only. A URL the scope already has is 409 with existing_id, a 17th endpoint 429
GET/v1/webhooks/{id}Read one: events, enabled, status (healthy, failing or disabled), success_rate_24h and last_failure
PATCH/v1/webhooks/{id}Change url, events or description (null clears it), or pause it (enabled: false)
DELETE/v1/webhooks/{id}Delete it; its retries stop
GET/v1/webhooks/{id}/secret{"object": "webhook_secret", "secret", "previous_secret_expires_at"} (webhooks:reveal), recorded in the request log with its key
POST/v1/webhooks/{id}/rotate_secretThe webhook with its new secret; the old one also signs for grace_s (default 86,400, at most 7 days, 0 stops it at once)
GET/v1/webhooks/{id}/deliveriesIts deliveries, newest first (status, event_id)
POST/v1/webhooks/{id}/deliveries/{delivery_id}/redeliverSend one again now: the delivery, with this attempt
POST/v1/webhooks/{id}/testSend a sample of event_type now and wait up to 10 seconds: an unrecorded delivery
GET/v1/eventsEvents, newest first, from the last 30 days (type: types or * patterns, comma-separated; since, inclusive; until; user_id)
GET/v1/events/{id}One event with its deliveries
GET/v1/events/typesThe catalog: name, description and emitted (whether NoClick sends it today)

An event has id, type, user_id, created_at and data. A webhook_delivery has id, webhook_id, event_id, event_type, status (pending, retrying, succeeded or failed), attempts, response_status, response_body (its first 4 KB), request_body, duration_ms, error, next_retry_at and created_at. A delivery is a POST of the event's JSON with a NoClick-Signature: t=<unix seconds>,v1=<hex> header, where the hex is HMAC-SHA256 of <t>.<body> keyed with the endpoint's secret (two v1 values while a rotated secret's grace runs). A 2xx within 10 seconds succeeds; anything else, a redirect included, is retried after a minute, five minutes, half an hour, two hours, five hours, then every 12 hours, for 3 days.

Workspaces

Workspaces take the channels scope and live where the call acts: your account, or the user NoClick-User names. See Workspaces.

MethodPathWhat it does
GET/v1/workspacesList workspaces, newest first (name, status: active or archived, user_id)
POST/v1/workspaces201 with a workspace (name, members, budget_usd_per_month, logo_url: an https URL). A name an active workspace of the scope has is 409 with existing_id; a 101st active one 429
GET/v1/workspaces/{id}Read one, with its members
PATCH/v1/workspaces/{id}Change its name, logo_url or budget_usd_per_month (null clears the last two)
DELETE/v1/workspaces/{id}Delete it with its channels and their messages
POST/v1/workspaces/{id}/archiveClose its channels and stop its subscriptions; everything stays readable and it takes no more changes (409)
POST/v1/workspaces/{id}/members201 with a workspace_member: an agent of its scope ({"agent_id"}) or one of your users ({"user_id"}; a user's own workspace takes only that user)
DELETE/v1/workspaces/{id}/members/{member_id}Remove one, from every one of its channels too
POST/v1/workspaces/{id}/subscriptions201 with a subscription: source and filters as a channel's take them, channels ("all", its open channels now and later, or a list of its open channels' ids) and wake (null, "all" or the workspace's agent ids)
GET/v1/workspaces/{id}/subscriptionsIts subscriptions
GET/v1/workspaces/{id}/subscriptions/{subscription_id}Read one
DELETE/v1/workspaces/{id}/subscriptions/{subscription_id}Stop one; a webhook's URL then answers 410 gone

A workspace has id, name, logo_url, user_id, status, budget_usd_per_month, spent_usd (this UTC month), channel_count (its open channels), members and created_at; a workspace_member has id, type (agent or user), agent_id, user_id and name. POST /v1/channels takes workspace_id (its agent and user members must be the workspace's, 400 otherwise, and its name unique among the workspace's open channels, 409 with existing_id); a workspace channel's members and a post's as may be {"user_id": "<your id>"}; GET /v1/channels filters by workspace_id and name; a channel has workspace_id. A workspace subscription's webhook answers {"status": "posted", "message_ids"}, one message per open channel it posts in.

Volumes and buckets

Volumes take the volumes scope and buckets storage. Each lives where the call acts: your account, or the user NoClick-User names. A user reaches its own volumes and your account's, and its own buckets. See Volumes and Storage.

MethodPathWhat it does
GET/v1/volumesList volumes, newest first (name; user_id: by default the caller's user's and your account's)
POST/v1/volumes201 with a volume (name). A taken name is 409 with existing_id; region answers 409 not_available (volumes aren't placed by region yet)
GET/v1/volumes/{id}Read one: size_bytes, file_count (as last measured, measured_at) and mounted_by
DELETE/v1/volumes/{id}Delete it and its files; 409 while an agent or a running sandbox mounts it
GET/v1/volumes/{id}/files?path=The file's bytes (up to 100 MB), or with view=list a folder's entries in path order (recursive, limit up to 1,000, cursor: the last path seen)
PUT/v1/volumes/{id}/files?path=Store the request body at path (up to 100 MB), making folders as needed
DELETE/v1/volumes/{id}/files?path=Remove a file, or a folder (recursive=true when it isn't empty, else 409)
POST/v1/volumes/{id}/copy{src, dst}: dst becomes a copy of the file or folder
GET/v1/bucketsList buckets, newest first (name, user_id)
POST/v1/buckets201 with a bucket (name, public, region: a location hint, fixed). A taken name is 409 with existing_id
GET/v1/buckets/{id}Read one: size_bytes, object_count (as last measured), public_base_url, used_by
PATCH/v1/buckets/{id}{public}: open it, or close it and stop every public URL at once
DELETE/v1/buckets/{id}Delete it; 409 while it holds files (unless force=true) or an agent has it in its tools
GET/v1/buckets/{id}/objectsList files in key order (prefix, delimiter, limit up to 1,000, cursor), or with key read one's bytes (view=metadata for its details)
PUT/v1/buckets/{id}/objects?key=Store the request body at key, up to 100 MB (content_type, metadata: a JSON object of strings, up to 2 KB)
DELETE/v1/buckets/{id}/objects?key=Remove a file
POST/v1/buckets/{id}/signed_urls{key, method, expires_in, content_type}: a link that reads (GET) or uploads (PUT, up to 5 GB) one key, 60 seconds to 7 days

A volume_entry has path (from the volume's root), type (file or directory), size and updated_at; a bucket_object has key, size, content_type, etag, metadata, updated_at and folder. A public bucket serves each file at public_base_url + key, which redirects to a short-lived signed link. POST /v1/sandboxes, POST /v1/agents and PATCH /v1/agents/{id} take volumes ({"/data": "<volume id or name>"}), and both carry it. A bucket is one of an agent's tools as bucket:<id> (all five operations) or bucket:<id>.<operation> (objects, head, put, signed_url, remove), bound as {"tool": "bucket:<id>.put", "bind": {"prefix": "users/{{user.id}}/"}}.

An agent's per-conversation files take the agents scope, in the caller's user's thread (else the agent's own user's, else your account's):

MethodPathWhat it does
GET/v1/agents/{id}/workspace?conversation=The workspace's files, each with a download url good for an hour
GET/v1/agents/{id}/workspace/files?conversation=&path=Read a file
PUT/v1/agents/{id}/workspace/files?conversation=&path=Write a file (up to 100 MB)
GET/v1/agents/{id}/workspace/url?conversation=&path=A link to a file (expires_in_s, 60 to 86,400)

Secrets, dicts and queues

Each takes its own scope (secrets, dicts, queues) and lives where the call acts: your account, or the user NoClick-User names, its name (1 to 64 letters, digits and . _ -) unique there; a taken name is 409 with existing_id. A user's call reads its own dicts and queues, and its own secrets and your account's. Dicts and queues keep their data in us-east (region takes no other yet: another, or a default region elsewhere, is 400 naming the regions that do). See Secrets, Dicts and Queues.

MethodPathWhat it does
GET/v1/secretsList secrets, with their key names (name; user_id: by default the caller's user's and your account's)
POST/v1/secrets201 with a secret: name, values (up to 100 keys, each an environment variable name, not PATH, HOME or one starting LD_, NC_ or NOCLICK_; each value up to 32,768 characters)
GET/v1/secrets/{id}Read one: its key names and who uses it (used_by), never a value
PATCH/v1/secrets/{id}{values, replace}: merged into what's there (a null removes a key), or with replace: true in place of it; the same values write nothing
DELETE/v1/secrets/{id}Delete it; 409 while an agent lists it
GET/v1/dictsList dicts (name, user_id)
POST/v1/dicts201 with a dict: name, region
GET/v1/dicts/{id}Read one: key_count, size_bytes
DELETE/v1/dicts/{id}Delete it and its keys
GET/v1/dicts/{id}/itemsKeys in byte order with their values and expiry (prefix, limit up to 1,000, cursor), or only the keys with view=keys
POST/v1/dicts/{id}/items{items: [{key, value, ttl_s}]}: up to 1,000 keys, all or none
GET/v1/dicts/{id}/items/{key}One key (percent-encoded, so any string, / too); missing or expired is 404 with key
PUT/v1/dicts/{id}/items/{key}{value, ttl_s, if_absent}: set it (no ttl_s clears an expiry); if_absent only where it's missing or expired, atomically, answering written
DELETE/v1/dicts/{id}/items/{key}Remove it, answering the key as it was; missing is 404 with key
POST/v1/dicts/{id}/items/{key}/increment{by, ttl_s}: add to an integer atomically (a missing key starts at 0, ttl_s set only then); anything but an integer is 400
POST/v1/dicts/{id}/clearRemove every key, answering how many
GET/v1/queuesList queues: depth, in_flight and dead_letter_count for each
POST/v1/queues201 with a queue: name, max_attempts (1 to 100, 5 by default), region
GET/v1/queues/{id}Read one, with used_by (the agents an OnQueue trigger hands its messages to)
PATCH/v1/queues/{id}{max_attempts}; messages already on it follow it
DELETE/v1/queues/{id}Delete it and its messages; 409 while a live agent's OnQueue trigger reads it
POST/v1/queues/{id}/messages{messages: [{body, delay_s, dedupe_key}]}: up to 100, all or none (body up to 256 KB of JSON, delay_s up to 7 days, a dedupe_key repeated within 24 hours adds nothing and answers the first message's id)
GET/v1/queues/{id}/messagesTake up to limit (10) messages, each leased for lease_s (1 second to 12 hours) and carrying its lease_id, waiting up to wait_s (60) for one; view=peek reads the oldest waiting and in flight instead
GET/v1/queues/{id}/messages/{message_id}Read one message
POST/v1/queues/{id}/messages/{message_id}/ack{lease_id}: done, deleted (204); once the lease ran out, 409
POST/v1/queues/{id}/messages/{message_id}/nack{lease_id, delay_s, error}: a failed attempt, back after delay_s, or a dead letter at max_attempts
POST/v1/queues/{id}/messages/{message_id}/release{lease_id, delay_s}: back without spending an attempt
GET/v1/queues/{id}/dead_lettersDead letters, newest first, with their last_error
POST/v1/queues/{id}/dead_letters/{message_id}/retryBack on the queue, its attempts reset
DELETE/v1/queues/{id}/dead_letters/{message_id}Drop a dead letter
GET/v1/queues/{id}/depthdepth and in_flight every 15 minutes over the last hours (up to 168)
POST/v1/queues/{id}/clearDrop waiting messages and dead letters (in flight ones finish), answering how many

A queue_message has id, queue_id, body, status (waiting, delayed, in_flight or dead), attempts (deliveries, this one included), enqueued_at, visible_at (while delayed), lease_expires_at, last_error, dead_lettered_at, and on a take its lease_id. Waiting messages and dead letters are kept 14 days. POST /v1/sandboxes, POST /v1/agents and PATCH /v1/agents/{id} take secret_ids (their keys in the environment, a later one's winning; an agent's never a model provider's key), and both carry it. POST /v1/dicts/{id}/export and POST /v1/queues/{id}/export start a dict.export or queue.export operation (see Export).

Regions

Where a resource runs or keeps its data, in NoClick's own names. See Regions.

MethodPathWhat it does
GET/v1/regionsEvery region: name (us-east, us-west, eu, ap), label, location, kinds (what it places: sandbox, agent, bucket, database, project and machine everywhere, browser but in us-west, and in us-east dict and queue) and default (your account's default). kind keeps those placing it; another kind answers 400. Takes the sandboxes, agents, storage, databases, apps, machines, browsers, dicts, queues or volumes scope

POST /v1/sandboxes and POST /v1/agents take region, and a sandbox and an agent carry it (null: wherever NoClick had capacity). A missing region takes a child agent's parent's, else your account's default. An agent's region never changes: PATCH /v1/agents/{id} with another answers 409 conflict with region. Your account's default is set in the console.

Hosted MCP servers

A hosted MCP server serves a toolkit's tools at one URL; these take the mcp_servers scope and live where the call acts: your account, or the user NoClick-User names, whose connections its calls act in. See Hosted MCP servers.

MethodPathWhat it does
GET/v1/mcp_serversList servers, newest first (name, user_id)
POST/v1/mcp_servers201 with an mcp_server (name, tools: what a toolkit takes, at most 64 tools). A name the scope has is 409 with existing_id; a 101st server of the account 429
GET/v1/mcp_servers/{id}Read one: its url, tools and tool_count
PATCH/v1/mcp_servers/{id}Change its tools or name; the URL stays
DELETE/v1/mcp_servers/{id}Delete it; its URL stops
POST/v1/mcp_servers/{id}/rotateA new URL; the old one stops at once
GET/v1/mcp_servers/{id}/clientsThe MCP clients that have connected, most recently seen first

An mcp_server has id, name, url, tools (as sent), tool_count (what it serves right now: an entry whose connection isn't connected serves nothing), user_id, created_at and last_client_at. An mcp_client has name and version as the client gave them, first_seen_at, last_seen_at and tool_count (what it saw when it last listed). The url speaks MCP over Streamable HTTP (stateless, JSON replies) and needs no key: initialize answers with an Mcp-Session-Id the client sends back, tools/list serves the tools, and tools/call runs one it serves as a toolkit call, as the key that made the server, recorded as a tool call with source mcp_server and its mcp_server_id. GET /v1/tool_calls?source=mcp_server lists them.

Inboxes, phone numbers and brand

These take the inbox, phone and brand scopes. See Inboxes, Phone numbers and Brand.

MethodPathWhat it does
POST/v1/inboxes201 with an inbox (local_part, name, agent_id, senders: anyone, users or owner)
PATCH/v1/inboxes/{id}Change its agent_id (null: none) or senders, from the next message
GET/v1/inboxes/{id}/messagesReceived mail, oldest first: after a message id, since a time (with a timezone), wait_s to long-poll. Each message has from (its From address when a DKIM signature aligned with it verified, else the envelope sender), from_source (header or envelope), envelope_from, authenticated, conversation (its email thread's, inbox:<inbox id>:<thread id>), agent_operation_id (the turn it woke) and agent_error (why it woke none, or why that turn failed)
POST/v1/inboxes/{id}/wait_forAn inbox.wait_for operation for the first matching message (sender, subject_contains, timeout_s); with since, mail already received at or after that time matches too
GET/v1/phone_numbers/availableNumbers for sale (country, type: local, mobile, toll_free, national or shared_cost; area_code, locality, region, contains, starts_with, ends_with, features: voice or voice_sms), each with monthly_usd, upfront_usd, capabilities, requirements (needs: none, address, documents or steps, and items, each with its id, kind, name, description, example and values) and texting (registration, unavailable or none)
GET/v1/phone_numbers/coverageThe countries numbers are in stock in, each with its types, texting_types, documents_types (the kinds bought with documents the carrier reviews first) and steps_types (the kinds that ask a step that can't be taken here, which can't be bought here yet); right after a deploy, while that's still being checked, 503 not_available with Retry-After
GET/v1/phone_numbers/placesWhere in a country numbers of a type are for sale: cities, or area codes in the US and Canada (kind, value, region)
POST/v1/phone_numbersBuy one (number with its country and type, or country, type and area_code or locality for the first one there; max_monthly_usd, max_upfront_usd, address where a requirement's kind is address or contact, requirements where its country asks for documents (each textual and document requirement's id to its text or an uploaded document's id), name, record_calls): 201 with the number, or 202 with a phone_number_order while the carrier reviews the documents, charged nothing until it completes; a number whose requirements.needs is steps answers 409 not_available
POST/v1/phone_numbers/documents201: upload a document for one requirement (country, type, requirement_id, filename, file: a PDF, PNG or JPEG up to 20 MB, base64); the carrier holds it and you name its id in a purchase's requirements within 25 minutes (one a purchase named can be named again)
GET/v1/phone_numbers/orders/{id}A number order in the carrier's review: status in_review, rejected (reason, the carrier's words, and deadline), completed (phone_number_id: the number, now charged) or failed (nothing charged), and while it's open the requirements a resubmission answers
POST/v1/phone_numbers/orders/{id}/resubmitFile corrected answers (address, requirements) for an order the carrier sent back; it's in_review again. Any other order answers 409
PATCH/v1/phone_numbers/{id}Rename it (name) or record its calls (record_calls), from the next call
POST/v1/phone_numbers/{id}/answerRoute its calls to an agent (agent_id) and set afresh how they run: transfer_to (E.164: the agent's transfer_call) and callers (anyone, users or owner). texts answers 409 not_available
GET/v1/phone_numbers/{id}/callsThe calls it answered and placed, newest first (direction, since, limit, cursor)
GET/v1/phone_numbers/connectableThe numbers in your phone provider account (connection, by name or id), each with label, routed_elsewhere, and phone_number_id and agent_id when it's already connected to NoClick
POST/v1/phone_numbers/connect201: connect one you keep with your provider (number, connection, name, record_calls); its calls stay where they go until it's answered
POST/v1/phone_numbers/{id}/disconnectHand a connected number back to its provider account, its calls going where they went before

A number has source (bought or connected), country, type, connection (a connected number's), agent_id, transfer_to, callers, record_calls and monthly_usd (null for a connected number, which its provider bills). A call has direction, from, to, status (calling, then completed, not_connected, refused, failed or lost), user_id (the user it ran as), transcript, transfer (to, status, seconds) and recording_url (a signed link valid for an hour, when the number records its calls).

MethodPathWhat it does
GET/v1/brandThe account's brand (is_default: true while none is set)
PUT/v1/brandReplace it: name, logo_url, color, support_url, email_from_name and oauth_apps ({provider: secret id}, an account secret holding CLIENT_ID and CLIENT_SECRET); a field left out is cleared
PATCH/v1/brandChange only what's sent (null clears a field); oauth_apps changes the providers it names (null for one removes it), oauth_apps: null removes them all
DELETE/v1/brandBack to NoClick's defaults

oauth_redirect_urls has, for every provider you can bring an OAuth app for, the redirect URI to register with it. An account key changes the brand; a user's key only reads it.

AI models

/v1/ai is OpenAI-compatible: point an OpenAI client at https://api.noclick.com/v1/ai with your key. A model call takes the ai scope; model: "agent:<name>" (or agent:<id>) talks to one of your agents and takes agents. See AI models.

MethodPathWhat it does
POST/v1/ai/chat/completionsOpenAI Chat Completions: model (an openrouter/ id, agent:<name>, or omitted for the default), messages, stream, tools, tool_choice, response_format, max_tokens, temperature. An agent's conversation rides metadata.conversation
POST/v1/ai/responsesOpenAI Responses: input (a string or items), instructions, function tools, text.format, stream, previous_response_id; for an agent, conversation
POST/v1/ai/embeddingsOpenAI Embeddings for 1 to 2,048 texts (dimensions)
GET/v1/ai/modelsThe models and your agents in OpenAI's list shape, each with kind and its price in US dollars (input_usd_per_token, output_usd_per_token, usd_per_minute, usd_per_minute_with_speakers); kind is chat, embedding, image, transcription or agent
POST/v1/ai/imagesOne image: prompt, model, size (WIDTHxHEIGHT) -> {url, expires_at, width, height, cost_usd}
POST/v1/ai/transcriptionsJSON {url, model?, language?, speakers?, background?}, or the audio's bytes with the rest in the query (speakers=true or names comma-separated) -> {text, language, duration_s, segments, cost_usd}. speakers (each segment's speaker) and background (202 with an ai.transcription operation whose result is the transcript) run on noclick/transcribe-1, which is in invite-only beta: without access it answers 409 not_available

Every answer carries cost_usd. Errors on these routes use OpenAI's shape, {"error": {"message", "type", "param", "code"}}, with NoClick's type in code and next_step beside it. A request that doesn't stream must answer within 90 seconds or fails with 504 request_timeout (sent with x-should-retry: false); a stream starts at once and sends a : keepalive comment every 15 seconds while it waits.

Workflows

Workflows, built from a description by NoClick's builder and run from your code; they take the workflows scope. See Workflows.

MethodPathWhat it does
GET/v1/workflowsYour account's workflows, most recently changed first: q (a prefix of the name), name (exactly)
POST/v1/workflowsCreate one: {name, instructions?, description?} -> 201; with instructions its build is a workflow.build operation
GET/v1/workflows/{id}Read one: its steps, inputs (a JSON schema of what its trigger takes) and app_url
DELETE/v1/workflows/{id}Move it to the trash (restorable in the app for 30 days)
POST/v1/workflows/{id}/buildsChange it from a description: {instructions} -> 202 with a workflow.build operation, whose result is {summary, steps}; 409 while another build runs
POST/v1/workflows/{id}/builds/{operation_id}/answerAnswer what a build asks (its operation's question): {values, message?, skip?}
POST/v1/workflows/{id}/runsRun it: {inputs} -> 202 with a workflow.run operation, whose result is the run (outputs by step name)
GET/v1/workflows/{id}/runsIts runs, newest first, however they started: status (running, succeeded, failed), user_id
PUT/v1/workflows/{id}/endpointTurn its HTTP endpoint on: {public?} -> {url, method, public}
POST/v1/workflows/{id}/endpointThe endpoint: the inputs as the body -> the run's outputs, or 202 with its operation after 30 seconds
GET/v1/workflows/{id}/endpoint/runs/{operation_id}A run the endpoint answered 202 for, answered the same way once it's done

A public endpoint also takes a publishable key that lists it.

Objects and lists

Objects are JSON with an object type and an id:

{"object": "user", "id": "user_42", "name": "Ana Lima", "status": "active"}

Lists wrap them with a cursor. Pass next_cursor back as cursor for the next page; null means you've reached the end:

{"object": "list", "data": [...], "next_cursor": "eyJ0IjoiMjAyNi0..."}

Errors

A failed request answers with its HTTP status and an error object:

{
  "error": {
    "type": "needs_connection",
    "message": "The 'gmail' connection has no Gmail account connected for this user yet.",
    "next_step": "Send the user connect_url; retry once the connect operation succeeds.",
    "connection": "gmail",
    "integration": "gmail",
    "connect_url": "https://noclick.com/credential/provide/...",
    "operation_id": "3b9e..."
  }
}

type is stable; the Error reference lists each one with its status. A path no route serves answers 404 not_found, and a method its route doesn't take 405 invalid_request (with Allow). When a key's request rate is over its limit, the 429 carries Retry-After; a daily send cap says when it frees in retry_after instead.

Every response carries an X-Request-Id. Quote it when you contact support.

Make writes safe to retry

Send an Idempotency-Key header with every POST. Repeating a request with the same key and the same body returns the first response instead of running again, for 24 hours. A replayed response carries Idempotent-Replayed: true. A request refused with a 4xx isn't remembered, so a repeat runs again, and a repeat that arrives while the first request is still running gets a 409: send it again shortly.

Long-running work

Work that waits returns an operation object:

{"object": "operation", "id": "3b9e...", "kind": "credential.connect", "status": "awaiting_human", "result": null}

Long-poll it until it's final. Each call waits up to the timeout_s you pass (up to about a minute) and returns the operation as it stands:

curl "https://api.noclick.com/v1/operations/3b9e.../wait?timeout_s=50" \
  -H "Authorization: Bearer $NOCLICK_API_KEY"

GET /v1/operations/{id} reads it without waiting, and POST /v1/operations/{id}/cancel cancels it.

From a browser

The API answers cross-origin requests from any origin. Use a session token or a publishable key, never your secret key, and send it as a bearer; the API never reads cookies.

Keys and the CLI

A publishable key (nk_pub_) is made on the console's API keys page (POST /v1/keys with type: "publishable", public_endpoints as {kind, id}, per_visitor_limit_per_minute and allowed_origins; GET /v1/public_endpoints lists what may go in it, and PATCH /v1/keys/{id} changes the three): it calls only the public endpoints it lists, such as a shared agent's chat, from those origins, within its limit per visitor, and every other route answers it 403. A key's view says its type (secret or publishable) and shows a publishable key in full, as publishable_key. The CLI signs in with routes of its own; the first two need no key:

MethodPathWhat it does
POST/v1/cli/loginsStart a sign-in: a device code, and the link and short code a person approves in the console. Limited per address
POST/v1/cli/logins/tokenPoll with the device code: pending with the interval_s to wait (slow_down, with a longer one, when polled sooner), then approved with the new key, once; 403 when denied, 410 once the code expired or handed its key over
GET/v1/cli/logins/{code}Signed in as the account owner: the sign-in waiting for this code, with its machine and the address it started from
POST/v1/cli/logins/{code}/answerSigned in as the account owner: {"approved": true} lets the next poll take a key named after the machine; false denies it
DELETE/v1/keys/currentRevoke the secret key that makes the call (noclick logout)

Databases, repos, apps, domains and browsers

These follow every convention above. A resource lives at /v1/<plural> and /v1/<plural>/{id}; GET /v1/<plural>?name= finds one by name, which is unique for its user; user_id on a list reads across users (all, none, or one user's id), while the NoClick-User header acts as one. Actions are POST to /{id}/<verb>, and every write takes an Idempotency-Key.

Long work answers 202 with an operation of its own kind (project.deploy, domain.buy, database.restore, workflow.run, user.export, ...), which you wait on as above; /wait returns its result once it has succeeded. A purchase takes a ceiling (max_price_usd, max_monthly_usd) and answers 402 price_exceeded above it, a call your account can't pay for answers 402 payment_required, and a purchase your policy holds waits in awaiting_human until a person approves it. Every purchase is charged to your account; NoClick never bills your users. Secret values are write-only, and tokens and credentials come back short-lived.

Databases

MethodPathWhat it does
GET/v1/databasesList databases (name, engine)
POST/v1/databasesCreate one: name, engine (sqlite by default, or postgres)
GET/v1/databases/{id}Read one: engine, status, size, table count
DELETE/v1/databases/{id}Delete it and its data (409 while it has branches)
POST/v1/databases/{id}/queryRun one statement (sql, params, read_only), or several in one transaction (statements, each {sql, params})
GET/v1/databases/{id}/migrationsThe migrations applied, in order
POST/v1/databases/{id}/migrationsApply the migrations not applied yet
GET/v1/databases/{id}/schemaIts tables, their columns and row counts
POST/v1/databases/{id}/restoreRestore it to a moment (at): a database.restore operation
GET/v1/databases/{id}/branchesThe databases branched from it
POST/v1/databases/{id}/branchesBranch a copy (name, at): a database.branch operation; into Postgres (engine) is in invite-only beta
POST/v1/databases/{id}/connection_urlA short-lived connection URL (Postgres only)
POST/v1/databases/{id}/exportExport it as SQL: a database.export operation

In an agent's tools, a database is database:<id> (query, execute and schema), or database:<id>.query for one operation.

Repos

MethodPathWhat it does
GET/v1/reposList repos
POST/v1/reposCreate one
GET/v1/repos/{id}Read one: default branch, size, last commit
DELETE/v1/repos/{id}Delete it
POST/v1/repos/{id}/clone_urlA git URL with a short-lived token inside (write, ttl_s) for plain git over HTTPS
GET/v1/repos/{id}/commitsIts commits, newest first (branch)
POST/v1/repos/{id}/commitsCommit files in one commit (content, or null to delete)
GET/v1/repos/{id}/filesList a folder (prefix, ref, recursive), or read a file's bytes (path)
GET/v1/repos/{id}/branchesIts branches and their heads
POST/v1/repos/{id}/exportA git bundle to take with you, or with {to: "github", connection, owner, name?, private?} a copy in a new GitHub repository through a GitHub connection: a repo.export or repo.export_to_github operation

Apps

An app is a project: a repo deployed to a URL, with what the running app needs.

MethodPathWhat it does
GET/v1/projectsList apps
POST/v1/projectsCreate one from a repo (runtime edge or container; framework detected unless given; budget_usd_per_month, 10 when omitted; deploy_on_push)
GET/v1/projects/{id}Read one: URL, repo, production branch and deployment, domains
PATCH/v1/projects/{id}Change its repo, framework, production branch, budget_usd_per_month (null removes it), deploy_on_push (off, production, all) or check_paths (what a promote checks first)
DELETE/v1/projects/{id}Move it to the trash, offline within seconds and restorable for 30 days (delete_data also moves its repo and the databases and buckets bound to it)
GET/v1/projects/{id}/deploymentsIts deployments, newest first (branch)
POST/v1/projects/{id}/deploymentsBuild and deploy a branch (branch, commit): a project.deploy operation
GET/v1/deployments/{id}Read a deployment: branch, commit, status, URL, started_by (deploy or push)
GET/v1/deployments/{id}/logsIts build output or what it printed while serving (kind)
GET/v1/deployments/{id}/requestsHow its requests went (since): by status, the error rate, the slowest routes
POST/v1/deployments/{id}/checkLoad paths of it in a browser: a deployment.check operation giving each one's status, load time, console errors and screenshot
POST/v1/deployments/{id}/cancelStop a build
POST/v1/deployments/{id}/promotePut it live (a rollback promotes an earlier one): a deployment.promote operation, checked first when the app has check_paths (force skips it) and undoable with its data
GET/v1/projects/{id}/bindingsWhat it reads by name: databases, buckets, dicts, queues, secrets, connections and agents
POST/v1/projects/{id}/bindingsBind a resource under a name
DELETE/v1/projects/{id}/bindings/{name}Unbind it
GET/v1/projects/{id}/secretsIts secrets' names and when each was set (never the values)
PUT/v1/projects/{id}/secrets/{name}Set a secret
DELETE/v1/projects/{id}/secrets/{name}Delete a secret
GET/v1/projects/{id}/schedulesIts schedules
POST/v1/projects/{id}/schedulesCall a path on a cron schedule (cron, tz, path, name); the same name again redefines it
GET/v1/schedules/{id}Read a schedule: next and last run, last_status and last_error, status
DELETE/v1/schedules/{id}Delete it
POST/v1/schedules/{id}/pause, /resume, /runPause it, resume it, or run it once now
GET/v1/projects/{id}/domainsThe domains that serve it
POST/v1/projects/{id}/domainsServe it at a domain (connecting the domain if needed): the domain
DELETE/v1/projects/{id}/domains/{name}Stop serving it there; the domain is kept
GET/v1/projects/{id}/logsWhat it printed while serving (since, level, deployment_id, limit)
GET/v1/projects/{id}/usageWhat it spent this month, against its budget: requests, cpu_ms, usd, budget_usd, limited_by and by_type
POST/v1/projects/{id}/exportIts code, settings, secret names, bindings and bound data: a project.export operation

A deployment's credential (NOCLICK_DEPLOYMENT_TOKEN in its environment) is a bearer that acts as a key of the app's user, attributed to the app: GET /v1/projects/current reads the app, its bindings resolve, and every other route a key of that user reaches answers it, except: what the gate keeps for a person (403 permission_denied: keys, grants, sessions, caps and budgets, policy, approvals' answers, raw tokens, secrets' values, accepting terms), what files a person's details with a carrier (documents, a port, a texting registration), turning on a domain's auto-renew, and the areas beyond the app (quotes, humans, users, tool_calls, approvals, trash, policy). A purchase (POST /v1/phone_numbers, POST /v1/domains) charges what it costs now within what's left of the app's budget (402 spend_cap_exceeded with remaining_usd, else), is the app's, renews within its budget, and one in a person's name answers 402 approval_required until they approve it. Calls, texts, automations, hosted MCP servers, channels, machines and cards count toward the app's budget and wait on it; of workflows and workspaces it reaches only what its code made. It reads its own user's events, webhooks, usage and the attachments its code uploaded, and its own requests and operations, never changing them. It acts for the app's user alone: NoClick-User, and a user_id naming anyone else, answer 403. On a resource its own code didn't make it reads and uses (its data, a message, a run) but never changes or deletes it (403), and on one bound to the app it may also clear its data; another app, and what another app's code made (by id, or named in a body), answer 403 and 404; an operation it didn't start, 404. POST /v1/agents/{id}/messages (and /ask, /a2a) starts turns in the app's own threads, a conversation it sends being kept as app:<app id>:<key> (from a preview, app-preview:<app id>:<key>, its turn answered by a simulated world), and is refused (403) for an agent holding the noclick tool; POST /v1/agents and PATCH refuse giving an agent the noclick or agents tools, a person other than the owner, or a platform trigger. Once the app's budget or its user's cap is used up, a paid call answers 402 spend_cap_exceeded (scope project or user), and once your balance is, 402 payment_required, each naming the app in project and project_id. GET /v1/usage?group_by=project splits spend by app, and GET /v1/usage/limits adds projects.

App services

MethodPathWhat it does
POST/v1/projects/{id}/emailTurn on transactional email, from your domain or the default sender
GET/v1/projects/{id}/emailThe sender and the DNS records it waits on
POST/v1/projects/{id}/email/sendSend one email
POST/v1/projects/{id}/email/codesSend a one-time sign-in code, on a lane of its own
POST/v1/projects/{id}/email/broadcastsBroadcast to up to 50,000 opted-in addresses: a project.email.broadcast operation
GET/v1/projects/{id}/email/messagesEmails sent, with delivery, opens and bounces
POST/v1/projects/{id}/email/resubscribeLift an address's broadcast unsubscribe after a confirmed opt-in
POST/v1/projects/{id}/paymentsTurn on payments in a country, in test mode, with a link to claim the account
GET/v1/projects/{id}/paymentsTest, claimed (and its step), live or disconnected
POST/v1/projects/{id}/sign_inCommit sign-in code into the app's repo (providers): a project.sign_in.enable operation
POST/v1/projects/{id}/sign_in/upgradeMove the sign-in code to the version NoClick ships now: a project.sign_in.upgrade operation
GET/v1/projects/{id}/sign_inThe providers on, its tables, library version and what it still needs

Payments aren't switched on yet: until they are, POST /v1/projects/{id}/payments answers 409 not_available.

Domains

MethodPathWhat it does
GET/v1/domains/searchNames for sale, with prices for the TLD's shortest term, its terms and any trademark claims_notice (q, tlds)
GET/v1/domains/agreementThe domain registration agreement every registrant accepts: its url and version
GET/v1/domainsList your domains
POST/v1/domainsBuy one, charged to your account (name, max_price_usd, years (the TLD's minimum when omitted), registrant with its agreement_accepted_at, accept_claims; with NoClick-User, registrant is required): a domain.buy operation
POST/v1/domains/connectConnect a domain you own elsewhere; it waits on the DNS records it returns
GET/v1/domains/transfer_quoteWhether a domain registered elsewhere can move to NoClick now, and its price (name)
POST/v1/domains/transfer_inMove one here from its registrar, charged to your account like a purchase (name, auth_code, max_price_usd, registrant): a domain.transfer_in operation
GET/v1/domains/{id}Read one: status, expiry, records, what it serves
PATCH/v1/domains/{id}Turn auto-renew on or off (auto_renew), or update the registrant
DELETE/v1/domains/{id}Disconnect a connected domain (a bought one lapses or transfers out)
POST/v1/domains/{id}/verifyCheck its DNS again
GET/v1/domains/{id}/dnsA bought domain's DNS records
POST/v1/domains/{id}/dnsAdd a record (type, name, value)
DELETE/v1/domains/{id}/dnsRemove a record
POST/v1/domains/{id}/renewRenew it (years, the TLD's minimum when omitted; max_price_usd): a domain.renew operation
POST/v1/domains/{id}/use_noclick_dnsMove a transferred domain's DNS to NoClick, answering the records it copied
POST/v1/domains/{id}/transfer_outUnlock it and get its transfer code

Browsers

MethodPathWhat it does
GET/v1/browser_profilesList profiles
POST/v1/browser_profilesCreate one: a person's saved logins (name, region)
GET/v1/browser_profiles/{id}Read one, with the sites it's signed in to
DELETE/v1/browser_profiles/{id}Delete it and its logins
POST/v1/browser_profiles/{id}/loginSend the person a live browser to sign in to a site once: a browser.login operation
DELETE/v1/browser_profiles/{id}/sites/{domain}Forget one site's login (a short browser clears it; returns when done)
GET/v1/browsersList browsers
POST/v1/browsersStart a browser (profile_id, timeout_s, region, solve_captchas, human)
GET/v1/browsers/{id}Read one: its URL, status and live view
DELETE/v1/browsers/{id}Close it
POST/v1/browsers/{id}/gotoOpen a URL
POST/v1/browsers/{id}/actDo a task in plain words (max_steps, files): a browser.act operation, 202
POST/v1/browsers/{id}/extractRead structured data off the page (schema)
GET/v1/browsers/{id}/screenshotA PNG of the page
GET/v1/browsers/{id}/contentThe page as Markdown, text or HTML

Cards

These take the cards scope. See Cards.

MethodPathWhat it does
GET/v1/cards/programWhere your company's card issuing stands: status, requirements_due, onboarding_url
GET/v1/cards/merchant_categoriesEvery merchant category name with the codes it covers
GET/v1/cardsList cards, newest first (status, agent_id, user_id)
POST/v1/cardsIssue a virtual card with a limit, merchants and an agent
GET/v1/cards/{id}Read one: limit, spent, status
PATCH/v1/cards/{id}Change its limit, merchants, approval threshold, agent or name
POST/v1/cards/{id}/purchasesApprove before paying: a card.purchase operation whose result is a number for exactly that purchase
GET/v1/cards/{id}/transactionsIts charges, declines and refunds, newest first
GET/v1/cards/{id}/transactions/{transaction_id}Read one
POST/v1/cards/{id}/transactions/{transaction_id}/disputeDispute a settled charge
POST/v1/cards/{id}/revealA page that shows its number and CVC, briefly, to whoever opens it
POST/v1/cards/{id}/freeze, /unfreeze, /closeFreeze it, unfreeze it, or close it for good
GET/v1/cards/fundingThe balance cards spend from, and where to top it up

POST /v1/agents and PATCH /v1/agents/{id} take card_id, the card the agent holds and pays with (null takes it back; giving one takes the cards scope), and an agent carries it.

Cards are in invite-only beta. Without access, GET /v1/cards/program, GET /v1/cards/funding, POST /v1/cards and an agent's card_id answer 409 not_available, with no program_status; the other routes answer as for an account with no cards, and merchant_categories lists the categories. With access, issuing a card or reading funding before your company's program is active answers not_available with program_status and requirements_due.

Invite-only beta routes

They follow the same conventions.

Phone numbers

Porting a number in, beside the phone number routes that work today. A ported number's source is ported.

MethodPathWhat it does
POST/v1/phone_numbers/port_inPort one in from its carrier (number, account_holder, service_address, account_number, pin, bill, requested_at, max_monthly_usd, name): a phone.port_in operation, awaiting_human until the account holder signs
POST/v1/phone_numbers/port_in/{operation_id}/retrySend a rejected port again with corrected details: a new phone.port_in operation

Machines

MethodPathWhat it does
GET/v1/machines/sizesThe sizes: name, cpu (vCPUs), memory_gb, disk_gb, monthly_usd, working_conversations
GET/v1/machinesList machines (name, user_id)
POST/v1/machinesStart one: name, size, region, volumes, network, max_monthly_usd
GET/v1/machines/{id}Read one: status (and status_reason), size, price, network, ports and how many agents run on it
PATCH/v1/machines/{id}name, size, volumes, network or max_monthly_usd; a larger size or new volumes restart it once running turns end, a smaller size moves it to a new host
DELETE/v1/machines/{id}Delete it with its disk (409 while agents run on it)
POST/v1/machines/{id}/execRun a command on the machine itself (background for a machine.exec operation)
PUT / GET/v1/machines/{id}/files?path=Write or read a file on its disk
POST/v1/machines/{id}/portsServe a port at an HTTPS URL (port, public); a private one asks for a key with the machines scope
DELETE/v1/machines/{id}/ports/{port}Stop serving it
GET/v1/machines/{id}/agentsThe agents that run on it
POST/v1/machines/{id}/restartRestart it: a machine.restart operation
POST/v1/machines/{id}/stop, /startStop it (its agents take no turns) or start it again

Every create of a resource that holds data or hosts code (databases, apps, machines; buckets, dicts and queues already do) takes region, fixed for its life. An agent takes machine_id to run on a machine.

Agents

Beside the agent routes that work today: a connection from another scope, another user's or the account's, is connection:<id> (or connection:<id>.<operation>), accepted only on a key acting for the whole account; POST /v1/toolkits/call then takes the toolkit's tools too, so the tool's name resolves to that connection.

Export

MethodPathWhat it does
POST/v1/users/{id}/exportEverything a user has, in portable formats: a user.export operation; {delete_after: true} deletes the user once it's stored
POST/v1/buckets/{id}/export, /v1/volumes/{id}/export, /v1/dicts/{id}/export, /v1/queues/{id}/exportOne resource in its own format: a bucket.export, volume.export, dict.export or queue.export operation

An export is an operation: read it (and its bundle, once it succeeded) with GET /v1/operations/{id} or /wait. Its result is an export: url (the zip, valid until expires_at), size_bytes, contents (each {kind, name, detail}) and remaining_dependencies. A second export of the same user or resource while one runs answers 409 with its operation_id.