# Click API

- **OpenAPI Version:** `3.2.1`
- **API Version:** `0.1.0`

### Introduction

#### An agent primitive for your application

Click provides an agents API that you build application logic on top of and connect tools to. Configure an agent's model, instructions and tools, then give it work through stateful sessions.

Your application decides when the agent runs, what it can access and how its results become part of your product. Click manages agent execution, conversation state and tool dispatch, and exposes messages, events and files through the API.

#### What Click provides, what your application owns

| Responsibility                | Click                                                                | Your application                                                                     |
| ----------------------------- | -------------------------------------------------------------------- | ------------------------------------------------------------------------------------ |
| Agent execution               | Model invocation, the agent loop and execution controls.             | Instructions, model selection, when to start work and coordination between sessions. |
| Conversation and results      | Durable conversation history, event streaming and saved outputs.     | Product experience, interpreting results and application records.                    |
| Tools                         | Tool dispatch, built-in tools, sandbox tools and MCP calls.          | Implementing application functions and connecting external services.                 |
| Identity and access           | Project isolation and enforcement of configured session tool grants. | End-user authentication, authorization, tool permissions and approvals.              |
| Credentials                   | Storage and use of supplied model keys and MCP credentials.          | Supplying provider keys and acquiring or refreshing MCP credentials.                 |
| External actions and spending | Execution outcomes and recorded tool results.                        | Reconciling uncertain external effects and enforcing application spending controls.  |

#### How it fits together

Configure an agent → create a session → submit input → receive messages, tool requests and results. Send follow-up input to the same session to continue the conversation.

- **Agent:** a reusable, versioned configuration of model, instructions and tools.
- **Session:** the stateful handle for conversation and execution. It fixes an agent version, subject, environment and MCP bindings at creation.
- **Input:** content submitted to a session to start work, steer active work or queue follow-up work.
- **Run:** one execution started by an input, including steering applied while active. It can span multiple model generations and tool calls; a session has at most one active run.
- **Connection:** a remote MCP endpoint and its authentication configuration. Grant a session access to specific tools when binding it.

#### What persists when execution stops

Recorded conversation history and saved output files survive execution loss and sandbox loss, subject to retention and deletion. Durable history does not guarantee resumable execution: a deliberately paused run can continue, but an interrupted run cannot resume. Unsaved sandbox files and processes have no continuity guarantee after sandbox loss. See Guides → Recovery for the next steps in each case.

Retention periods, purge deadlines, sandbox lifetime, availability and numeric rate limits are not specified; do not rely on indefinite retention of sessions, history or files.

#### Hand it to your coding agent

A platform API key is a prerequisite for coding-agent integrations. A human must sign in to the dashboard, create a platform API key and provide their coding agent with:

- The platform API key.
- The API base URL, including `/v1`.
- This reference page.

Coding agents start after this handoff; a platform API key cannot create or manage other platform API keys. Authenticate backend requests with `Authorization: Bearer <platform_api_key>` and keep the key out of end-user clients.

Model requests also require your own stored OpenAI or Anthropic API key; Click supplies no model credentials. After receiving the required credentials, your coding agent can store a model API key, configure an agent and create sessions. The quick start below walks through the first conversation.

### Quick start

Start with a text-only conversation: no tools, MCP connections or sandbox. You need Python 3, a human-provided platform API key and API base URL (including `/v1`), and either an Anthropic API key with access to `anthropic/claude-sonnet-5-5` or an OpenAI API key with access to `openai/gpt-6.1-sol`. Set `PLATFORM_API_BASE_URL`, `PLATFORM_API_KEY` and `ANTHROPIC_API_KEY` or `OPENAI_API_KEY` in your backend environment, save the following as `quick_start.py`, and run `python3 quick_start.py`. It uses Anthropic when `ANTHROPIC_API_KEY` is set, otherwise OpenAI. The keys stay in your backend; never put them in browser code.

The sequence is: store a model key → create an agent → create an empty session → open its event stream → submit input → read authoritative messages and the run outcome → send a follow-up in the same session. Every returned ID is captured from the actual response. Open the stream before submitting input so previews can cover the new message. This example consumes `message.completed`; previews are optional and are not authoritative.

