# Create agent Source: https://docs.cerca.dev/api-reference/agents/create-agent https://app.stainless.com/api/spec/documented/cerca/openapi.documented.yml post /agents # Delete agent Source: https://docs.cerca.dev/api-reference/agents/delete-agent https://app.stainless.com/api/spec/documented/cerca/openapi.documented.yml delete /agents/{agentId} # List agents Source: https://docs.cerca.dev/api-reference/agents/list-agents https://app.stainless.com/api/spec/documented/cerca/openapi.documented.yml get /agents # Retrieve agent Source: https://docs.cerca.dev/api-reference/agents/retrieve-agent https://app.stainless.com/api/spec/documented/cerca/openapi.documented.yml get /agents/{agentId} # Retrieve config Source: https://docs.cerca.dev/api-reference/agents/retrieve-config https://app.stainless.com/api/spec/documented/cerca/openapi.documented.yml get /agents/{agentId}/config # Update agent Source: https://docs.cerca.dev/api-reference/agents/update-agent https://app.stainless.com/api/spec/documented/cerca/openapi.documented.yml put /agents/{agentId} # Update metadata Source: https://docs.cerca.dev/api-reference/agents/update-metadata https://app.stainless.com/api/spec/documented/cerca/openapi.documented.yml patch /agents/{agentId}/metadata # Create approval Source: https://docs.cerca.dev/api-reference/approvals/create-approval https://app.stainless.com/api/spec/documented/cerca/openapi.documented.yml post /agents/{agentId}/threads/{threadId}/approvals/{approvalId} # Delete approval grant Source: https://docs.cerca.dev/api-reference/approvals/delete-approval-grant https://app.stainless.com/api/spec/documented/cerca/openapi.documented.yml delete /agents/{agentId}/approval-grants/{grantId} # Delete approval grant Source: https://docs.cerca.dev/api-reference/approvals/delete-approval-grant-1 https://app.stainless.com/api/spec/documented/cerca/openapi.documented.yml delete /agents/{agentId}/threads/{threadId}/approval-grants/{grantId} # List approval grants Source: https://docs.cerca.dev/api-reference/approvals/list-approval-grants https://app.stainless.com/api/spec/documented/cerca/openapi.documented.yml get /agents/{agentId}/approval-grants # List approval grants Source: https://docs.cerca.dev/api-reference/approvals/list-approval-grants-1 https://app.stainless.com/api/spec/documented/cerca/openapi.documented.yml get /agents/{agentId}/threads/{threadId}/approval-grants # List approvals Source: https://docs.cerca.dev/api-reference/approvals/list-approvals https://app.stainless.com/api/spec/documented/cerca/openapi.documented.yml get /agents/{agentId}/approvals # List fleets Source: https://docs.cerca.dev/api-reference/auth/list-fleets https://app.stainless.com/api/spec/documented/cerca/openapi.documented.yml get /auth/fleets # Retrieve context Source: https://docs.cerca.dev/api-reference/auth/retrieve-context https://app.stainless.com/api/spec/documented/cerca/openapi.documented.yml get /auth/context # Attach connection Source: https://docs.cerca.dev/api-reference/connections/attach-connection https://app.stainless.com/api/spec/documented/cerca/openapi.documented.yml post /agents/{agentId}/connections # Create API key Source: https://docs.cerca.dev/api-reference/connections/create-api-key https://app.stainless.com/api/spec/documented/cerca/openapi.documented.yml post /connections/api-keys # Delete connection Source: https://docs.cerca.dev/api-reference/connections/delete-connection https://app.stainless.com/api/spec/documented/cerca/openapi.documented.yml delete /connections/{connectionId} # Detach connection Source: https://docs.cerca.dev/api-reference/connections/detach-connection https://app.stainless.com/api/spec/documented/cerca/openapi.documented.yml delete /agents/{agentId}/connections/{connectionId} # List connections Source: https://docs.cerca.dev/api-reference/connections/list-connections https://app.stainless.com/api/spec/documented/cerca/openapi.documented.yml get /connections # List connections Source: https://docs.cerca.dev/api-reference/connections/list-connections-1 https://app.stainless.com/api/spec/documented/cerca/openapi.documented.yml get /agents/{agentId}/connections # Delete context entry Source: https://docs.cerca.dev/api-reference/context/delete-context-entry https://app.stainless.com/api/spec/documented/cerca/openapi.documented.yml delete /agents/{agentId}/context/entry # List context entries Source: https://docs.cerca.dev/api-reference/context/list-context-entries https://app.stainless.com/api/spec/documented/cerca/openapi.documented.yml get /agents/{agentId}/context # Retrieve context entry Source: https://docs.cerca.dev/api-reference/context/retrieve-context-entry https://app.stainless.com/api/spec/documented/cerca/openapi.documented.yml get /agents/{agentId}/context/entry # Search context Source: https://docs.cerca.dev/api-reference/context/search-context https://app.stainless.com/api/spec/documented/cerca/openapi.documented.yml get /agents/{agentId}/context/search # Update context entry Source: https://docs.cerca.dev/api-reference/context/update-context-entry https://app.stainless.com/api/spec/documented/cerca/openapi.documented.yml put /agents/{agentId}/context/entry # Agent event stream Source: https://docs.cerca.dev/api-reference/events/agent-event-stream https://app.stainless.com/api/spec/documented/cerca/openapi.documented.yml get /agents/{agentId}/events/stream Server-Sent Events stream. Each SSE data frame is JSON matching this response schema. # Fleet event stream Source: https://docs.cerca.dev/api-reference/events/fleet-event-stream https://app.stainless.com/api/spec/documented/cerca/openapi.documented.yml get /fleets/{fleetId}/events/stream Server-Sent Events stream. Each SSE data frame is JSON matching this response schema. # List events Source: https://docs.cerca.dev/api-reference/events/list-events https://app.stainless.com/api/spec/documented/cerca/openapi.documented.yml get /fleets/{fleetId}/events # List events Source: https://docs.cerca.dev/api-reference/events/list-events-1 https://app.stainless.com/api/spec/documented/cerca/openapi.documented.yml get /agents/{agentId}/events # List events Source: https://docs.cerca.dev/api-reference/events/list-events-2 https://app.stainless.com/api/spec/documented/cerca/openapi.documented.yml get /agents/{agentId}/threads/{threadId}/events # Thread event stream Source: https://docs.cerca.dev/api-reference/events/thread-event-stream https://app.stainless.com/api/spec/documented/cerca/openapi.documented.yml get /agents/{agentId}/threads/{threadId}/events/stream Server-Sent Events stream. Each SSE data frame is JSON matching this response schema. # Thread message stream Source: https://docs.cerca.dev/api-reference/events/thread-message-stream https://app.stainless.com/api/spec/documented/cerca/openapi.documented.yml get /agents/{agentId}/threads/{threadId}/stream Server-Sent Events stream. Each SSE data frame is JSON matching this response schema. # List models Source: https://docs.cerca.dev/api-reference/models/list-models https://app.stainless.com/api/spec/documented/cerca/openapi.documented.yml get /models # Start OAuth authorization Source: https://docs.cerca.dev/api-reference/oauth/start-oauth-authorization https://app.stainless.com/api/spec/documented/cerca/openapi.documented.yml post /oauth/connect/{provider} # Execute sandbox command Source: https://docs.cerca.dev/api-reference/sandbox/execute-sandbox-command https://app.stainless.com/api/spec/documented/cerca/openapi.documented.yml post /agents/{agentId}/sandbox/exec # Read sandbox file Source: https://docs.cerca.dev/api-reference/sandbox/read-sandbox-file https://app.stainless.com/api/spec/documented/cerca/openapi.documented.yml post /agents/{agentId}/sandbox/read # Write sandbox file Source: https://docs.cerca.dev/api-reference/sandbox/write-sandbox-file https://app.stainless.com/api/spec/documented/cerca/openapi.documented.yml post /agents/{agentId}/sandbox/write # Create schedule Source: https://docs.cerca.dev/api-reference/schedules/create-schedule https://app.stainless.com/api/spec/documented/cerca/openapi.documented.yml post /agents/{agentId}/schedules # Delete schedule Source: https://docs.cerca.dev/api-reference/schedules/delete-schedule https://app.stainless.com/api/spec/documented/cerca/openapi.documented.yml delete /agents/{agentId}/schedules/{scheduleId} # List schedules Source: https://docs.cerca.dev/api-reference/schedules/list-schedules https://app.stainless.com/api/spec/documented/cerca/openapi.documented.yml get /agents/{agentId}/schedules # Trigger schedule Source: https://docs.cerca.dev/api-reference/schedules/trigger-schedule https://app.stainless.com/api/spec/documented/cerca/openapi.documented.yml post /agents/{agentId}/schedules/{scheduleId}/trigger # Update schedule Source: https://docs.cerca.dev/api-reference/schedules/update-schedule https://app.stainless.com/api/spec/documented/cerca/openapi.documented.yml put /agents/{agentId}/schedules/{scheduleId} # Cancel thread Source: https://docs.cerca.dev/api-reference/threads/cancel-thread https://app.stainless.com/api/spec/documented/cerca/openapi.documented.yml post /agents/{agentId}/threads/{threadId}/cancel Cancel a running or awaiting thread. The underlying runtime treats repeat cancellation as an idempotent lifecycle operation when possible. # Close thread Source: https://docs.cerca.dev/api-reference/threads/close-thread https://app.stainless.com/api/spec/documented/cerca/openapi.documented.yml post /agents/{agentId}/threads/{threadId}/close Close an idle thread. Closing a running, awaiting, or already-closed thread returns a lifecycle conflict. # Compact thread Source: https://docs.cerca.dev/api-reference/threads/compact-thread https://app.stainless.com/api/spec/documented/cerca/openapi.documented.yml post /agents/{agentId}/threads/{threadId}/compact Force context compaction for an idle thread. Compacting a running thread returns a lifecycle conflict. # Create thread Source: https://docs.cerca.dev/api-reference/threads/create-thread https://app.stainless.com/api/spec/documented/cerca/openapi.documented.yml post /agents/{agentId}/threads # Create turn Source: https://docs.cerca.dev/api-reference/threads/create-turn https://app.stainless.com/api/spec/documented/cerca/openapi.documented.yml post /agents/{agentId}/threads/{threadId}/turns # Get thread activity Source: https://docs.cerca.dev/api-reference/threads/get-thread-activity https://app.stainless.com/api/spec/documented/cerca/openapi.documented.yml get /agents/{agentId}/threads/{threadId}/activity Fetch compact current and recent activity for a thread without returning transcript content or runtime debug state. # List thread messages Source: https://docs.cerca.dev/api-reference/threads/list-thread-messages https://app.stainless.com/api/spec/documented/cerca/openapi.documented.yml get /agents/{agentId}/threads/{threadId}/messages List a bounded page of transcript messages for a thread, newest first. Use the returned `cursor` to page older messages. # List threads Source: https://docs.cerca.dev/api-reference/threads/list-threads https://app.stainless.com/api/spec/documented/cerca/openapi.documented.yml get /agents/{agentId}/threads # Retrieve thread Source: https://docs.cerca.dev/api-reference/threads/retrieve-thread https://app.stainless.com/api/spec/documented/cerca/openapi.documented.yml get /agents/{agentId}/threads/{threadId} # Steer thread Source: https://docs.cerca.dev/api-reference/threads/steer-thread https://app.stainless.com/api/spec/documented/cerca/openapi.documented.yml post /agents/{agentId}/threads/{threadId}/steer Steer a thread with another user message. Steering a closed thread returns a conflict; steering a running or awaiting thread queues the message. # Create tool Source: https://docs.cerca.dev/api-reference/tools/create-tool https://app.stainless.com/api/spec/documented/cerca/openapi.documented.yml post /fleets/{fleetId}/tools # Delete tool source Source: https://docs.cerca.dev/api-reference/tools/delete-tool-source https://app.stainless.com/api/spec/documented/cerca/openapi.documented.yml delete /fleets/{fleetId}/tools/{sourceId} # List tools Source: https://docs.cerca.dev/api-reference/tools/list-tools https://app.stainless.com/api/spec/documented/cerca/openapi.documented.yml get /fleets/{fleetId}/tools # List tools Source: https://docs.cerca.dev/api-reference/tools/list-tools-1 https://app.stainless.com/api/spec/documented/cerca/openapi.documented.yml get /agents/{agentId}/tools # Retrieve tool source Source: https://docs.cerca.dev/api-reference/tools/retrieve-tool-source https://app.stainless.com/api/spec/documented/cerca/openapi.documented.yml get /fleets/{fleetId}/tools/{sourceId} # Update tool source Source: https://docs.cerca.dev/api-reference/tools/update-tool-source https://app.stainless.com/api/spec/documented/cerca/openapi.documented.yml put /fleets/{fleetId}/tools/{sourceId} # Create webhook Source: https://docs.cerca.dev/api-reference/webhooks/create-webhook https://app.stainless.com/api/spec/documented/cerca/openapi.documented.yml post /fleets/{fleetId}/webhooks # Delete webhook Source: https://docs.cerca.dev/api-reference/webhooks/delete-webhook https://app.stainless.com/api/spec/documented/cerca/openapi.documented.yml delete /fleets/{fleetId}/webhooks/{webhookId} # List webhooks Source: https://docs.cerca.dev/api-reference/webhooks/list-webhooks https://app.stainless.com/api/spec/documented/cerca/openapi.documented.yml get /fleets/{fleetId}/webhooks # Retrieve webhook Source: https://docs.cerca.dev/api-reference/webhooks/retrieve-webhook https://app.stainless.com/api/spec/documented/cerca/openapi.documented.yml get /fleets/{fleetId}/webhooks/{webhookId} # Rotate webhook secret Source: https://docs.cerca.dev/api-reference/webhooks/rotate-webhook-secret https://app.stainless.com/api/spec/documented/cerca/openapi.documented.yml post /fleets/{fleetId}/webhooks/{webhookId}/rotate # Send test webhook Source: https://docs.cerca.dev/api-reference/webhooks/send-test-webhook https://app.stainless.com/api/spec/documented/cerca/openapi.documented.yml post /fleets/{fleetId}/webhooks/{webhookId}/test # Update webhook Source: https://docs.cerca.dev/api-reference/webhooks/update-webhook https://app.stainless.com/api/spec/documented/cerca/openapi.documented.yml put /fleets/{fleetId}/webhooks/{webhookId} # Authentication Source: https://docs.cerca.dev/authentication Use organization-scoped API keys and connection-backed credentials. Cerca's public API uses organization-scoped API keys. Send the key as a bearer token on every API-key route: ```http theme={null} Authorization: Bearer $CERCA_API_KEY ``` A new integration starts by resolving which organization the key belongs to, then discovering the fleets it can use to decide where a new agent should live. Do not put third-party credentials in prompts or tool input when there is a safer path. Store reusable credentials as connections, then attach those connections to the agent that needs them. OAuth-backed tools can act through the selected end-user account without exposing raw access tokens to the model. Admin-only work, such as creating organizations, fleets, API keys, and OAuth apps, still happens in the Cerca app. # Client libraries Source: https://docs.cerca.dev/client-libraries Install the official Cerca SDKs and CLI. Cerca ships official clients for TypeScript, Python, Go, and Ruby. The REST API is also available directly with curl, and the CLI is useful for local scripts and debugging. All clients use the same organization-scoped API key: ```sh theme={null} export CERCA_API_KEY="..." ``` ## TypeScript Install the JavaScript and TypeScript SDK from npm: ```sh theme={null} npm install @cerca-dev/sdk ``` Use the package manager your project already uses: ```sh theme={null} pnpm add @cerca-dev/sdk yarn add @cerca-dev/sdk bun add @cerca-dev/sdk ``` Create a client with your API key: ```ts theme={null} import Cerca from "@cerca-dev/sdk"; const client = new Cerca({ apiKey: process.env.CERCA_API_KEY, }); ``` ## Python Install the Python SDK from PyPI: ```sh theme={null} pip install cerca-python ``` For projects that use uv: ```sh theme={null} uv add cerca-python ``` Create a client with your API key: ```python theme={null} import os from cerca import Cerca client = Cerca(api_key=os.environ["CERCA_API_KEY"]) ``` ## Go Install the Go SDK with `go get`: ```sh theme={null} go get github.com/matrices/cerca-go ``` Create a client with your API key: ```go theme={null} package main import ( "os" cerca "github.com/matrices/cerca-go" "github.com/matrices/cerca-go/option" ) func main() { client := cerca.NewClient(option.WithAPIKey(os.Getenv("CERCA_API_KEY"))) _ = client } ``` ## Ruby Install the Ruby SDK from RubyGems: ```sh theme={null} gem install cerca ``` For Bundler-managed apps: ```sh theme={null} bundle add cerca ``` Create a client with your API key: ```ruby theme={null} require "cerca" client = Cerca::Client.new(api_key: ENV["CERCA_API_KEY"]) ``` ## CLI Install the Cerca CLI with Homebrew: ```sh theme={null} brew install matrices/cerca/cerca ``` Then authenticate each command with the same API key environment variable: ```sh theme={null} export CERCA_API_KEY="..." cerca auth context ``` ## Next steps After installing a client, follow the [Quickstart](/quickstart) to verify your key, create an agent, and start a thread. The API Reference tab includes endpoint-level examples for each generated SDK. # Agents Source: https://docs.cerca.dev/concepts/agents Durable workspaces with configuration, context, tools, and sandbox state. An agent is the unit of isolation in Cerca. Think of it as an always-on cloud workspace for one user, account, or business process. It owns durable context, threads, attached connections, tool access, approval settings, and a persistent sandbox filesystem. Most products create one agent per user. That keeps a user's threads, files, memory, and connected accounts in one place. You can also create multiple agents for one user when the work needs hard separation, for example a finance agent and a support agent with different tools and approval policies. Agents inherit shared defaults from a fleet. The fleet can provide instructions, a default model, an allowlist of tools, and approval rules. If a field is not locked by the fleet, the agent can override it. Use agent configuration for stable behavior: standing instructions, the namespaces the model can reach, and the gates that keep risky actions under human control. # Approvals Source: https://docs.cerca.dev/concepts/approvals Gate risky tools with exact names, namespace wildcards, and timeouts. Approvals decide when a tool call must pause for a human decision. They are configured on the same fleet and agent configuration surface as tools and instructions. Approval policy keys use the same naming style as tools. You can gate one exact tool, such as `sandbox.exec`; a namespace wildcard, such as `docs.*`; or everything with `*`. When multiple keys match, the most specific one wins, so a broad fleet-level rule can be tightened or relaxed for a single tool without rewriting the rest. Fleet defaults and agent overrides are merged per approval key unless the fleet locks approvals. That lets an operator add a new fleet-level gate without rewriting every agent. Use approvals for actions with side effects: sending email, writing to production systems, running commands, changing approval policy, or calling custom tools that mutate customer data. To react to approval activity from your own product, subscribe to the [`approval.requested`, `approval.resolved`, and `approval.granted`](/concepts/webhooks-events#approval) webhook events. # Connections Source: https://docs.cerca.dev/concepts/connections Reusable credentials that let agents act through the right account. Connections are reusable authenticated links to external systems. They let an agent use Gmail, GitHub, Notion, Slack, a custom OAuth provider, or an API-key-backed system without putting raw credentials into prompts or logs. A connection is scoped to an organization or fleet. After the user authorizes the provider, your app attaches the connection to an agent, optionally with metadata that helps identify the account in the UI or during tool discovery. Tool execution only considers connections attached to the current agent. If exactly one eligible connection exists, the runtime can select it automatically. If multiple eligible accounts match, discovery surfaces them as separate entries with account labels so the model can choose deliberately. Use connections when a tool should act as the end user. Use tool-owned auth when every agent should call the same backend identity. Keeping that distinction explicit makes account selection auditable and easier to reason about. To track when accounts are linked or unlinked, subscribe to the [`connection.attached` and `connection.detached`](/concepts/webhooks-events#connection) webhook events. # Sandbox Source: https://docs.cerca.dev/concepts/sandbox A persistent cloud sandbox for commands, files, and artifacts. Each agent has a persistent cloud sandbox. It gives the agent a place to run commands, write code, inspect files, and sync artifacts produced by tools. The sandbox is part of the agent workspace, so work can continue across threads instead of starting from an empty machine every time. The sandbox supports both one-shot commands and longer-running interactive sessions, so the agent can do quick lookups or work through a multi-step CLI flow without losing the file system in between. Avoid sending plaintext credentials through a prompt or terminal interaction when a connection, fleet variable, or non-interactive credential path can do the job. # Schedules Source: https://docs.cerca.dev/concepts/schedules Let agents run future or recurring work through the runtime. Schedules let an agent keep working after the immediate thread is done. They are part of the same harness as normal agent work: the runtime owns the durable state, recovers from transient failures, and gives the scheduled work the agent's configured context and tools. Enable the `agent.schedule.*` namespace when the agent should be able to create or manage scheduled work from inside a thread. Keep the instructions specific: what should happen, when it should happen, and what approval gates apply if the scheduled task needs to take action. Use schedules for jobs that belong with the agent's long-lived responsibility, such as checking a queue, preparing a recurring report, or following up later. Do not use them as a replacement for your product's own source of truth. Your app should still store the business record; the agent should store the work plan and execution context. When a schedule fires, the runtime emits a [`schedule.triggered`](/concepts/webhooks-events#schedule) webhook so your product can observe the run without polling. # Threads Source: https://docs.cerca.dev/concepts/threads Conversations or runs inside an agent workspace. A thread is a conversation or run inside an agent. It is the place where the model receives a request, calls tools, writes files, asks for approvals, and produces output. The agent keeps the durable workspace; the thread keeps the execution flow for one piece of work. You can start a thread with a prompt, stream updates while it works, and inspect logs after the fact. The runtime handles the harness work around the model: context loading, tool-call execution, retries, compaction, and error recovery. That lets the thread continue through longer tasks without forcing your app to orchestrate every model step. Threads are also where user steering happens. If the work changes, add new input to the running thread instead of throwing away the agent state. If a tool call needs approval, the thread pauses until the approval is resolved or times out. The important split is simple: your application creates and observes threads, while the running thread decides which allowed tools to call. When your product needs to react to thread progress without watching the stream, subscribe to the [`thread.*` and `turn.*`](/concepts/webhooks-events#thread) webhook events instead of polling. # Tools Source: https://docs.cerca.dev/concepts/tools Built-in runtime tools and fleet-owned external tools. Tools are capabilities available to the model while a thread is running. Your app configures what the agent is allowed to use, then the runtime exposes the right tools to the model at execution time. There are built-in tools for runtime work such as memory, web, sandbox, scheduling, orchestration, and artifacts. Configurable tools can be enabled by exact name or namespace wildcard, for example `sandbox.exec`, `web.*`, `memory.*`, or `agent.schedule.*`. External tools are owned by a fleet. Each one lives under a namespace, such as `docs`, and exposes actions as full names like `docs.search`. The agent opts in with a namespace wildcard such as `docs.*`. Large tool catalogs are kept out of the main context. The model discovers a ranked subset of relevant tools for the goal at hand, then calls the ones it actually needs. # Webhooks Source: https://docs.cerca.dev/concepts/webhooks How your product hears about agent activity it didn't directly trigger. Webhooks are how your product hears about agent activity it did not directly trigger. When a thread completes overnight, an approval request comes in, a schedule fires, or a connection is detached, the runtime delivers an HTTP POST to a URL you control instead of waiting for you to poll. A subscription is fleet-scoped. One URL can listen to a curated subset of event types or all of them. The secret is returned only when the subscription is created or rotated, so capture it then; the API will not show it again. ## Delivery model Deliveries are at-least-once over HTTPS. Each request times out after 10 seconds. A failed attempt — network error, `429`, or `5xx` — is retried once after a 1 second delay; everything else is delivered exactly once. Because retries can produce duplicates, your handler should be idempotent. The `X-Agent-Delivery-Id` header is stable across retries of the same delivery and is the right key to dedupe on. Every request is signed. The `X-Agent-Signature` header carries an HMAC-SHA256 of `{timestamp}.{payload}` using your subscription secret, prefixed with `sha256=`. The `X-Agent-Timestamp` header carries the same timestamp the signature was computed over, which lets you reject replays whose drift exceeds your tolerance window. Ordering is not guaranteed. Two events about the same thread may arrive out of order, and a retried delivery can land after a later event. Treat each event as a fact about a moment in time and reconcile against your own state when ordering matters. ## When to use webhooks Use webhooks when the reaction needs to happen without a user watching — sending a notification after a long-running thread finishes, recording an audit trail, fanning a `schedule.triggered` event out to your own queue. Use thread streaming, not webhooks, for foreground UX where a user is actively waiting on the agent. Approvals are the canonical webhook use case: an `approval.requested` event tells your product to surface a decision to the right human, and `approval.resolved` lets you continue once the decision lands. For the full set of events Cerca emits and their payload shapes, see the [event reference](/concepts/webhooks-events). For a step-by-step receive path with signature verification, see [Receiving webhooks](/guides/receiving-webhooks). # Webhook events Source: https://docs.cerca.dev/concepts/webhooks-events Reference for every webhook event type, its payload, and what triggers it. This page lists every webhook event Cerca emits, with the payload shape and a representative JSON example. For the delivery model, signing, and retry semantics, see [Webhooks](/concepts/webhooks). For the receive path, see [Receiving webhooks](/guides/receiving-webhooks). ## Envelope Every delivery shares the same outer shape. The event-specific payload lives under `data`; everything else identifies which agent, fleet, and thread the event belongs to. | Field | Type | Description | | ----------- | ---------------- | ----------------------------------------------------------------------------------------------------- | | `id` | `string` | Stable ID for this event. Mirrors the `X-Agent-Delivery-Id` header and is the right key to dedupe on. | | `event` | `string` | The event type, e.g. `thread.completed`. Mirrors `X-Agent-Event`. | | `timestamp` | `string` | ISO 8601 timestamp the event was generated at. | | `agentId` | `string` | Agent the event belongs to. | | `orgId` | `string` | Organization that owns the agent. | | `fleetId` | `string` | Fleet the subscription is scoped to. | | `threadId` | `string \| null` | Thread the event belongs to, if any. `null` for agent- and connection-level events. | | `data` | `object` | Event-specific payload. See sections below. | ```json theme={null} { "id": "evt_01HZX9F4G2N3K7B0Q1WVYE6T8M", "event": "thread.completed", "timestamp": "2026-05-04T12:00:00.000Z", "agentId": "agent_abc123", "orgId": "org_acme", "fleetId": "fleet_internal_ops", "threadId": "thread_xyz789", "data": { "status": "idle", "previousStatus": "running", "result": "Booked the flight and emailed the itinerary." } } ``` ## Agent ### `agent.created` Fired when a new agent is provisioned in the fleet. | Field | Type | Description | | ------- | ------- | -------------------------------------------------- | | `agent` | `Agent` | The full agent record, minus internal-only fields. | ```json theme={null} { "agent": { "id": "agent_abc123", "userId": "user_carol", "orgId": "org_acme", "fleetId": "fleet_internal_ops", "configuration": { "model": "claude-sonnet-4-5", "instructions": "You are a research assistant.", "tools": [] }, "metadata": { "team": "research" }, "createdAt": "2026-05-04T12:00:00.000Z", "updatedAt": "2026-05-04T12:00:00.000Z" } } ``` ### `agent.updated` Fired when an agent's configuration or metadata changes. `trigger` distinguishes the two so you can decide whether to invalidate cached config. | Field | Type | Description | | --------- | ------------------------------- | ----------------------------------------- | | `trigger` | `"configuration" \| "metadata"` | Which kind of change triggered the event. | | `agent` | `Agent` | The agent record after the update. | ```json theme={null} { "trigger": "configuration", "agent": { "id": "agent_abc123", "userId": "user_carol", "orgId": "org_acme", "fleetId": "fleet_internal_ops", "configuration": { "model": "claude-sonnet-4-5", "instructions": "You are a research assistant. Cite your sources.", "tools": [] }, "metadata": { "team": "research" }, "createdAt": "2026-05-04T12:00:00.000Z", "updatedAt": "2026-05-04T12:05:00.000Z" } } ``` ### `agent.deleted` Fired when an agent is deleted. The agent record is no longer retrievable, so only the ID is included. | Field | Type | Description | | --------- | -------- | ----------------------- | | `agentId` | `string` | The deleted agent's ID. | ```json theme={null} { "agentId": "agent_abc123" } ``` ## Thread Thread status values are `"idle"`, `"running"`, `"awaiting"`, or `"closed"`. ### `thread.created` Fired when a new thread starts on an agent. The `threadId` is on the envelope. | Field | Type | Description | | --------- | -------- | ---------------------------------------- | | `message` | `string` | The user message that opened the thread. | | `model` | `string` | Model the thread was created against. | ```json theme={null} { "message": "Plan a three-day trip to Lisbon.", "model": "claude-sonnet-4-5" } ``` ### `thread.status.changed` Fired on every thread status transition. Use this when you want to track every state change; use `thread.completed` and `thread.failed` if you only care about runs that finished. | Field | Type | Description | | ---------------- | ---------------- | --------------------------------------------------- | | `status` | `ThreadStatus` | The new status. | | `previousStatus` | `ThreadStatus` | The status before the transition. | | `result` | `string \| null` | Final result if the thread is closing successfully. | | `error` | `string \| null` | Error message if the thread is failing. | ```json theme={null} { "status": "awaiting", "previousStatus": "running", "result": null, "error": null } ``` ### `thread.completed` Fired when a `running` or `awaiting` thread settles back to `idle` with a result. A thread can complete multiple times over its lifetime — once per run that finishes successfully. Always paired with a `thread.status.changed` event for the same transition. | Field | Type | Description | | ---------------- | -------------- | ----------------------------------------------------------------------------------------------------------------------------------------------------------------- | | `status` | `ThreadStatus` | Currently always `"idle"` at runtime, but the contract is typed as the full `ThreadStatus` union — don't write a typed handler that asserts the literal `"idle"`. | | `previousStatus` | `ThreadStatus` | The status before the transition (`"running"` or `"awaiting"`). | | `result` | `string` | The run's final result. | ```json theme={null} { "status": "idle", "previousStatus": "running", "result": "Booked the flight and emailed the itinerary." } ``` ### `thread.failed` Fired when a `running` or `awaiting` thread settles back to `idle` with an error. Like `thread.completed`, this can fire more than once over a thread's lifetime. | Field | Type | Description | | ---------------- | -------------- | ----------------------------------------------------------------------------------------------------------------------------------------------------------------- | | `status` | `ThreadStatus` | Currently always `"idle"` at runtime, but the contract is typed as the full `ThreadStatus` union — don't write a typed handler that asserts the literal `"idle"`. | | `previousStatus` | `ThreadStatus` | The status before the transition (`"running"` or `"awaiting"`). | | `error` | `string` | The error message. | ```json theme={null} { "status": "idle", "previousStatus": "running", "error": "Tool call exceeded retry budget: search_flights" } ``` ## Turn A turn is one user-message-and-response exchange inside a thread. ### `turn.created` Fired when a new turn starts. `seq` orders turns within a thread. | Field | Type | Description | | ----------- | -------- | -------------------------------------------- | | `turnId` | `string` | The new turn's ID. | | `seq` | `number` | Monotonic sequence number within the thread. | | `message` | `string` | The user message that opened this turn. | | `startedAt` | `string` | ISO 8601 timestamp the turn started. | ```json theme={null} { "turnId": "turn_01HZX9F4G2N3K7B0Q1WVYE6T8M", "seq": 3, "message": "Now book the cheapest of the three.", "startedAt": "2026-05-04T12:00:00.000Z" } ``` ### `turn.completed` Fired when a turn finishes successfully. | Field | Type | Description | | ------------- | -------------------- | -------------------------------------------------------------------------------- | | `turnId` | `string` | The turn's ID. | | `seq` | `number` | Sequence number within the thread. | | `status` | `"completed"` | Always `"completed"` for this event. | | `result` | `string \| null` | The turn's result text, if any. | | `error` | `string \| null` | Always `null` for completed turns; included for shape parity with `turn.failed`. | | `tokenUsage` | `TokenUsage \| null` | Input and output token counts, when available. | | `completedAt` | `string` | ISO 8601 timestamp the turn finished. | ```json theme={null} { "turnId": "turn_01HZX9F4G2N3K7B0Q1WVYE6T8M", "seq": 3, "status": "completed", "result": "Booked TAP flight TP202 for $312.", "error": null, "tokenUsage": { "inputTokens": 1842, "outputTokens": 256 }, "completedAt": "2026-05-04T12:00:08.000Z" } ``` ### `turn.failed` Fired when a turn ends in failure. Same shape as `turn.completed` with a `"failed"` status and a populated `error`. | Field | Type | Description | | ------------- | -------------------- | --------------------------------------- | | `turnId` | `string` | The turn's ID. | | `seq` | `number` | Sequence number within the thread. | | `status` | `"failed"` | Always `"failed"` for this event. | | `result` | `string \| null` | Partial result, if any. | | `error` | `string \| null` | Error message describing the failure. | | `tokenUsage` | `TokenUsage \| null` | Token usage up to the point of failure. | | `completedAt` | `string` | ISO 8601 timestamp the turn ended. | ```json theme={null} { "turnId": "turn_01HZX9F4G2N3K7B0Q1WVYE6T8M", "seq": 3, "status": "failed", "result": null, "error": "Tool call exceeded retry budget: book_flight", "tokenUsage": { "inputTokens": 1842, "outputTokens": 64 }, "completedAt": "2026-05-04T12:00:08.000Z" } ``` ## Message ### `message.created` Fired when a new user or assistant message is appended to a thread. Tool messages and intermediate assistant content do not fire this event — use thread streaming for the full transcript. **The message body is not included in this payload.** You only get the role and metadata; to read what was actually said, fetch the message via the API or subscribe to thread streaming. | Field | Type | Description | | ----------- | ----------------------- | -------------------------------------------- | | `turnId` | `string \| null` | Turn the message belongs to, if any. | | `seq` | `number` | Monotonic sequence number within the thread. | | `role` | `"user" \| "assistant"` | Who authored the message. | | `createdAt` | `string` | ISO 8601 timestamp the message was created. | ```json theme={null} { "turnId": "turn_01HZX9F4G2N3K7B0Q1WVYE6T8M", "seq": 7, "role": "assistant", "createdAt": "2026-05-04T12:00:08.000Z" } ``` ## Approval ### `approval.requested` Fired when a tool call requires human approval before running. Surface the approval to a human and resolve it via the API; the resolution drives an `approval.resolved` event. | Field | Type | Description | | ---------- | ------------------------------ | --------------------------------------------------------- | | `approval` | `PublicWebhookApprovalRequest` | The approval request, including tool, input, and timeout. | ```json theme={null} { "approval": { "id": "approval_01HZX9F4G2N3K7B0Q1WVYE6T8M", "threadId": "thread_xyz789", "turnId": "turn_01HZX9F4G2N3K7B0Q1WVYE6T8M", "toolName": "book_flight", "runtimeToolName": "book_flight", "input": { "carrier": "TAP", "flightNumber": "TP202", "priceUsd": 312 }, "toolUseId": "toolu_01ABC", "toolIndex": 0, "status": "pending", "timeoutMs": 3600000, "timeoutAt": "2026-05-04T13:00:00.000Z", "createdAt": "2026-05-04T12:00:00.000Z", "resolvedAt": null } } ``` ### `approval.resolved` Fired when an approval reaches a terminal state — approved by a human, denied, timed out, or cancelled because the thread moved on. | Field | Type | Description | | ------------ | ------------------------------------------------------ | ------------------------------ | | `approvalId` | `string` | ID of the resolved approval. | | `decision` | `"approved" \| "denied" \| "timed_out" \| "cancelled"` | How the approval was resolved. | ```json theme={null} { "approvalId": "approval_01HZX9F4G2N3K7B0Q1WVYE6T8M", "decision": "approved" } ``` ### `approval.granted` Fired when a user grants standing approval for a tool, scoped to either a single thread or the whole agent. Subsequent calls to that tool within the scope skip the approval step. | Field | Type | Description | | ----------- | --------------------- | -------------------------------------------------------- | | `scope` | `"thread" \| "agent"` | Scope the grant applies to. | | `toolName` | `string` | Tool the grant covers. | | `threadId?` | `string` | Thread the grant applies to, when `scope` is `"thread"`. | | `grantId` | `string` | ID of the new grant. | ```json theme={null} { "scope": "thread", "toolName": "book_flight", "threadId": "thread_xyz789", "grantId": "grant_01HZX9F4G2N3K7B0Q1WVYE6T8M" } ``` ## Schedule ### `schedule.created` Fired when an agent creates a schedule via the `agent.schedule.*` namespace. | Field | Type | Description | | ------------ | -------- | -------------------------------------------------- | | `scheduleId` | `string` | The new schedule's ID. | | `name` | `string` | Human-readable name. | | `cron?` | `string` | Cron expression, when the schedule is recurring. | | `runAt?` | `string` | ISO 8601 timestamp, when the schedule is one-shot. | ```json theme={null} { "scheduleId": "sched_01HZX9F4G2N3K7B0Q1WVYE6T8M", "name": "Weekly research digest", "cron": "0 9 * * MON" } ``` ### `schedule.deleted` Fired when a schedule is removed. | Field | Type | Description | | ------------ | -------- | -------------------------- | | `scheduleId` | `string` | The removed schedule's ID. | ```json theme={null} { "scheduleId": "sched_01HZX9F4G2N3K7B0Q1WVYE6T8M" } ``` ### `schedule.triggered` Fired when a schedule fires and starts a thread. The new thread's ID is in the payload; the envelope's `threadId` also points at it. | Field | Type | Description | | ------------ | -------- | ---------------------------- | | `scheduleId` | `string` | Schedule that fired. | | `threadId` | `string` | Thread the schedule started. | ```json theme={null} { "scheduleId": "sched_01HZX9F4G2N3K7B0Q1WVYE6T8M", "threadId": "thread_xyz789" } ``` ## Connection ### `connection.attached` Fired when a connection is attached to an agent. Pair this with `connection.detached` to keep your own view of which integrations the agent currently has. | Field | Type | Description | | -------------- | -------- | -------------------------------------------------- | | `connectionId` | `string` | Connection that was attached. | | `provider` | `string` | Provider identifier, e.g. `"google"` or `"slack"`. | ```json theme={null} { "connectionId": "conn_01HZX9F4G2N3K7B0Q1WVYE6T8M", "provider": "google" } ``` ### `connection.detached` Fired when a connection is removed from an agent. | Field | Type | Description | | -------------- | -------- | ----------------------------- | | `connectionId` | `string` | Connection that was detached. | | `provider` | `string` | Provider identifier. | ```json theme={null} { "connectionId": "conn_01HZX9F4G2N3K7B0Q1WVYE6T8M", "provider": "google" } ``` ## Test ### `webhook.test` Synthetic event fired only by `POST /fleets/{fleetId}/webhooks/{id}/test`. The payload `data` is always `{}`. This event is **not** subscribable — you cannot include `"webhook.test"` in a subscription's `events` array on create or update — and it does not appear in `ALL_WEBHOOK_EVENT_TYPES`. Treat it as a probe for verifying the receive path; ignore unknown event types in your handler so a future test event does not fail closed. ```json theme={null} {} ``` # Building your first agent Source: https://docs.cerca.dev/guides/building-your-first-agent A compact path from API key to a useful long-running workspace. Start with the product boundary. Decide what one agent owns: usually one end user, one workspace, or one business process. Then choose the fleet that should supply shared defaults for that class of agent. Create the agent with a small set of instructions and only the tools it needs at first. A practical first allowlist is `web.*` for research, `memory.*` for durable context, and `sandbox.*` when the agent should inspect files or run commands. If the agent needs your product data, add a fleet-owned tool and expose it with a namespace wildcard like `docs.*`. Seed stable context before asking the agent to work. Use user-owned context for facts your application or user controls, and agent-owned context for notes the agent can maintain as it learns. When you start the first thread, stream events and show approvals clearly. Approval prompts are part of the product experience, not an implementation detail. After the thread finishes, inspect the files, logs, tool outputs, and memory changes. Then tighten the configuration: remove tools the agent did not need, add approval gates around side effects, and move repeated instructions up to the fleet. # Receiving webhooks Source: https://docs.cerca.dev/guides/receiving-webhooks Subscribe to events, verify deliveries, and handle retries idempotently. This guide walks through the full receive path: create a subscription, accept a delivery, verify the signature, dedupe retries, and round-trip a synthetic `webhook.test` event before going live. For the mental model — fleet scope, delivery semantics, ordering — see [Webhooks](/concepts/webhooks). ## Subscribe Create the subscription against the fleet that owns the agents you want to hear about. Pass the HTTPS endpoint that will receive deliveries and, optionally, the events to filter to. Omit `events` to subscribe to every non-test event. ```sh theme={null} curl https://api.cerca.dev/fleets/$FLEET_ID/webhooks \ -H "Authorization: Bearer $CERCA_API_KEY" \ -H "Content-Type: application/json" \ -d '{ "url": "https://example.com/webhooks/cerca", "events": ["thread.completed", "approval.requested"] }' ``` The response contains the subscription metadata plus the signing secret: ```json theme={null} { "id": "webhook_abc123", "fleetId": "fleet_internal_ops", "url": "https://example.com/webhooks/cerca", "events": ["thread.completed", "approval.requested"], "enabled": true, "createdAt": "2026-05-04T12:00:00.000Z", "updatedAt": "2026-05-04T12:00:00.000Z", "secret": "whsec_abc123" } ``` The `secret` field is returned **only on create and rotate**. Store it somewhere you can read from your receiver — a secrets manager, Workers secret, or environment variable. Cerca will not show it again. ## Receive Every delivery is a `POST` with a JSON body and four headers you care about: | Header | Description | | --------------------- | ------------------------------------------------------------------ | | `X-Agent-Event` | The event type, e.g. `thread.completed`. | | `X-Agent-Delivery-Id` | Stable ID for this delivery. The same ID is reused across retries. | | `X-Agent-Timestamp` | Unix seconds the signature was computed at. | | `X-Agent-Signature` | `sha256=` HMAC of `{timestamp}.{payload}`. | A minimal Cloudflare Worker handler looks like this: ```ts theme={null} export default { async fetch(request: Request, env: { CERCA_WEBHOOK_SECRET: string }) { if (request.method !== "POST") { return new Response("Method not allowed", { status: 405 }); } const signatureHeader = request.headers.get("X-Agent-Signature"); const timestamp = request.headers.get("X-Agent-Timestamp"); const deliveryId = request.headers.get("X-Agent-Delivery-Id"); const eventType = request.headers.get("X-Agent-Event"); const payload = await request.text(); if (!signatureHeader || !timestamp || !deliveryId || !eventType) { return new Response("Missing webhook headers", { status: 400 }); } const ok = await verifyWebhook({ payload, signatureHeader, timestamp, secret: env.CERCA_WEBHOOK_SECRET, }); if (!ok) { return new Response("Invalid signature", { status: 401 }); } const event = JSON.parse(payload); await handleEvent({ eventType, deliveryId, event }); return new Response(null, { status: 204 }); }, }; ``` Return a `2xx` status to acknowledge the delivery. The runtime retries once on network errors, `429` responses, and `5xx` responses; other non-2xx responses (`400`, `401`, `403`, `404`, etc.) are recorded as a failure with no retry, so use `5xx` if you want the runtime to try again. ## Verify The signing string is `${timestamp}.${payload}` — the value of `X-Agent-Timestamp`, a single dot, and the raw request body. Strip the `sha256=` prefix from the header value before comparing, reject deliveries whose timestamp drifts too far (5 minutes is a reasonable default), and compare in constant time so a bad signature does not leak timing. ```ts theme={null} async function verifyWebhook(input: { payload: string; signatureHeader: string; timestamp: string; secret: string; }): Promise { const skewSeconds = Math.abs( Math.floor(Date.now() / 1000) - Number(input.timestamp), ); if (!Number.isFinite(skewSeconds) || skewSeconds > 5 * 60) { return false; } const provided = input.signatureHeader.startsWith("sha256=") ? input.signatureHeader.slice("sha256=".length) : input.signatureHeader; const key = await crypto.subtle.importKey( "raw", new TextEncoder().encode(input.secret), { name: "HMAC", hash: "SHA-256" }, false, ["sign"], ); const signed = await crypto.subtle.sign( "HMAC", key, new TextEncoder().encode(`${input.timestamp}.${input.payload}`), ); const expected = Array.from(new Uint8Array(signed)) .map((byte) => byte.toString(16).padStart(2, "0")) .join(""); return constantTimeEqual(expected, provided); } function constantTimeEqual(a: string, b: string): boolean { // SHA-256 hex is always 64 chars, so the expected length is public and // the early-return does not leak secret-dependent timing here. if (a.length !== b.length) { return false; } let mismatch = 0; for (let i = 0; i < a.length; i += 1) { mismatch |= a.charCodeAt(i) ^ b.charCodeAt(i); } return mismatch === 0; } ``` Verify against the raw body bytes that arrived. Reading the body once and reusing the same string for both the signature check and `JSON.parse` is the safest pattern — re-serializing parsed JSON will not match the signature. ## Handle retries idempotently Deliveries are at-least-once, so your handler must tolerate seeing the same event twice. The `X-Agent-Delivery-Id` header is stable across retries of a given delivery — claim it atomically and skip work you have already done: ```ts theme={null} async function handleEvent(input: { eventType: string; deliveryId: string; event: unknown; }) { const claimed = await claimDeliveryId(input.deliveryId); if (!claimed) { return; } await processEvent(input.eventType, input.event); } ``` Ordering is not guaranteed either. When ordering matters, reconcile against your own state — for example, ignore a `thread.status.changed` event if you have already recorded a later state for that thread. ## Test The test endpoint fires a synthetic `webhook.test` event end-to-end against your URL using the real signing secret. It is the fastest way to confirm the full receive-and-verify path works before any real event fires. ```sh theme={null} curl -X POST https://api.cerca.dev/fleets/$FLEET_ID/webhooks/$WEBHOOK_ID/test \ -H "Authorization: Bearer $CERCA_API_KEY" ``` The response reports the structured delivery result: ```json theme={null} { "success": true, "statusCode": 204, "error": null } ``` A `success: false` result means the runtime could not reach your handler or got a non-`2xx` response. The delivered event has `event: "webhook.test"` and an empty `data` object, so ignore unknown event types in your handler rather than treating them as errors. `webhook.test` is reserved for the test endpoint and cannot be added to a subscription's `events` array on create or update. ## Rotate Rotate the secret if you suspect it leaked, on a regular schedule, or before retiring an old receiver. Rotation issues a new secret and invalidates the old one immediately. ```sh theme={null} curl -X POST https://api.cerca.dev/fleets/$FLEET_ID/webhooks/$WEBHOOK_ID/rotate \ -H "Authorization: Bearer $CERCA_API_KEY" ``` The response shape matches create — the new secret is returned as `secret` and is not retrievable later. To rotate without dropped deliveries, configure your receiver to accept either the old secret or the new one for an overlap window, deploy that change, run the rotate call, and remove the old secret once you have confirmed nothing is still signing with it. # Introduction Source: https://docs.cerca.dev/introduction A cloud-native harness for long-running agents. Cerca is a hosted runtime for agents that need to keep working past a single prompt. Instead of running a harness on your own VM — owning the patches, restarts, recovery semantics, and sandbox — you configure agents over an API and the runtime keeps them executing through model errors, tool failures, and process restarts. Every agent and thread is durable and addressable, so long-running work picks up where it left off through restarts and partial failures. ## What the runtime gives you * **Durable execution and context management.** Threads recover from transient errors — bad LLM responses, timed-out tool calls, dropped connections — without losing state. Old turns are compacted and sub-agents spun up so threads stay responsive as they grow. * **A cloud sandbox.** Each agent has a CLI environment to write and run code in. * **Secure credentials.** Connections (OAuth or API keys) are stored encrypted; tools call through them, so the model never sees raw secrets. * **Approvals.** Tools can be marked approval-required. The runtime pauses on those calls until a decision is recorded. * **Memory.** Agents can write and search structured memory across threads. ## What you provide * **Custom tools** — HTTP endpoints or MCP servers the agent can call. * **Goals and instructions** — system prompt, default model, and standing context the agent can rely on. * **Approval policies** — which tools run autonomously and which need a human. * **Inbound events** — webhooks or schedules that wake an agent up. ## Core objects * **Agent** — a durable workspace, usually one per end-user. Holds configuration, memory, connections, and any threads it has run. * **Thread** — one conversation or run inside an agent. Threads stream events, can pause for approvals, and can spawn sub-threads. * **Fleet** — shared configuration for a group of agents: tools, OAuth apps, default instructions, approval defaults. Configure tools once at the fleet level instead of per agent. ## Next steps * [Quickstart](/quickstart) — verify your API key, create an agent, start a thread. * [Client libraries](/client-libraries) — install the TypeScript, Python, Go, Ruby, or CLI client. * [Authentication](/authentication) — API keys, connections, and credential scoping. * [Agents](/concepts/agents), [threads](/concepts/threads), [tools](/concepts/tools), [approvals](/concepts/approvals), and [webhooks](/concepts/webhooks) for the mental model. * API Reference — exact request shapes and response schemas. # Quickstart Source: https://docs.cerca.dev/quickstart Verify your key, create an agent, and start using the API. Start with an API key from the Cerca app. API-key routes use the production base URL: ```http theme={null} Authorization: Bearer $CERCA_API_KEY ``` If you want to call the API from application code, install the official SDK for your language first. See [Client libraries](/client-libraries) for TypeScript, Python, Go, Ruby, and CLI install commands. First, verify the key and recover the organization it belongs to: ```sh theme={null} curl https://api.cerca.dev/auth/context \ -H "Authorization: Bearer $CERCA_API_KEY" ``` Then list the fleets available to that key and choose the fleet that should own the agent configuration. Fleets are where shared defaults live: custom tools, OAuth apps, approval policies, default model settings, and other configuration you do not want to repeat for every agent. Create one agent per durable workspace. A common pattern is one agent per end user: ```sh theme={null} curl https://api.cerca.dev/agents \ -H "Authorization: Bearer $CERCA_API_KEY" \ -H "Content-Type: application/json" \ -d '{ "userId": "user_finance_ops", "fleetId": "fleet_internal_ops", "configuration": { "instructions": "Help the finance team keep vendor work moving.", "tools": ["web.*", "sandbox.*"] } }' ``` After that, start threads against the agent, stream progress, and resolve approvals when the runtime pauses for a decision. Once the agent is running, the next step is to wire your product into agent activity that happens outside a foreground request — thread completions, approval requests, schedule triggers. Subscribe a URL with [Webhooks](/concepts/webhooks) and follow the [Receiving webhooks](/guides/receiving-webhooks) guide to verify deliveries end-to-end.