```python
import json
import os
import urllib.request

base = os.environ["PLATFORM_API_BASE_URL"].rstrip("/")
headers = {"Authorization": "Bearer " + os.environ["PLATFORM_API_KEY"]}

def request(method, path, body=None, expected=200):
    data = None if body is None else json.dumps(body).encode("utf-8")
    req = urllib.request.Request(base + path, data=data, method=method,
        headers={**headers, "Content-Type": "application/json"})
    with urllib.request.urlopen(req, timeout=30) as response:
        if response.status != expected:
            raise RuntimeError(f"Unexpected HTTP status: {response.status}")
        return json.load(response)

def events(response):
    data = []
    for raw in response:
        line = raw.decode("utf-8").rstrip("\r\n")
        if not line:
            if data:
                yield json.loads("\n".join(data))
            data = []
        elif line.startswith("data:"):
            data.append(line[5:].lstrip(" "))

provider = "anthropic" if os.environ.get("ANTHROPIC_API_KEY") else "openai"
default_model = {"anthropic": "anthropic/claude-sonnet-5-5", "openai": "openai/gpt-6.1-sol"}[provider]
key = request("POST", "/model-api-keys", {
    "name": "Quick start " + provider, "provider": provider,
    "key": os.environ[provider.upper() + "_API_KEY"],
}, expected=201)
print("model_api_key_id=" + key["id"])
agent = request("POST", "/agents", {"name": "Chat assistant", "config": {
    "model": os.environ.get("MODEL", default_model),
    "model_api_key_id": key["id"],
    "instructions": "Answer the user's questions helpfully.", "tools": [],
}}, expected=201)
print("agent_id=" + agent["id"])
session = request("POST", "/sessions", {
    "agent": {"id": agent["id"], "version": agent["latest_version"]},
    "subject_id": "example/user_123", "environment": {"type": "none"},
}, expected=201)
sid = session["id"]
print("session_id=" + sid)
stream_request = urllib.request.Request(
    base + f"/sessions/{sid}/events/stream?include_deltas=true",
    headers={**headers, "Accept": "text/event-stream"})
with urllib.request.urlopen(stream_request, timeout=60) as stream:
    frames = events(stream)
    for prompt in ["Hello! What can you help me with?", "What did I just ask you?"]:
        receipt = request("POST", f"/sessions/{sid}/inputs", {
            "mode": "queue", "content": [{"type": "text", "text": prompt}],
        }, expected=202)
        print("input_id=" + receipt["id"])
        run_id = receipt["run_id"]
        for event in frames:
            kind = event["type"]
            if kind == "message.delta":
                continue  # Previews have a different shape; wait for authoritative text.
            data = event["data"]
            if kind == "input.cancelled" and data["input"]["id"] == receipt["id"]:
                raise RuntimeError(f"Input {receipt['id']} cancelled: {data['input']['cancellation_reason']}")
            if kind.startswith("run.") and data["run"]["initial_input_id"] == receipt["id"]:
                run_id = data["run"]["id"]
            if kind == "input.applied" and data["input"]["id"] == receipt["id"]:
                run_id = data["input"]["run_id"]
            if kind == "message.completed" and data["run_id"] == run_id:
                print("assistant=" + "".join(block["text"] for block in
                    data["message"]["content"] if block["type"] == "text"))
            if kind in {"run.completed", "run.failed", "run.interrupted", "run.stopped"}:
                if data["run"]["id"] != run_id:
                    continue
                run = request("GET", f"/sessions/{sid}/runs/{run_id}")
                if run["status"] != "completed":
                    raise RuntimeError(f"Run {run_id}: {run['status']}: {run['error']}")
                print("completed_run_id=" + run_id)
                break
        else:
            raise RuntimeError("Stream closed before a run outcome; see Guides → Recovery")
```

HTTP errors raise instead of triggering automatic retries. The stream read timeout is a client limit, not a platform run deadline: a timeout does not stop the agent. Save the session and input IDs before recovering. The sample leaves its resources in place for inspection. Normal completion means execution ended normally; evaluate the answer's correctness separately. Use the same session for follow-ups; `queue` gives each input its own run, while `steer` can join active work. Set `MODEL` to choose another registered model of the same provider. See `GET /sessions/{session_id}/events/stream` for a standalone streaming consumer and replay rules.

### Guides

#### Recovery

Read `GET /sessions/{session_id}` and, when known, `GET /sessions/{session_id}/runs/{run_id}` before deciding how to recover. Session status controls whether execution is allowed; run status records execution progress and outcome. An idle session, a completed message, or a closed connection alone does not prove success.

| Situation                                                        | Action                                                                                                                                                                     | What continues or survives                                                                                                                                   |
| ---------------------------------------------------------------- | -------------------------------------------------------------------------------------------------------------------------------------------------------------------------- | ------------------------------------------------------------------------------------------------------------------------------------------------------------ |
| Client or event stream disconnected                              | Reopen the stream with the last committed event ID as `after` or `Last-Event-ID`; deduplicate by event ID.                                                                 | Disconnecting never stops execution. Deltas are not replayed; restore authoritative messages from history.                                                   |
| Event cursor expired (`410`)                                     | Open a live stream and buffer it, read current session and history, reconcile by event ID, then apply buffered events.                                                     | Retention is unspecified; store application records needed beyond retained history.                                                                          |
| Deliberately paused session                                      | Wait until `paused`, then `POST /sessions/{session_id}/resume`.                                                                                                            | The retained run can continue. Pending application calls keep their original deadlines while paused.                                                         |
| Failed run                                                       | Repair the cause, reconcile uncertain external effects, then resume the session and send fresh input or allow retained queued input to start.                              | Resume never retries the failed run. Queued input is retained; cancel unwanted queued inputs before resuming.                                                |
| Selected model key deleted or a different key is needed          | Store a replacement model key, create an agent version selecting it, and create a new session with that version.                                                           | Existing sessions keep their original key reference; resume cannot change it.                                                                                |
| Interrupted run                                                  | Reconcile uncertain external effects, `POST /sessions/{session_id}/stop` with the interrupted `run_id`, then resume the session.                                           | Interrupted execution cannot resume. History, pending calls, receipts and queued input survive; stop cancels pending calls and unapplied steering.           |
| Lost sandbox                                                     | Create a new session and attach any saved output files needed as inputs.                                                                                                   | The sandbox is never recreated. Saved outputs and history survive; unsaved files and processes have no continuity guarantee.                                 |
| MCP authentication, credential expiry or an unavailable endpoint | Replace the connection's credentials and renew downstream authorization separately when required, or repair the endpoint. The affected calls fail without failing the run. | Later calls, including in the active run, use the current credentials and retry setup. Replacement does not retry failed calls or refresh downstream tokens. |

A stop, failure, timeout or interruption never proves that dispatched tools had no external effects. Reconcile in the system that owns those effects; do not rerun an uncertain operation merely because its response was lost.

##### Retries

| Request or operation                                                                 | After an ambiguous response                                                                                                                                                                                                                                                                    |
| ------------------------------------------------------------------------------------ | ---------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------- |
| Create a key, model key, agent/version, connection, session, upload, or submit input | No idempotency keys or duplicate suppression. Inspect any known resources and IDs before resubmitting; the API supplies no general way to prove whether an unidentified write committed. Matching text or names is not proof.                                                                  |
| Submit application tool results                                                      | Resend the stored, structurally identical result for the same `run_id` and `call_id`. Accepted results return the original receipt without new events, even after the run ended or session was archived. A deleted session returns `404`; a changed result returns `409 tool_result_conflict`. |
| Stop a run                                                                           | Repeat with the same `run_id`. A terminal target is acknowledged without stopping newer work.                                                                                                                                                                                                  |
| Pause or resume a session                                                            | Repeating the desired control is supported, but it operates on the session's current work. Read state first if other callers can change it; it is not scoped to a particular run. Conflicting states can return `409`.                                                                         |
| Archive a session or cancel queued input                                             | Repetition is supported. Archival is irreversible; input cancellation can race dispatch and return `409` once assigned to a run.                                                                                                                                                               |
| Execute an application function                                                      | Deduplicate by session, run and call ID; persist its result. Idempotent result submission does not make your external effects exactly-once. Reconcile uncertain execution instead of blindly repeating it.                                                                                     |

#### Application functions

Use application functions to connect agents to operations inside your own SaaS. One agent definition can serve many users; each user's session selects that definition, while your backend executes its functions under the session user's identity. Function declarations grant operation names, not access to particular records.

##### User and session authorization

Authenticate the user through your application's existing authentication, derive a tenant-qualified `subject_id` on the backend, and create a separate session with `environment: {type: none}`. Persist the session ID with your application's tenant and user ownership record. Never accept `subject_id` from the browser or model as proof of identity.

The platform key grants project-wide access. Before forwarding any session read, input, stream, control, history or file-download request, verify that the authenticated application user can access the session and related resource. A `subject_id` filter is a lookup convenience, not an end-user authorization boundary. The same user mapping must also authorize every function's record and action, using current permissions.

##### Tool execution and confirmation

Create an agent with a function declaration such as the following `config.tools` entry. Your backend implements the function; no callback URL or sandbox is involved.

```json
{"type":"function","name":"get_customer","description":"Look up a customer visible to the authenticated session user.","parameters":{"type":"object","properties":{"customer_id":{"type":"string"}},"required":["customer_id"],"additionalProperties":false}}
```

Open the stream and submit input. On `tool.requested`, read the current session, confirm the call is still in `pending_tool_calls`, and resolve its immutable `subject_id` to your authenticated application principal. Pass that principal to the same business service that enforces permissions for your normal product operations. Model arguments select the requested record and action; they never select the acting user. If the model supplies another user's record ID, deny access without exposing that user's data.

For a write requiring confirmation, persist the session, run and call IDs with the exact requested arguments and show the action to the authorized user. Accept confirmation through your application's authenticated interface, bound to that specific call. A model statement that the user approved is not confirmation. Re-read the pending call and recheck current permissions and its deadline before executing; return an application error if the user declines. Pause does not extend the ten-minute deadline, and stop or expiry can race execution. A platform control never undoes a business operation.

Deduplicate execution by session, run and call ID. Persist the output or error before submitting it to `POST /sessions/{session_id}/tool-results`:

```json
{"run_id":"run_demo","results":[{"call_id":"call_customer","success":true,"output":{"customer_id":"customer_123","name":"Example Customer"}}]}
```

Substitute IDs from the actual pending call. An identical result retry is safe; executing the function twice may not be. Each call has a ten-minute deadline, which pause does not extend. A result receipt does not mean the agent has finished; continue consuming events through the run outcome.

##### Recovering pending calls

After a stream reconnect or application-worker restart, read current session state and inspect `pending_tool_calls` before processing historical requests. Reuse the stored result for a completed local execution; never execute its business effect again just because delivery or the receipt was lost. Rediscover unresolved calls from session state even if their stream event was missed. Keep durable pending confirmations so a restart does not turn them into automatic approvals. Interrupted runs retain pending calls, but submitting results does not resume their execution; follow Guides → Recovery.

When the business mutation and result ledger share a database, commit them in one transaction. For an external service, use its own idempotency or operation-status mechanism and reconcile uncertain effects. The platform's identical-result retry does not provide exactly-once execution of your business operation.

##### Runnable SaaS example

[Download saas.py](/reference/examples/saas.py). This Python 3.11+ standard-library backend gives Alice and Bob separate tickets and sessions with one shared agent definition. It implements `list_tickets` and `rename_ticket`, checks record ownership, requires an authenticated confirmation for each rename, and stores business changes and tool results atomically in SQLite. Repeated result delivery and process restarts reuse that ledger.

Supply `PLATFORM_API_BASE_URL` (including `/v1`), `PLATFORM_API_KEY` and `ANTHROPIC_API_KEY` or `OPENAI_API_KEY` as in the quick start. Create two distinct local application bearer tokens of at least 32 characters as `SAAS_ALICE_TOKEN` and `SAAS_BOB_TOKEN`; for example, generate each with `python3 -c 'import secrets; print(secrets.token_urlsafe(32))'`. These local tokens represent the demo users; in your product, connect the principal mapping to your existing authentication. Keep the platform and model keys in the backend.

Save the downloaded source as `saas.py` and run `python3 saas.py --database saas.sqlite3 --port 8090`. First startup stores the model key and creates a shared agent using `anthropic/claude-sonnet-5-5` or `openai/gpt-6.1-sol`, as in the quick start; `MODEL` selects another registered model of that provider. The database retains its selected agent/version, session ownership, pending confirmations and result ledger across restarts. `SAAS_AGENT_ID` and optional `SAAS_AGENT_VERSION` can select an existing agent with these function definitions instead. An incomplete initial setup blocks automatic setup retries: inspect the saved IDs and reconcile any ambiguous creates before proceeding. The sample leaves platform resources in place for inspection.

Run this client in another terminal with the same application-token environment. It checks cross-user session rejection, submits work for both users, shows each requested rename, and asks for explicit confirmation. Polling current sessions recovers pending calls; this example reads authoritative run messages and does not display text previews.

```python
import json
import os
import time
import urllib.error
import urllib.request

origin = os.environ.get("SAAS_URL", "http://127.0.0.1:8090")
tokens = {"alice": os.environ["SAAS_ALICE_TOKEN"], "bob": os.environ["SAAS_BOB_TOKEN"]}

def call(user, method, path, body=None):
    data = None if body is None else json.dumps(body).encode()
    request = urllib.request.Request(origin + path, data=data, method=method,
        headers={"Authorization": "Bearer " + tokens[user], "Content-Type": "application/json"})
    with urllib.request.urlopen(request, timeout=20) as response:
        return json.load(response)

sessions = {user: call(user, "POST", "/sessions", {})["id"] for user in tokens}
print("sessions=" + json.dumps(sessions))
try:
    call("bob", "GET", "/sessions/" + sessions["alice"])
    raise RuntimeError("Cross-user access was unexpectedly allowed")
except urllib.error.HTTPError as error:
    if error.code != 404:
        raise

for user, sid in sessions.items():
    print(user, "tickets:", call(user, "GET", "/tickets"))
    receipt = call(user, "POST", f"/sessions/{sid}/inputs", {
        "text": "List my tickets, then request to rename my first ticket to Reviewed.",
    })
    print(user, "input_id:", receipt["id"])
    while True:
        state = call(user, "GET", f"/sessions/{sid}")
        for pending in state["approvals"]:
            print(user, "requested action:", pending["tool"], pending["arguments"])
            approved = input("Approve this exact change? Type yes: ").strip().lower() == "yes"
            path = f"/sessions/{sid}/runs/{pending['run_id']}/calls/{pending['call_id']}/decision"
            call(user, "POST", path, {"approved": approved})
        run = state["run"]
        if run and run["status"] in {"completed", "failed", "stopped", "interrupted"}:
            print(user, "run outcome:", run["status"], "messages:", run["messages"], "error:", run["error"])
            break
        time.sleep(0.5)
```

The example's local API exposes only owned tickets, session creation, input submission, session/run status and per-call decisions. Your product must apply the same authorization checks when adding streaming, controls or file downloads. Its polling executor can be replaced with `tool.requested` handling plus session-state recovery. It never automatically resubmits input. After an ambiguous response, reuse known session/input IDs and inspect state. Local SQLite atomicity covers only these demo ticket changes; it is not a guarantee for external services or effects racing platform cancellation.

#### Research files

Create an agent with `tools: [web_search, web_fetch, read_file, apply_patch]` and instructions to research the request and save a cited report under `/workspace/outputs/`. Create an empty session with `environment: {type: sandbox}` and no MCP connections, open the stream, then submit a text request. Built-in web search and fetch do not require sandbox egress grants.

After `run.completed`, retrieve the run and use its `output_file_ids`; each listed file is ready to download. Retrieve metadata with `GET /files/{file_id}` and bytes with `GET /files/{file_id}/content`, using the platform bearer key. Judge the report and its citations independently of run completion. Stopped and failed runs save no new outputs. Saved outputs survive sandbox loss and archival, subject to retention; unsaved sandbox files do not.

#### API conventions

Every operation is under the base URL's `/v1` path; other paths return 404 `not_found`. A method that a path does not support returns 405 `method_not_allowed` with an `Allow` header listing the supported methods. The `Bearer` authentication scheme name is case-insensitive.

Send JSON request bodies with `Content-Type: application/json`; media type parameters such as `charset=utf-8` are allowed. A non-empty body with another or no media type returns 415 `unsupported_media_type`. Uploads use `multipart/form-data`.

Creation and input submission have no idempotency keys or duplicate suppression: do not blindly resubmit an ambiguous request. Identical tool-result submissions return stored receipts, and some control operations allow repeated requests; see Guides → Recovery for the retry rules.

Strings in JSON request bodies, including object keys, must not contain the NUL character (U+0000); such requests return 400 `invalid_request`. NUL characters produced by the agent or its tools, in messages, tool arguments, results and errors, are recorded as U+FFFD.

List pages follow a stable order, and `next_cursor` continues after the last record of its page. Deleting records or changing their status between requests never invalidates a cursor: records that left the collection are skipped and later records are returned.

##### Error codes

Errors use the `Error` envelope: a stable `code`, a human-readable `message` and the `request_id` to quote when reporting a problem. Branch on `code`, never on `message`. Operations describe when each code applies. Run and tool failures are not HTTP errors; their codes are documented on `GET /sessions/{session_id}/runs/{run_id}` and with each tool on `POST /agents`.

| Status | Code                              | Meaning                                                                                                                                                       |
| ------ | --------------------------------- | ------------------------------------------------------------------------------------------------------------------------------------------------------------- |
| 400    | `invalid_request`                 | The body does not match the operation's schema, contains a NUL character or has an invalid value.                                                             |
| 400    | `invalid_json`                    | The body is not valid JSON.                                                                                                                                   |
| 400    | `request_too_large`               | A JSON body exceeds 16 MiB.                                                                                                                                   |
| 400    | `invalid_parameter`               | A path, query or header parameter does not match its schema.                                                                                                  |
| 400    | `invalid_cursor`                  | A pagination cursor is malformed or belongs to another endpoint or filter, or a stream's event ID is unknown or belongs to another session.                   |
| 400    | `conflicting_cursor`              | A stream received different `after` and `Last-Event-ID` values.                                                                                               |
| 400    | `invalid_model`                   | The model is not a registered `openai/` or `anthropic/` model.                                                                                                |
| 400    | `model_api_key_provider_mismatch` | The model API key belongs to another provider.                                                                                                                |
| 400    | `invalid_tools`                   | Tool names repeat, or a function uses a built-in tool name.                                                                                                   |
| 400    | `invalid_schema`                  | A function's `parameters` is not a valid JSON Schema, references an external schema, has an unresolvable reference or contains an invalid regular expression. |
| 400    | `invalid_url`                     | An MCP endpoint is not an HTTPS URL without credentials or a fragment.                                                                                        |
| 400    | `duplicate_connection`            | A session binds the same `connection_id` twice.                                                                                                               |
| 400    | `invalid_network_policy`          | An allowed host is not an exact DNS hostname.                                                                                                                 |
| 400    | `unsupported_image`               | The sandbox `image` is not `managed-default`.                                                                                                                 |
| 400    | `files_require_sandbox`           | File content was sent to a session without a sandbox.                                                                                                         |
| 400    | `unsupported_reasoning_effort`    | The session's model does not support the requested reasoning effort.                                                                                          |
| 400    | `unsupported_image_input`         | The session's model does not accept images.                                                                                                                   |
| 400    | `invalid_image`                   | Image content is not a static PNG, JPEG or WebP.                                                                                                              |
| 400    | `image_limits_exceeded`           | Image content exceeds the model's image limits.                                                                                                               |
| 400    | `invalid_upload`                  | The upload is not exactly one multipart `file` field.                                                                                                         |
| 400    | `duplicate_call_id`               | A tool-result batch repeats a `call_id`.                                                                                                                      |
| 400    | `invalid_tool_result`             | A result names a call that the platform executes.                                                                                                             |
| 401    | `unauthorized`                    | The bearer credential is missing, invalid, expired or revoked.                                                                                                |
| 403    | `forbidden`                       | The credential cannot perform this operation, such as a project API key managing API keys.                                                                    |
| 403    | `connection_subject_mismatch`     | A bound connection belongs to another `subject_id`.                                                                                                           |
| 404    | `not_found`                       | The path, or a resource that it or the body names, does not exist in this project.                                                                            |
| 405    | `method_not_allowed`              | The path does not support the method; see the `Allow` header.                                                                                                 |
| 409    | `session_active`                  | The session has an active or stopping run.                                                                                                                    |
| 409    | `session_archived`                | The session is archived and read-only.                                                                                                                        |
| 409    | `session_paused`                  | Steering was sent while the session is pausing or paused.                                                                                                     |
| 409    | `session_pausing`                 | Resume was requested before the session finished pausing.                                                                                                     |
| 409    | `run_interrupted`                 | The session's run is interrupted; stop it before steering or resuming.                                                                                        |
| 409    | `run_stopping`                    | The session's run is stopping.                                                                                                                                |
| 409    | `run_not_active`                  | A stop named a run of this session that is neither active nor finished.                                                                                       |
| 409    | `input_not_queued`                | The input already has a run, so it cannot be cancelled.                                                                                                       |
| 409    | `connection_session_scoped`       | A session-owned connection cannot be bound to another session.                                                                                                |
| 409    | `tool_call_not_pending`           | The call no longer accepts new results.                                                                                                                       |
| 409    | `tool_call_run_mismatch`          | The call belongs to another run.                                                                                                                              |
| 409    | `tool_result_conflict`            | The call already has a different accepted result.                                                                                                             |
| 410    | `cursor_expired`                  | The cursor or event is no longer retained under the retention policy.                                                                                         |
| 413    | `file_too_large`                  | The upload exceeds 16 MiB.                                                                                                                                    |
| 415    | `unsupported_media_type`          | A JSON body was sent without `Content-Type: application/json`.                                                                                                |
| 429    | `rate_limited`                    | The service request rate limit was reached.                                                                                                                   |
| 500    | `internal_error`                  | A platform error; no automatic resubmission or tool replay is promised.                                                                                       |
| 500    | `image_preparation_failed`        | Accepted image bytes could not be read or prepared.                                                                                                           |
| 500    | `stream_unavailable`              | Text previews could not be set up for a stream.                                                                                                               |

The dashboard manages project API keys, model API keys, agents and session logs. Each signed-in user owns an isolated project; organization membership does not share projects. Signing out ends the dashboard session but does not revoke API keys. Dashboard access tokens remain valid until their expiry; deleted project API keys cannot authenticate new requests. Existing requests and event streams are not cancelled by credential revocation.

## Servers

- **URL:** `https://api.useclick.ai/v1`
  - **Description:** Platform instance

## Authentication

- **ProjectToken**: HTTP bearer

  Project API key created from the dashboard. Keys grant full project access, including model API key management, except project API key management. The local development instance also accepts `dev-key`, and `dev-key-other` for a second project; these projects are separate from signed-in dashboard projects. Every resource belongs to one project: IDs from another project, in paths or request bodies, return 404 `not_found`. The key does not identify end users; your backend authenticates them and supplies `subject_id`. Keep the key in your backend: the platform never forwards it to sandboxes or MCP servers.

Or:

- **DashboardSession**: HTTP bearer (`JWT`)

  A valid, unexpired dashboard login access token. It identifies the signed-in user's project and authorizes API key management as well as normal project operations.
