# Connect Channel
Source: https://docs.chowder.dev/api-reference/channels/connect
POST /v1/instances/{instance_id}/channels/{channel}/connect
Connect a channel to an instance by providing its configuration.
Connects a channel to the instance. What happens next depends on the channel's connection type:
* **Token-based channels** (like Discord or Telegram): provide the required credentials in `config` and the channel connects immediately.
* **Interactive channels** (like WhatsApp): the connection starts a pairing flow. You'll get back QR data to scan, and the status will be `"awaiting_scan"` until the user completes the process.
Behind the scenes, this writes the configuration to `openclaw.json` and restarts the gateway.
## Authentication
Requires an **org-level API key** or a **scoped token** with the `channels` permission.
## Path Parameters
The ID of the instance.
The channel identifier (e.g. `discord`, `whatsapp`).
## Body Parameters
A key-value map of configuration fields for this channel. Use the [channel info endpoint](/api-reference/channels/info) to discover which fields are required. For interactive channels, you can optionally provide extra config.
## Response
The channel identifier.
Either `"connected"` (channel is live) or `"awaiting_scan"` (interactive channels waiting for user action).
Raw QR code data for interactive channels. Only present when `status` is `"awaiting_scan"`.
Base64-encoded PNG image of the QR code. Only present when `status` is `"awaiting_scan"`.
An optional human-readable status message.
```bash cURL (token-based) theme={"theme":{"light":"github-light","dark":"github-dark-dimmed"}}
curl -X POST https://api.chowder.dev/v1/instances/ins_abc123/channels/discord/connect \
-H "Authorization: Bearer YOUR_API_KEY" \
-H "Content-Type: application/json" \
-d '{
"config": {
"token": "MTIz..."
}
}'
```
```bash cURL (interactive) theme={"theme":{"light":"github-light","dark":"github-dark-dimmed"}}
curl -X POST https://api.chowder.dev/v1/instances/ins_abc123/channels/whatsapp/connect \
-H "Authorization: Bearer YOUR_API_KEY" \
-H "Content-Type: application/json" \
-d '{
"config": {}
}'
```
```json 200 (token-based) theme={"theme":{"light":"github-light","dark":"github-dark-dimmed"}}
{
"channel": "discord",
"status": "connected",
"message": "Channel connected successfully."
}
```
```json 200 (interactive) theme={"theme":{"light":"github-light","dark":"github-dark-dimmed"}}
{
"channel": "whatsapp",
"status": "awaiting_scan",
"qr_data": "2@ABC123...",
"qr_image_base64": "iVBORw0KGgo..."
}
```
# Disconnect Channel
Source: https://docs.chowder.dev/api-reference/channels/disconnect
POST /v1/instances/{instance_id}/channels/{channel}/disconnect
Disconnect a channel from an instance.
Disconnects a channel from the instance. This sets `enabled: false` in the instance configuration and restarts the gateway. The channel's saved config is preserved — you can reconnect later without re-entering credentials.
No request body is needed.
## Authentication
Requires an **org-level API key** or a **scoped token** with the `channels` permission.
## Path Parameters
The ID of the instance.
The channel identifier (e.g. `discord`, `whatsapp`).
## Response
Returns `204 No Content` on success.
```bash cURL theme={"theme":{"light":"github-light","dark":"github-dark-dimmed"}}
curl -X POST https://api.chowder.dev/v1/instances/ins_abc123/channels/discord/disconnect \
-H "Authorization: Bearer YOUR_API_KEY"
```
```text 204 theme={"theme":{"light":"github-light","dark":"github-dark-dimmed"}}
No Content
```
# Get Channel Info
Source: https://docs.chowder.dev/api-reference/channels/info
GET /v1/instances/{instance_id}/channels/{channel}
Get details about a specific channel, including its required configuration fields.
Returns metadata for a specific channel — most importantly, the fields you'll need to provide when connecting it. This endpoint is public and doesn't require authentication, so it's useful for building setup UIs.
## Authentication
None required.
## Path Parameters
The ID of the instance.
The channel identifier (e.g. `discord`, `whatsapp`, `telegram`).
## Response
The channel identifier.
Either `"token"` or `"interactive"`.
A map of field names to human-readable descriptions. These are the fields you'll need to pass in the `config` object when calling the [connect endpoint](/api-reference/channels/connect).
```bash cURL theme={"theme":{"light":"github-light","dark":"github-dark-dimmed"}}
curl -X GET https://api.chowder.dev/v1/instances/ins_abc123/channels/discord
```
```json 200 theme={"theme":{"light":"github-light","dark":"github-dark-dimmed"}}
{
"channel": "discord",
"connection_type": "token",
"required_fields": {
"token": "Your Discord bot token from the Developer Portal."
}
}
```
# List Channels
Source: https://docs.chowder.dev/api-reference/channels/list
GET /v1/instances/{instance_id}/channels
Get all available channels for an instance.
Returns the list of channels available for a given instance, along with their connection type and whether they're currently enabled.
## Authentication
Requires an **org-level API key** or a **scoped token** with the `channels` permission.
## Path Parameters
The ID of the instance to list channels for.
## Response
Returns an array of channel objects.
The channel identifier (e.g. `discord`, `whatsapp`, `telegram`).
Either `"token"` or `"interactive"`. Token-based channels are configured with credentials directly. Interactive channels (like WhatsApp) require a pairing flow such as QR scanning.
Whether this channel is currently active on the instance.
```bash cURL theme={"theme":{"light":"github-light","dark":"github-dark-dimmed"}}
curl -X GET https://api.chowder.dev/v1/instances/ins_abc123/channels \
-H "Authorization: Bearer YOUR_API_KEY"
```
```json 200 theme={"theme":{"light":"github-light","dark":"github-dark-dimmed"}}
[
{
"channel": "discord",
"connection_type": "token",
"enabled": true
},
{
"channel": "whatsapp",
"connection_type": "interactive",
"enabled": false
},
{
"channel": "telegram",
"connection_type": "token",
"enabled": false
}
]
```
# Approve Pairing
Source: https://docs.chowder.dev/api-reference/channels/pair
POST /v1/instances/{instance_id}/channels/{channel}/pair
Approve a DM pairing request for a channel.
Approves a DM pairing request by submitting a pairing code. This is used when a channel requires explicit approval before the bot can interact with a user in direct messages.
## Authentication
Requires an **org-level API key** or a **scoped token** with the `channels` permission.
## Path Parameters
The ID of the instance.
The channel identifier.
## Body Parameters
The pairing code to approve.
## Response
The channel identifier.
The pairing code that was submitted.
The result of the pairing attempt.
Additional output from the pairing process, if any.
```bash cURL theme={"theme":{"light":"github-light","dark":"github-dark-dimmed"}}
curl -X POST https://api.chowder.dev/v1/instances/ins_abc123/channels/whatsapp/pair \
-H "Authorization: Bearer YOUR_API_KEY" \
-H "Content-Type: application/json" \
-d '{
"code": "ABC-1234"
}'
```
```json 200 theme={"theme":{"light":"github-light","dark":"github-dark-dimmed"}}
{
"channel": "whatsapp",
"code": "ABC-1234",
"status": "paired",
"output": "Pairing approved successfully."
}
```
# Get Channel Status
Source: https://docs.chowder.dev/api-reference/channels/status
GET /v1/instances/{instance_id}/channels/{channel}/status
Check whether a channel is enabled and connected.
Returns the current status of a specific channel on an instance — whether it's enabled in the config and whether it's actually connected and running.
## Authentication
Requires an **org-level API key** or a **scoped token** with the `channels` permission.
## Path Parameters
The ID of the instance.
The channel identifier (e.g. `discord`, `whatsapp`).
## Response
The channel identifier.
Whether the channel is enabled in the instance configuration.
Whether the channel is actively connected and running.
Additional status details, if available. The shape of this object varies by channel.
```bash cURL theme={"theme":{"light":"github-light","dark":"github-dark-dimmed"}}
curl -X GET https://api.chowder.dev/v1/instances/ins_abc123/channels/discord/status \
-H "Authorization: Bearer YOUR_API_KEY"
```
```json 200 theme={"theme":{"light":"github-light","dark":"github-dark-dimmed"}}
{
"channel": "discord",
"enabled": true,
"connected": true,
"details": {
"guilds": 3,
"uptime_seconds": 84200
}
}
```
# Delete File
Source: https://docs.chowder.dev/api-reference/files/delete
DELETE /v1/instances/{instance_id}/files
Delete a file or directory from the instance.
Deletes a file or directory at the specified path on the instance. Be careful — this is permanent and there's no undo.
## Authentication
Requires an **org-level API key** or a **scoped token** with the appropriate permission.
## Path Parameters
The ID of the instance.
## Query Parameters
The path of the file or directory to delete, relative to the instance root.
## Response
Returns `204 No Content` on success.
```bash cURL theme={"theme":{"light":"github-light","dark":"github-dark-dimmed"}}
curl -X DELETE "https://api.chowder.dev/v1/instances/ins_abc123/files?path=config/old-config.json" \
-H "Authorization: Bearer YOUR_API_KEY"
```
```text 204 theme={"theme":{"light":"github-light","dark":"github-dark-dimmed"}}
No Content
```
# Download File
Source: https://docs.chowder.dev/api-reference/files/download
GET /v1/instances/{instance_id}/files/download
Download a file from the instance as binary.
Downloads a file from the instance. The response is the raw binary file content with appropriate `Content-Disposition` and `Content-Type` headers set, so it works naturally with browsers and tools like `curl -O`.
## Authentication
Requires an **org-level API key** or a **scoped token** with the appropriate permission.
## Path Parameters
The ID of the instance.
## Query Parameters
The file path to download, relative to the instance root.
## Response
Returns the raw binary file content with a `Content-Disposition: attachment` header.
```bash cURL theme={"theme":{"light":"github-light","dark":"github-dark-dimmed"}}
curl -X GET "https://api.chowder.dev/v1/instances/ins_abc123/files/download?path=assets/logo.png" \
-H "Authorization: Bearer YOUR_API_KEY" \
-O -J
```
```text 200 theme={"theme":{"light":"github-light","dark":"github-dark-dimmed"}}
Binary file content with headers:
Content-Disposition: attachment; filename="logo.png"
Content-Type: image/png
```
# List Directory
Source: https://docs.chowder.dev/api-reference/files/list-directory
GET /v1/instances/{instance_id}/files
List the contents of a directory on the instance.
Lists the files and subdirectories at a given path on the instance. Defaults to the root directory (`.`) if no path is specified.
## Authentication
Requires an **org-level API key** or a **scoped token** with the appropriate permission.
## Path Parameters
The ID of the instance.
## Query Parameters
The directory path to list, relative to the instance root.
## Response
The directory path that was listed.
The file or directory name.
The full path relative to the instance root.
Whether this entry is a directory.
File size in bytes. `0` for directories.
Last modification timestamp (ISO 8601).
Unix-style permission string (e.g. `"-rw-r--r--"`).
```bash cURL theme={"theme":{"light":"github-light","dark":"github-dark-dimmed"}}
curl -X GET "https://api.chowder.dev/v1/instances/ins_abc123/files?path=skills" \
-H "Authorization: Bearer YOUR_API_KEY"
```
```json 200 theme={"theme":{"light":"github-light","dark":"github-dark-dimmed"}}
{
"path": "skills",
"entries": [
{
"name": "web-search",
"path": "skills/web-search",
"is_dir": true,
"size": 0,
"mod_time": "2026-01-15T10:30:00Z",
"permissions": "drwxr-xr-x"
},
{
"name": "openclaw.json",
"path": "skills/openclaw.json",
"is_dir": false,
"size": 1024,
"mod_time": "2026-01-15T09:00:00Z",
"permissions": "-rw-r--r--"
}
]
}
```
# Create Directory
Source: https://docs.chowder.dev/api-reference/files/mkdir
POST /v1/instances/{instance_id}/files/mkdir
Create a new directory on the instance.
Creates a new directory at the specified path on the instance. Parent directories are created automatically if they don't exist (like `mkdir -p`).
## Authentication
Requires an **org-level API key** or a **scoped token** with the appropriate permission.
## Path Parameters
The ID of the instance.
## Query Parameters
The directory path to create, relative to the instance root.
## Response
Returns `204 No Content` on success.
```bash cURL theme={"theme":{"light":"github-light","dark":"github-dark-dimmed"}}
curl -X POST "https://api.chowder.dev/v1/instances/ins_abc123/files/mkdir?path=skills/my-custom-skill" \
-H "Authorization: Bearer YOUR_API_KEY"
```
```text 204 theme={"theme":{"light":"github-light","dark":"github-dark-dimmed"}}
No Content
```
# Move File
Source: https://docs.chowder.dev/api-reference/files/move
POST /v1/instances/{instance_id}/files/move
Move or rename a file or directory on the instance.
Moves (or renames) a file or directory from one path to another on the instance. Works like `mv` — you can use it to rename files in place or move them to a different directory.
## Authentication
Requires an **org-level API key** or a **scoped token** with the appropriate permission.
## Path Parameters
The ID of the instance.
## Body Parameters
The current path of the file or directory, relative to the instance root.
The new path for the file or directory, relative to the instance root.
## Response
Returns `204 No Content` on success.
```bash cURL theme={"theme":{"light":"github-light","dark":"github-dark-dimmed"}}
curl -X POST https://api.chowder.dev/v1/instances/ins_abc123/files/move \
-H "Authorization: Bearer YOUR_API_KEY" \
-H "Content-Type: application/json" \
-d '{
"source": "config/old-name.json",
"destination": "config/new-name.json"
}'
```
```text 204 theme={"theme":{"light":"github-light","dark":"github-dark-dimmed"}}
No Content
```
# Read File
Source: https://docs.chowder.dev/api-reference/files/read
GET /v1/instances/{instance_id}/files/content
Read the text content of a file on the instance.
Returns the text content of a file on the instance. This is meant for text files — if you need to download binary files, use the [download endpoint](/api-reference/files/download) instead.
## Authentication
Requires an **org-level API key** or a **scoped token** with the appropriate permission.
## Path Parameters
The ID of the instance.
## Query Parameters
The file path to read, relative to the instance root.
## Response
The file path that was read.
The text content of the file.
```bash cURL theme={"theme":{"light":"github-light","dark":"github-dark-dimmed"}}
curl -X GET "https://api.chowder.dev/v1/instances/ins_abc123/files/content?path=openclaw.json" \
-H "Authorization: Bearer YOUR_API_KEY"
```
```json 200 theme={"theme":{"light":"github-light","dark":"github-dark-dimmed"}}
{
"path": "openclaw.json",
"content": "{\n \"channels\": {\n \"discord\": {\n \"enabled\": true\n }\n }\n}"
}
```
# Upload File
Source: https://docs.chowder.dev/api-reference/files/upload
POST /v1/instances/{instance_id}/files/upload
Upload a binary file to the instance via multipart form data.
Uploads a file to the instance using multipart form data. This is the way to go for binary files like images, audio, or anything that isn't plain text. For text content, you can also use the simpler [write endpoint](/api-reference/files/write).
## Authentication
Requires an **org-level API key** or a **scoped token** with the appropriate permission.
## Path Parameters
The ID of the instance.
## Form Parameters
The destination file path on the instance, relative to the instance root.
The file to upload (binary).
## Response
Returns `204 No Content` on success.
```bash cURL theme={"theme":{"light":"github-light","dark":"github-dark-dimmed"}}
curl -X POST https://api.chowder.dev/v1/instances/ins_abc123/files/upload \
-H "Authorization: Bearer YOUR_API_KEY" \
-F "path=assets/logo.png" \
-F "file=@/local/path/to/logo.png"
```
```text 204 theme={"theme":{"light":"github-light","dark":"github-dark-dimmed"}}
No Content
```
# Write File
Source: https://docs.chowder.dev/api-reference/files/write
PUT /v1/instances/{instance_id}/files/content
Write text content to a file on the instance.
Writes text content to a file on the instance. If the file already exists, it will be overwritten. If it doesn't exist, it will be created (along with any necessary parent directories).
## Authentication
Requires an **org-level API key** or a **scoped token** with the appropriate permission.
## Path Parameters
The ID of the instance.
## Body Parameters
The file path to write to, relative to the instance root.
The text content to write to the file.
## Response
Returns `204 No Content` on success.
```bash cURL theme={"theme":{"light":"github-light","dark":"github-dark-dimmed"}}
curl -X PUT https://api.chowder.dev/v1/instances/ins_abc123/files/content \
-H "Authorization: Bearer YOUR_API_KEY" \
-H "Content-Type: application/json" \
-d '{
"path": "config/custom.json",
"content": "{\n \"key\": \"value\"\n}"
}'
```
```text 204 theme={"theme":{"light":"github-light","dark":"github-dark-dimmed"}}
No Content
```
# Create Instance
Source: https://docs.chowder.dev/api-reference/instances/create
POST /v1/instances
Spin up a new OpenClaw instance with a cloud sandbox.
Create a new instance. This kicks off a provisioning process that spins up a cloud sandbox, installs OpenClaw, configures your model provider, and starts the gateway. The endpoint returns immediately with `status: "provisioning"` — the sandbox takes roughly **\~90 seconds** to become fully ready.
Poll [Get Instance Status](/api-reference/instances/status) to know when provisioning completes.
Requires an **organization key** (`chd_org_*`). Your org must have an API key configured for the chosen `model_provider` — set one via [Update Organization](/api-reference/organization/update) first.
## Request Body
A name for the instance. 1–128 characters.
Which LLM provider to use. Your org must have an API key set for this provider.
Options: `"anthropic"`, `"openai"`, `"gemini"`
The cloud sandbox provider. Currently only `"sandbox"` is supported.
Which OpenClaw version to deploy. Defaults to `"v1"`.
Optional config overrides passed to the OpenClaw instance. These are dot-path key/value pairs that get applied via `openclaw config set`. Useful for tweaking behavior, setting system prompts, etc.
Preferred region for the sandbox. If not specified, the default region is used.
## Response
Returns `201 Created` with the instance object.
Unique instance ID (UUID).The owning organization's ID.Instance name.
Current status. Will be `"provisioning"` immediately after creation. See the [overview](/api-reference/overview#instance-lifecycle) for all possible states.
The configured model provider.The sandbox provider (e.g. `"sandbox"`).The underlying sandbox ID. `null` until provisioning completes.Public URL for the OpenClaw gateway. `null` until provisioning completes.The deployed OpenClaw version.Any config overrides applied to this instance.The sandbox region, if specified.If status is `"error"`, this explains what went wrong.ISO 8601 timestamp.ISO 8601 timestamp.
```bash cURL theme={"theme":{"light":"github-light","dark":"github-dark-dimmed"}}
curl -X POST https://api.chowder.dev/v1/instances \
-H "Authorization: Bearer chd_org_abc123..." \
-H "Content-Type: application/json" \
-d '{
"name": "my-assistant",
"model_provider": "anthropic"
}'
```
```json 201 theme={"theme":{"light":"github-light","dark":"github-dark-dimmed"}}
{
"id": "f47ac10b-58cc-4372-a567-0e02b2c3d479",
"organization_id": "a1b2c3d4-e5f6-7890-abcd-ef1234567890",
"name": "my-assistant",
"status": "provisioning",
"model_provider": "anthropic",
"sandbox_provider": "sandbox",
"sandbox_id": null,
"gateway_url": null,
"openclaw_version": "v1",
"openclaw_config": null,
"region": null,
"error_message": null,
"created_at": "2026-02-14T16:00:00.000000+00:00",
"updated_at": "2026-02-14T16:00:00.000000+00:00"
}
```
# Delete Instance
Source: https://docs.chowder.dev/api-reference/instances/delete
DELETE /v1/instances/{instance_id}
Permanently delete an instance and destroy its sandbox.
Delete an instance permanently. This destroys the underlying cloud sandbox (including all files and state) and marks the instance as `terminated`. Terminated instances are excluded from [List Instances](/api-reference/instances/list).
This action is **irreversible**.
Requires an **organization key** (`chd_org_*`). Scoped keys cannot delete instances.
## Path Parameters
The ID of the instance to delete.
## Response
Returns `204 No Content` on success. The response body is empty.
```bash cURL theme={"theme":{"light":"github-light","dark":"github-dark-dimmed"}}
curl -X DELETE https://api.chowder.dev/v1/instances/f47ac10b-58cc-4372-a567-0e02b2c3d479 \
-H "Authorization: Bearer chd_org_abc123..."
```
```text 204 theme={"theme":{"light":"github-light","dark":"github-dark-dimmed"}}
(empty response body)
```
# Get Instance
Source: https://docs.chowder.dev/api-reference/instances/get
GET /v1/instances/{instance_id}
Retrieve details for a specific instance.
Fetch the full details of a single instance by its ID.
Accepts an **organization key** (`chd_org_*`) or a **scoped key** (`chd_sk_*`) with `read` permission on this instance.
## Path Parameters
The ID of the instance to retrieve.
## Response
Returns `200 OK` with the instance object. Same shape as [Create Instance](/api-reference/instances/create).
```bash cURL theme={"theme":{"light":"github-light","dark":"github-dark-dimmed"}}
curl https://api.chowder.dev/v1/instances/f47ac10b-58cc-4372-a567-0e02b2c3d479 \
-H "Authorization: Bearer chd_org_abc123..."
```
```json 200 theme={"theme":{"light":"github-light","dark":"github-dark-dimmed"}}
{
"id": "f47ac10b-58cc-4372-a567-0e02b2c3d479",
"organization_id": "a1b2c3d4-e5f6-7890-abcd-ef1234567890",
"name": "my-assistant",
"status": "running",
"model_provider": "anthropic",
"sandbox_provider": "sandbox",
"sandbox_id": "sb_abc123",
"gateway_url": "https://sb-abc123-18789.preview.sandbox.app",
"openclaw_version": "v1",
"openclaw_config": null,
"region": null,
"error_message": null,
"created_at": "2026-02-14T16:00:00.000000+00:00",
"updated_at": "2026-02-14T16:01:30.000000+00:00"
}
```
# List Instances
Source: https://docs.chowder.dev/api-reference/instances/list
GET /v1/instances
List all instances in your organization.
Returns all non-terminated instances in your organization, sorted by creation date (newest first). Terminated instances are automatically excluded.
Requires an **organization key** (`chd_org_*`).
## Response
Returns `200 OK` with an array of instance objects.
Each object in the array has the same shape as the response from [Create Instance](/api-reference/instances/create).
```bash cURL theme={"theme":{"light":"github-light","dark":"github-dark-dimmed"}}
curl https://api.chowder.dev/v1/instances \
-H "Authorization: Bearer chd_org_abc123..."
```
```json 200 theme={"theme":{"light":"github-light","dark":"github-dark-dimmed"}}
[
{
"id": "f47ac10b-58cc-4372-a567-0e02b2c3d479",
"organization_id": "a1b2c3d4-e5f6-7890-abcd-ef1234567890",
"name": "my-assistant",
"status": "running",
"model_provider": "anthropic",
"sandbox_provider": "sandbox",
"sandbox_id": "sb_abc123",
"gateway_url": "https://sb-abc123-18789.preview.sandbox.app",
"openclaw_version": "v1",
"openclaw_config": null,
"region": null,
"error_message": null,
"created_at": "2026-02-14T16:00:00.000000+00:00",
"updated_at": "2026-02-14T16:01:30.000000+00:00"
},
{
"id": "c9876543-21ab-cdef-0123-456789abcdef",
"organization_id": "a1b2c3d4-e5f6-7890-abcd-ef1234567890",
"name": "test-bot",
"status": "stopped",
"model_provider": "openai",
"sandbox_provider": "sandbox",
"sandbox_id": "sb_def456",
"gateway_url": "https://sb-def456-18789.preview.sandbox.app",
"openclaw_version": "v1",
"openclaw_config": null,
"region": null,
"error_message": null,
"created_at": "2026-02-13T10:00:00.000000+00:00",
"updated_at": "2026-02-13T22:00:00.000000+00:00"
}
]
```
# Send Message
Source: https://docs.chowder.dev/api-reference/instances/send-message
POST /v1/instances/{instance_id}/responses
Send a message to an instance and get a response from the AI agent.
Send a message to a running OpenClaw instance. Chowder proxies your request to the instance's gateway, which processes it through the [OpenResponses API](https://github.com/openclaw/openresponses) — an open-source implementation of the OpenAI Responses API format.
The instance must be in `running` status. If it's stopped or provisioning, you'll get a `409 Conflict`.
Accepts an **organization key** (`chd_org_*`) or a **scoped key** (`chd_sk_*`) with `interact` permission on this instance.
## Path Parameters
The ID of the instance to send a message to.
## Request Body
The body follows the OpenAI Responses API format. At minimum you need `model` and `input`.
The model to use for this request. This is passed through to the configured model provider — use model identifiers like `"claude-sonnet-4-20250514"`, `"gpt-4o"`, or `"gemini-2.0-flash"` depending on which provider the instance is set up with.
The user message to send. This is the text input for the AI agent.
You can include any additional fields supported by the OpenResponses API (e.g. `instructions`, `tools`, `previous_response_id` for conversation continuity). These are passed through to the gateway as-is.
## Response
Returns `200 OK` with the OpenResponses API response object.
Unique response ID (e.g. `"resp_abc123"`). Use this as `previous_response_id` in follow-up messages to maintain conversation context.
Always `"response"`.Response status, typically `"completed"`.
Array of output items. Each item has a `type` field. The most common type is `"message"`, which contains the agent's reply.
`"message"``"assistant"`
Array of content blocks. Text responses have `type: "output_text"` with a `text` field containing the actual reply.
The model that generated the response.
Token usage for the request.
Tokens in the input.Tokens in the output.Total tokens used.
```bash cURL theme={"theme":{"light":"github-light","dark":"github-dark-dimmed"}}
curl -X POST https://api.chowder.dev/v1/instances/f47ac10b-58cc-4372-a567-0e02b2c3d479/responses \
-H "Authorization: Bearer chd_org_abc123..." \
-H "Content-Type: application/json" \
-d '{
"model": "claude-sonnet-4-20250514",
"input": "What files are in the current directory?"
}'
```
```json 200 theme={"theme":{"light":"github-light","dark":"github-dark-dimmed"}}
{
"id": "resp_6f8a9b2c4d1e",
"object": "response",
"status": "completed",
"output": [
{
"type": "message",
"role": "assistant",
"content": [
{
"type": "output_text",
"text": "Here are the files in the current directory:\n\n- README.md\n- package.json\n- src/\n- node_modules/"
}
]
}
],
"model": "claude-sonnet-4-20250514",
"usage": {
"input_tokens": 42,
"output_tokens": 38,
"total_tokens": 80
}
}
```
## Conversation Continuity
To have a multi-turn conversation, pass the `id` from the previous response as `previous_response_id` in your next request:
```bash theme={"theme":{"light":"github-light","dark":"github-dark-dimmed"}}
curl -X POST https://api.chowder.dev/v1/instances/f47ac10b-.../responses \
-H "Authorization: Bearer chd_org_abc123..." \
-H "Content-Type: application/json" \
-d '{
"model": "claude-sonnet-4-20250514",
"input": "Now create a new file called hello.txt with the text Hello World",
"previous_response_id": "resp_6f8a9b2c4d1e"
}'
```
The agent retains the full conversation history and can reference previous messages.
# Send Message (Session)
Source: https://docs.chowder.dev/api-reference/instances/send-message-session
POST /v1/instances/{instance_id}/session/{session_id}/responses
Send a message routed to a named session on an instance.
Same as [Send Message](/api-reference/instances/send-message), but routes the request to a specific **named session** on the OpenClaw instance. Sessions let you run multiple independent conversations on the same instance without them sharing context.
The `session_id` is a string you choose — if the session doesn't exist yet, it's created automatically. Use the same `session_id` across requests to maintain continuity within that session.
Accepts an **organization key** (`chd_org_*`) or a **scoped key** (`chd_sk_*`) with `interact` permission on this instance.
## Path Parameters
The ID of the instance to send a message to.
A session identifier you choose. Can be any string — e.g. a user ID, a ticket number, or a UUID. Sessions are created on first use.
## Request Body
Identical to [Send Message](/api-reference/instances/send-message). At minimum, provide `model` and `input`.
The model to use (e.g. `"claude-sonnet-4-20250514"`, `"gpt-4o"`).
The user message to send.
Any additional OpenResponses API fields (e.g. `instructions`, `tools`, `previous_response_id`) are passed through as-is.
## Response
Returns `200 OK` with the same OpenResponses API response format as [Send Message](/api-reference/instances/send-message).
```bash cURL theme={"theme":{"light":"github-light","dark":"github-dark-dimmed"}}
curl -X POST https://api.chowder.dev/v1/instances/f47ac10b-58cc-4372-a567-0e02b2c3d479/session/user-42/responses \
-H "Authorization: Bearer chd_org_abc123..." \
-H "Content-Type: application/json" \
-d '{
"model": "claude-sonnet-4-20250514",
"input": "Summarize the README for me."
}'
```
```json 200 theme={"theme":{"light":"github-light","dark":"github-dark-dimmed"}}
{
"id": "resp_a1b2c3d4e5f6",
"object": "response",
"status": "completed",
"output": [
{
"type": "message",
"role": "assistant",
"content": [
{
"type": "output_text",
"text": "The README describes a Node.js project that..."
}
]
}
],
"model": "claude-sonnet-4-20250514",
"usage": {
"input_tokens": 35,
"output_tokens": 52,
"total_tokens": 87
}
}
```
## When to Use Sessions
Sessions are useful when your instance serves multiple users or conversations:
* **Per-user isolation**: Use the user's ID as the `session_id` so each user gets their own conversation history.
* **Per-ticket support**: Use a ticket or thread ID so the agent maintains context for each support case.
* **A/B testing**: Run different prompts against the same instance in separate sessions.
Without sessions, all messages to an instance share the same default conversation context.
# Start Instance
Source: https://docs.chowder.dev/api-reference/instances/start
POST /v1/instances/{instance_id}/start
Start a stopped instance.
Wake up a stopped instance. This resumes the hibernated sandbox and restarts the OpenClaw gateway. The instance must be in `stopped` status — trying to start an already-running instance returns a `409 Conflict`.
Requires an **organization key** (`chd_org_*`). Scoped keys cannot start instances.
## Path Parameters
The ID of the instance to start.
## Response
Returns `200 OK` with the instance object. The `status` field will be `"running"`.
```bash cURL theme={"theme":{"light":"github-light","dark":"github-dark-dimmed"}}
curl -X POST https://api.chowder.dev/v1/instances/f47ac10b-58cc-4372-a567-0e02b2c3d479/start \
-H "Authorization: Bearer chd_org_abc123..."
```
```json 200 theme={"theme":{"light":"github-light","dark":"github-dark-dimmed"}}
{
"id": "f47ac10b-58cc-4372-a567-0e02b2c3d479",
"organization_id": "a1b2c3d4-e5f6-7890-abcd-ef1234567890",
"name": "my-assistant",
"status": "running",
"model_provider": "anthropic",
"sandbox_provider": "sandbox",
"sandbox_id": "sb_abc123",
"gateway_url": "https://sb-abc123-18789.preview.sandbox.app",
"openclaw_version": "v1",
"openclaw_config": null,
"region": null,
"error_message": null,
"created_at": "2026-02-14T16:00:00.000000+00:00",
"updated_at": "2026-02-14T18:00:00.000000+00:00"
}
```
# Get Instance Status
Source: https://docs.chowder.dev/api-reference/instances/status
GET /v1/instances/{instance_id}/status
Check the current status of an instance.
A lightweight endpoint for checking whether an instance is ready. Useful for polling during provisioning or verifying the gateway is reachable before sending messages.
Accepts an **organization key** (`chd_org_*`) or a **scoped key** (`chd_sk_*`) with `read` permission on this instance.
## Path Parameters
The ID of the instance to check.
## Response
Returns `200 OK` with a minimal status object.
The instance ID.
Current instance status: `"provisioning"`, `"running"`, `"stopped"`, `"error"`, or `"terminated"`.
The public gateway URL if the instance has been provisioned. `null` while provisioning or if the instance errored.
```bash cURL theme={"theme":{"light":"github-light","dark":"github-dark-dimmed"}}
curl https://api.chowder.dev/v1/instances/f47ac10b-58cc-4372-a567-0e02b2c3d479/status \
-H "Authorization: Bearer chd_org_abc123..."
```
```json 200 theme={"theme":{"light":"github-light","dark":"github-dark-dimmed"}}
{
"id": "f47ac10b-58cc-4372-a567-0e02b2c3d479",
"status": "running",
"gateway_url": "https://sb-abc123-18789.preview.sandbox.app"
}
```
# Stop Instance
Source: https://docs.chowder.dev/api-reference/instances/stop
POST /v1/instances/{instance_id}/stop
Stop a running instance and hibernate its sandbox.
Stop a running instance. The OpenClaw gateway is shut down and the sandbox is hibernated — your files and state are preserved, but the instance won't accept messages until you [start](/api-reference/instances/start) it again.
The instance must be in `running` status. Trying to stop an instance that isn't running returns a `409 Conflict`.
Requires an **organization key** (`chd_org_*`). Scoped keys cannot stop instances.
## Path Parameters
The ID of the instance to stop.
## Response
Returns `200 OK` with the instance object. The `status` field will be `"stopped"`.
```bash cURL theme={"theme":{"light":"github-light","dark":"github-dark-dimmed"}}
curl -X POST https://api.chowder.dev/v1/instances/f47ac10b-58cc-4372-a567-0e02b2c3d479/stop \
-H "Authorization: Bearer chd_org_abc123..."
```
```json 200 theme={"theme":{"light":"github-light","dark":"github-dark-dimmed"}}
{
"id": "f47ac10b-58cc-4372-a567-0e02b2c3d479",
"organization_id": "a1b2c3d4-e5f6-7890-abcd-ef1234567890",
"name": "my-assistant",
"status": "stopped",
"model_provider": "anthropic",
"sandbox_provider": "sandbox",
"sandbox_id": "sb_abc123",
"gateway_url": "https://sb-abc123-18789.preview.sandbox.app",
"openclaw_version": "v1",
"openclaw_config": null,
"region": null,
"error_message": null,
"created_at": "2026-02-14T16:00:00.000000+00:00",
"updated_at": "2026-02-14T20:00:00.000000+00:00"
}
```
# Update Instance
Source: https://docs.chowder.dev/api-reference/instances/update
PATCH /v1/instances/{instance_id}
Update an instance's name or OpenClaw configuration.
Update an instance's metadata or runtime configuration. If the instance is running and you change `openclaw_config`, the new config is pushed to the live sandbox immediately — the gateway hot-reloads config changes automatically.
Accepts an **organization key** (`chd_org_*`) or a **scoped key** (`chd_sk_*`) with `configure` permission on this instance.
## Path Parameters
The ID of the instance to update.
## Request Body
All fields are optional. Only include what you want to change.
New name for the instance. 1–128 characters.
OpenClaw configuration overrides. These are applied as dot-path key/value pairs via `openclaw config set`. If the instance is running, changes take effect immediately.
## Response
Returns `200 OK` with the updated instance object.
```bash cURL theme={"theme":{"light":"github-light","dark":"github-dark-dimmed"}}
curl -X PATCH https://api.chowder.dev/v1/instances/f47ac10b-58cc-4372-a567-0e02b2c3d479 \
-H "Authorization: Bearer chd_org_abc123..." \
-H "Content-Type: application/json" \
-d '{
"name": "my-assistant-v2",
"openclaw_config": {
"agent": {
"system_prompt": "You are a helpful coding assistant."
}
}
}'
```
```json 200 theme={"theme":{"light":"github-light","dark":"github-dark-dimmed"}}
{
"id": "f47ac10b-58cc-4372-a567-0e02b2c3d479",
"organization_id": "a1b2c3d4-e5f6-7890-abcd-ef1234567890",
"name": "my-assistant-v2",
"status": "running",
"model_provider": "anthropic",
"sandbox_provider": "sandbox",
"sandbox_id": "sb_abc123",
"gateway_url": "https://sb-abc123-18789.preview.sandbox.app",
"openclaw_version": "v1",
"openclaw_config": {
"agent": {
"system_prompt": "You are a helpful coding assistant."
}
},
"region": null,
"error_message": null,
"created_at": "2026-02-14T16:00:00.000000+00:00",
"updated_at": "2026-02-14T17:00:00.000000+00:00"
}
```
# Create API Key
Source: https://docs.chowder.dev/api-reference/keys/create
POST /v1/keys
Generate a new API key scoped to specific instances and permissions.
Create an API key that grants access to one or more instances within your organization. Every key is scoped with explicit permissions so you can hand out only the access that's actually needed.
## Authorization
Requires an **organization-level** API key in the `Authorization` header.
## Request Body
A human-readable name for the key. Pick something descriptive — you'll thank yourself later when you're staring at a list of keys.
Array of instance IDs this key should have access to.
Array of permission scopes to grant. Valid values:
| Permission | What it unlocks |
| ----------- | ---------------------------------------------- |
| `read` | View instance details and status |
| `interact` | Send messages to the instance |
| `configure` | Modify instance settings and install skills |
| `files` | Read and write files in the instance workspace |
| `channels` | Manage channels (create, update, delete) |
Optional ISO 8601 datetime string. If set, the key automatically becomes inactive after this time. If omitted, the key lives until you revoke it.
## Response
Unique identifier for the key.
The organization this key belongs to.
The name you gave the key.
Always `chd_sk_`. Useful for identifying Chowder keys in your configs.
Whether the key is currently active.
Expiration datetime, if one was set.
When the key was created.
The full API key, prefixed with `chd_sk_`. This is the value you'll use in `Authorization` headers.
**Store the `raw_key` immediately.** This is the only time the full key is returned. We store a hash on our end — there's no way to retrieve the raw value after this response.
```bash theme={"theme":{"light":"github-light","dark":"github-dark-dimmed"}}
curl -X POST https://api.chowder.dev/v1/keys \
-H "Authorization: Bearer chd_sk_your_org_key" \
-H "Content-Type: application/json" \
-d '{
"name": "Production Bot Key",
"instance_ids": ["inst_abc123", "inst_def456"],
"permissions": ["read", "interact", "channels"],
"expires_at": "2026-12-31T23:59:59Z"
}'
```
```json 201 theme={"theme":{"light":"github-light","dark":"github-dark-dimmed"}}
{
"id": "key_9f8a7b6c",
"organization_id": "org_1a2b3c4d",
"name": "Production Bot Key",
"key_prefix": "chd_sk_",
"is_active": true,
"expires_at": "2026-12-31T23:59:59Z",
"created_at": "2026-02-14T12:00:00Z",
"raw_key": "chd_sk_live_a1b2c3d4e5f6g7h8i9j0..."
}
```
# Get API Key
Source: https://docs.chowder.dev/api-reference/keys/get
GET /v1/keys/{key_id}
Retrieve details for a specific API key, including its instance access and permissions.
Look up a single API key by ID. This endpoint returns the full picture — key metadata plus which instances the key can access and with what permissions.
## Authorization
Requires an **organization-level** API key in the `Authorization` header.
## Path Parameters
The ID of the key to retrieve.
## Response
Unique identifier for the key.
The human-readable name assigned at creation.
The key prefix (`chd_sk_`).
Whether the key is currently active.
Expiration datetime, if one was set.
The last time this key was used to authenticate a request.
When the key was created.
The instances this key has access to, along with the granted permissions.
The instance this access grant applies to.
The permission scopes granted for this instance (e.g. `["read", "interact"]`).
```bash theme={"theme":{"light":"github-light","dark":"github-dark-dimmed"}}
curl https://api.chowder.dev/v1/keys/key_9f8a7b6c \
-H "Authorization: Bearer chd_sk_your_org_key"
```
```json 200 theme={"theme":{"light":"github-light","dark":"github-dark-dimmed"}}
{
"id": "key_9f8a7b6c",
"name": "Production Bot Key",
"key_prefix": "chd_sk_",
"is_active": true,
"expires_at": "2026-12-31T23:59:59Z",
"last_used_at": "2026-02-14T08:30:00Z",
"created_at": "2026-02-14T12:00:00Z",
"instances": [
{
"instance_id": "inst_abc123",
"permissions": ["read", "interact", "channels"]
},
{
"instance_id": "inst_def456",
"permissions": ["read", "interact", "channels"]
}
]
}
```
# List API Keys
Source: https://docs.chowder.dev/api-reference/keys/list
GET /v1/keys
Retrieve all API keys belonging to your organization.
Fetch every API key associated with your organization. Handy for auditing access or building a key management UI.
## Authorization
Requires an **organization-level** API key in the `Authorization` header.
## Response
Returns an array of key objects. Note that the raw key value is **never** included — we only store hashes, so what you see is what you get.
Unique identifier for the key.
The organization this key belongs to.
The human-readable name assigned at creation.
The key prefix (`chd_sk_`).
Whether the key is currently active. Revoked or expired keys will show `false`.
Expiration datetime, if one was set.
The last time this key was used to authenticate a request. `null` if the key has never been used.
When the key was created.
```bash theme={"theme":{"light":"github-light","dark":"github-dark-dimmed"}}
curl https://api.chowder.dev/v1/keys \
-H "Authorization: Bearer chd_sk_your_org_key"
```
```json 200 theme={"theme":{"light":"github-light","dark":"github-dark-dimmed"}}
[
{
"id": "key_9f8a7b6c",
"organization_id": "org_1a2b3c4d",
"name": "Production Bot Key",
"key_prefix": "chd_sk_",
"is_active": true,
"expires_at": "2026-12-31T23:59:59Z",
"last_used_at": "2026-02-14T08:30:00Z",
"created_at": "2026-02-14T12:00:00Z"
},
{
"id": "key_3e4f5g6h",
"organization_id": "org_1a2b3c4d",
"name": "Dev Testing Key",
"key_prefix": "chd_sk_",
"is_active": false,
"expires_at": null,
"last_used_at": null,
"created_at": "2026-01-10T09:15:00Z"
}
]
```
# Revoke API Key
Source: https://docs.chowder.dev/api-reference/keys/revoke
DELETE /v1/keys/{key_id}
Permanently deactivate an API key.
Revoke an API key so it can never be used again. This is immediate and irreversible — any request using this key will start failing right away.
If you just need to rotate a key, create a new one first, update your services, then revoke the old one.
## Authorization
Requires an **organization-level** API key in the `Authorization` header.
## Path Parameters
The ID of the key to revoke.
## Response
Returns `204 No Content` on success. No response body.
```bash theme={"theme":{"light":"github-light","dark":"github-dark-dimmed"}}
curl -X DELETE https://api.chowder.dev/v1/keys/key_9f8a7b6c \
-H "Authorization: Bearer chd_sk_your_org_key"
```
```bash 204 theme={"theme":{"light":"github-light","dark":"github-dark-dimmed"}}
# No content
```
# Get Organization
Source: https://docs.chowder.dev/api-reference/organization/get
GET /v1/organization
Retrieve details about your organization.
Fetch the current state of your organization, including which model providers are configured, your subscription tier, and how many instances you're running.
Requires an **organization key** (`chd_org_*`). Scoped keys cannot access this endpoint.
## Response
Returns `200 OK` with the organization object.
Unique organization ID (UUID).Your organization's name.
Providers that have API keys configured (e.g. `["anthropic", "openai"]`). The actual keys are never exposed.
Current plan — `"shrimp"`, `"lobster"`, etc.Maximum number of active instances your plan allows.How many active (non-terminated) instances you currently have.ISO 8601 timestamp.ISO 8601 timestamp.
```bash cURL theme={"theme":{"light":"github-light","dark":"github-dark-dimmed"}}
curl https://api.chowder.dev/v1/organization \
-H "Authorization: Bearer chd_org_abc123..."
```
```json 200 theme={"theme":{"light":"github-light","dark":"github-dark-dimmed"}}
{
"id": "a1b2c3d4-e5f6-7890-abcd-ef1234567890",
"name": "Acme Corp",
"model_providers": ["anthropic", "openai"],
"subscription_tier": "shrimp",
"instance_limit": 1,
"instance_count": 1,
"created_at": "2026-02-14T12:00:00.000000+00:00",
"updated_at": "2026-02-14T14:30:00.000000+00:00"
}
```
# Sign Up
Source: https://docs.chowder.dev/api-reference/organization/signup
POST /v1/organization/signup
Create a new organization and get your API key.
This is the starting point for using Chowder. Create an organization and you'll receive an org-level API key (`chd_org_*`) that grants full access to everything under that org.
No authentication is required for this endpoint — it's how you *get* your first key.
## Request Body
The name for your organization. Must be between 1 and 128 characters.
## Response
Returns `201 Created` with the new organization and your API key.
The `api_key` value is shown **only once** in this response. Store it somewhere safe — you won't be able to retrieve it again.
The created organization.
Unique organization ID (UUID).The org name you provided.List of model providers with API keys configured. Empty initially.Current plan. Defaults to `"shrimp"`.Max number of active instances allowed.Current number of active instances.ISO 8601 timestamp.ISO 8601 timestamp.
Your org-level API key, prefixed with `chd_org_`. Use this as a Bearer token for all subsequent requests.
```bash cURL theme={"theme":{"light":"github-light","dark":"github-dark-dimmed"}}
curl -X POST https://api.chowder.dev/v1/organization/signup \
-H "Content-Type: application/json" \
-d '{
"name": "Acme Corp"
}'
```
```json 201 theme={"theme":{"light":"github-light","dark":"github-dark-dimmed"}}
{
"organization": {
"id": "a1b2c3d4-e5f6-7890-abcd-ef1234567890",
"name": "Acme Corp",
"model_providers": [],
"subscription_tier": "shrimp",
"instance_limit": 1,
"instance_count": 0,
"created_at": "2026-02-14T12:00:00.000000+00:00",
"updated_at": "2026-02-14T12:00:00.000000+00:00"
},
"api_key": "chd_org_abc123def456ghi789jkl012mno345pqr678"
}
```
# Update Organization
Source: https://docs.chowder.dev/api-reference/organization/update
PATCH /v1/organization
Update your organization's name or model provider API keys.
Update your organization settings. You can change the name, add model provider API keys, or remove existing ones. All fields are optional — only include what you want to change.
Requires an **organization key** (`chd_org_*`). Scoped keys cannot access this endpoint.
## Request Body
New name for your organization. 1–128 characters.
A map of provider name to API key. Keys are **merged** with your existing configuration — you don't need to resend providers you've already set up.
Set a provider's value to an empty string `""` to remove it.
Supported providers: `anthropic`, `openai`, `gemini`.
## Response
Returns `200 OK` with the updated organization object.
Unique organization ID (UUID).Organization name.Providers with active API keys. Actual keys are never returned.Current plan.Max active instances allowed.Current active instance count.ISO 8601 timestamp.ISO 8601 timestamp.
```bash cURL theme={"theme":{"light":"github-light","dark":"github-dark-dimmed"}}
curl -X PATCH https://api.chowder.dev/v1/organization \
-H "Authorization: Bearer chd_org_abc123..." \
-H "Content-Type: application/json" \
-d '{
"name": "Acme Corp (Renamed)",
"model_api_keys": {
"anthropic": "sk-ant-api03-...",
"openai": "sk-proj-..."
}
}'
```
```json 200 theme={"theme":{"light":"github-light","dark":"github-dark-dimmed"}}
{
"id": "a1b2c3d4-e5f6-7890-abcd-ef1234567890",
"name": "Acme Corp (Renamed)",
"model_providers": ["anthropic", "openai"],
"subscription_tier": "shrimp",
"instance_limit": 1,
"instance_count": 1,
"created_at": "2026-02-14T12:00:00.000000+00:00",
"updated_at": "2026-02-14T15:00:00.000000+00:00"
}
```
# API Overview
Source: https://docs.chowder.dev/api-reference/overview
Everything you need to know before making your first Chowder API call.
## Base URL
All API requests go to:
```
https://api.chowder.dev/v1
```
Every path in this reference is relative to that base.
## Authentication
Chowder uses **Bearer token** authentication. Include your API key in the `Authorization` header on every request (except [Signup](/api-reference/organization/signup), which doesn't require auth).
```bash theme={"theme":{"light":"github-light","dark":"github-dark-dimmed"}}
Authorization: Bearer chd_org_abc123...
```
There are two key types:
| Prefix | Type | Access |
| ----------- | ---------------- | --------------------------------------------------------------------------------------------- |
| `chd_org_*` | Organization key | Full access to all endpoints and instances in your org. |
| `chd_sk_*` | Scoped key | Restricted to specific instances with granular permissions (`read`, `interact`, `configure`). |
You get an org key when you sign up. Scoped keys are created separately and are useful for giving external apps limited access to individual instances.
## Content Type
All request and response bodies are JSON. Set the header:
```
Content-Type: application/json
```
## Error Format
When something goes wrong, the API returns a JSON body with a `detail` field:
```json theme={"theme":{"light":"github-light","dark":"github-dark-dimmed"}}
{
"detail": "Instance not found."
}
```
Some errors (like gateway proxy failures) return a nested object:
```json theme={"theme":{"light":"github-light","dark":"github-dark-dimmed"}}
{
"detail": {
"error": "Gateway unavailable – the sandbox may have been stopped or deleted. Try restarting the instance."
}
}
```
## Status Codes
| Code | Meaning |
| ----- | ---------------------------------------------------------------------------------------------------------------- |
| `200` | Success. The response body contains the requested resource. |
| `201` | Created. A new resource was created and is returned in the body. |
| `204` | No Content. The request succeeded but there's nothing to return (e.g. delete). |
| `400` | Bad Request. Something is wrong with your input — check the `detail` message. |
| `401` | Unauthorized. Missing or invalid API key. |
| `403` | Forbidden. Your key doesn't have permission for this action. |
| `404` | Not Found. The resource doesn't exist, or it belongs to a different org. |
| `409` | Conflict. The resource is in a state that prevents this action (e.g. starting an already-running instance). |
| `422` | Validation Error. The request body failed schema validation. |
| `500` | Internal Server Error. Something broke on our side — please reach out if it persists. |
| `502` | Bad Gateway. The instance's sandbox or gateway is unreachable. Usually means the sandbox was stopped or deleted. |
## Rate Limits
There are **no rate limits** currently enforced. Be reasonable — we reserve the right to introduce limits in the future if needed.
## Instance Lifecycle
When you create an instance, it goes through a provisioning phase (\~90 seconds) where a cloud sandbox is spun up and configured. You can poll the [status endpoint](/api-reference/instances/status) to know when it's ready.
Possible instance statuses:
| Status | Description |
| -------------- | ----------------------------------------------------------- |
| `provisioning` | The sandbox is being created and configured. Not ready yet. |
| `running` | The instance is live and accepting messages. |
| `stopped` | The sandbox is hibernated. Start it to resume. |
| `error` | Provisioning failed. Check `error_message` on the instance. |
| `terminated` | The instance was deleted. It won't appear in list results. |
# Get Skill Info
Source: https://docs.chowder.dev/api-reference/skills/info
GET /v1/instances/{instance_id}/skills/{skill_name}
Get details about a specific skill.
Returns metadata for a specific skill installed on the instance.
## Authentication
Requires an **org-level API key** or a **scoped token** with the `configure` permission.
## Path Parameters
The ID of the instance.
The name of the skill (e.g. `web-search`).
## Response
The skill's identifier.
A short description of the skill.
Where the skill was installed from.
The filesystem path to the skill on the instance.
Whether the skill is fully configured and operational.
```bash cURL theme={"theme":{"light":"github-light","dark":"github-dark-dimmed"}}
curl -X GET https://api.chowder.dev/v1/instances/ins_abc123/skills/web-search \
-H "Authorization: Bearer YOUR_API_KEY"
```
```json 200 theme={"theme":{"light":"github-light","dark":"github-dark-dimmed"}}
{
"name": "web-search",
"description": "Search the web and return summarized results.",
"source": "clawhub",
"path": "/skills/web-search",
"ready": true
}
```
# Install Skill
Source: https://docs.chowder.dev/api-reference/skills/install
POST /v1/instances/{instance_id}/skills/install
Install a skill from ClawHub onto an instance.
Installs a skill from ClawHub onto the instance. Under the hood, this runs `npx clawhub install` with the given slug. If the skill requires environment variables, you can provide them upfront in the `env` field — or skip them and configure later via the [update config endpoint](/api-reference/skills/update-config).
## Authentication
Requires an **org-level API key** or a **scoped token** with the `configure` permission.
## Path Parameters
The ID of the instance.
## Body Parameters
The ClawHub slug identifying the skill to install (e.g. `web-search`).
Optional environment variables required by the skill. Keys are variable names, values are strings.
## Response
The name of the installed skill.
Either `"installed"` (fully ready) or `"installed_missing_env"` (installed but needs env vars to function).
Raw output from the installation process.
The environment variables that were successfully applied, if any.
A list of environment variable names that the skill still needs, if any.
A human-readable status message.
```bash cURL theme={"theme":{"light":"github-light","dark":"github-dark-dimmed"}}
curl -X POST https://api.chowder.dev/v1/instances/ins_abc123/skills/install \
-H "Authorization: Bearer YOUR_API_KEY" \
-H "Content-Type: application/json" \
-d '{
"slug": "web-search",
"env": {
"SEARCH_API_KEY": "sk-..."
}
}'
```
```json 200 (fully installed) theme={"theme":{"light":"github-light","dark":"github-dark-dimmed"}}
{
"skill": "web-search",
"status": "installed",
"output": "Successfully installed web-search@1.2.0",
"env_applied": {
"SEARCH_API_KEY": "sk-..."
},
"message": "Skill installed and ready to use."
}
```
```json 200 (missing env) theme={"theme":{"light":"github-light","dark":"github-dark-dimmed"}}
{
"skill": "image-gen",
"status": "installed_missing_env",
"output": "Successfully installed image-gen@0.9.1",
"required_env": ["OPENAI_API_KEY"],
"message": "Skill installed but requires environment variables to function."
}
```
# List Skills
Source: https://docs.chowder.dev/api-reference/skills/list
GET /v1/instances/{instance_id}/skills
List all skills available to an instance.
Returns the skills available on an instance. By default this includes all skills — both installed and uninstalled. Use the `eligible_only` query parameter to filter down to just the ones that are ready to go.
## Authentication
Requires an **org-level API key** or a **scoped token** with the `configure` permission.
## Path Parameters
The ID of the instance.
## Query Parameters
When `true`, only returns skills that are fully installed and ready to use.
## Response
Returns an array of skill objects.
The skill's identifier.
A short description of what the skill does.
Where the skill was installed from (e.g. `clawhub`, `local`).
Whether the skill is fully configured and ready to use.
```bash cURL theme={"theme":{"light":"github-light","dark":"github-dark-dimmed"}}
curl -X GET "https://api.chowder.dev/v1/instances/ins_abc123/skills?eligible_only=true" \
-H "Authorization: Bearer YOUR_API_KEY"
```
```json 200 theme={"theme":{"light":"github-light","dark":"github-dark-dimmed"}}
[
{
"name": "web-search",
"description": "Search the web and return summarized results.",
"source": "clawhub",
"ready": true
},
{
"name": "image-gen",
"description": "Generate images from text prompts.",
"source": "clawhub",
"ready": true
}
]
```
# Uninstall Skill
Source: https://docs.chowder.dev/api-reference/skills/uninstall
DELETE /v1/instances/{instance_id}/skills/{skill_name}
Remove a skill from an instance.
Uninstalls a skill from the instance. This removes the skill files and its configuration. If you just want to disable a skill without removing it, use the [update config endpoint](/api-reference/skills/update-config) to set `enabled: false` instead.
## Authentication
Requires an **org-level API key** or a **scoped token** with the `configure` permission.
## Path Parameters
The ID of the instance.
The name of the skill to uninstall.
## Response
Returns `204 No Content` on success.
```bash cURL theme={"theme":{"light":"github-light","dark":"github-dark-dimmed"}}
curl -X DELETE https://api.chowder.dev/v1/instances/ins_abc123/skills/web-search \
-H "Authorization: Bearer YOUR_API_KEY"
```
```text 204 theme={"theme":{"light":"github-light","dark":"github-dark-dimmed"}}
No Content
```
# Update Skill Config
Source: https://docs.chowder.dev/api-reference/skills/update-config
PATCH /v1/instances/{instance_id}/skills/{skill_name}
Update the configuration for an installed skill.
Updates the configuration of an installed skill. You can enable or disable it, set an API key, update environment variables, or adjust skill-specific config — all in a single call. Only the fields you include will be updated.
## Authentication
Requires an **org-level API key** or a **scoped token** with the `configure` permission.
## Path Parameters
The ID of the instance.
The name of the skill to configure.
## Body Parameters
Enable or disable the skill without uninstalling it.
Set an API key for the skill, if it requires one.
Environment variables to set for the skill. Keys are variable names, values are strings.
Skill-specific configuration options. The shape of this object depends on the skill.
## Response
Returns `204 No Content` on success.
```bash cURL theme={"theme":{"light":"github-light","dark":"github-dark-dimmed"}}
curl -X PATCH https://api.chowder.dev/v1/instances/ins_abc123/skills/web-search \
-H "Authorization: Bearer YOUR_API_KEY" \
-H "Content-Type: application/json" \
-d '{
"enabled": true,
"env": {
"SEARCH_API_KEY": "sk-new-key..."
},
"config": {
"max_results": 5
}
}'
```
```text 204 theme={"theme":{"light":"github-light","dark":"github-dark-dimmed"}}
No Content
```
# Create Checkout Session
Source: https://docs.chowder.dev/api-reference/subscription/checkout
POST /v1/subscription/checkout
Generate a Stripe Checkout URL to upgrade your subscription.
Start a subscription upgrade by generating a Stripe Checkout session. You'll get back a URL — redirect your user there to complete payment. Once they do, a webhook fires on our end and your organization's tier is updated automatically. No polling required.
## Authorization
Requires an **organization-level** API key in the `Authorization` header.
## Request Body
The tier to upgrade to. Must be one of `crab` or `lobster`. If you need whale-tier access, [reach out to us directly](https://chowder.dev/contact).
## Response
A Stripe Checkout URL. Redirect the user here to complete payment.
```bash theme={"theme":{"light":"github-light","dark":"github-dark-dimmed"}}
curl -X POST https://api.chowder.dev/v1/subscription/checkout \
-H "Authorization: Bearer chd_sk_your_org_key" \
-H "Content-Type: application/json" \
-d '{
"tier": "lobster"
}'
```
```json 200 theme={"theme":{"light":"github-light","dark":"github-dark-dimmed"}}
{
"url": "https://checkout.stripe.com/c/pay/cs_live_a1b2c3..."
}
```
# Get Subscription
Source: https://docs.chowder.dev/api-reference/subscription/get
GET /v1/subscription
Retrieve your organization's current subscription tier and usage.
Check what plan your organization is on and how many instances you're using. Useful for building billing dashboards or enforcing limits in your own application logic.
## Authorization
Requires an **organization-level** API key in the `Authorization` header.
## Response
Your current subscription tier. One of `shrimp`, `crab`, `lobster`, or `whale`.
The maximum number of instances allowed on this tier. `null` means unlimited (whale tier — you're living large).
How many instances your organization currently has.
Your Stripe customer ID, if a billing relationship exists.
Your Stripe subscription ID, if you're on a paid plan.
```bash theme={"theme":{"light":"github-light","dark":"github-dark-dimmed"}}
curl https://api.chowder.dev/v1/subscription \
-H "Authorization: Bearer chd_sk_your_org_key"
```
```json 200 theme={"theme":{"light":"github-light","dark":"github-dark-dimmed"}}
{
"tier": "lobster",
"instance_limit": 25,
"instance_count": 12,
"stripe_customer_id": "cus_abc123",
"stripe_subscription_id": "sub_def456"
}
```
# Create Portal Session
Source: https://docs.chowder.dev/api-reference/subscription/portal
POST /v1/subscription/portal
Generate a Stripe Customer Portal URL for managing billing.
Get a link to the Stripe Customer Portal where your users can manage their subscription, update payment methods, and view invoices. No request body needed — just call it and redirect.
## Authorization
Requires an **organization-level** API key in the `Authorization` header.
## Response
A Stripe Customer Portal URL. Redirect the user here to manage their billing.
```bash theme={"theme":{"light":"github-light","dark":"github-dark-dimmed"}}
curl -X POST https://api.chowder.dev/v1/subscription/portal \
-H "Authorization: Bearer chd_sk_your_org_key"
```
```json 200 theme={"theme":{"light":"github-light","dark":"github-dark-dimmed"}}
{
"url": "https://billing.stripe.com/p/session/live_x1y2z3..."
}
```
# Authentication
Source: https://docs.chowder.dev/concepts/authentication
How API keys work in Chowder — organization keys vs. scoped keys, permissions, and when to use which.
# Authentication
Every request to the Chowder API requires an API key passed as a Bearer token:
```bash theme={"theme":{"light":"github-light","dark":"github-dark-dimmed"}}
curl https://api.chowder.dev/v1/instances \
-H "Authorization: Bearer chd_org_abc123..."
```
There are two types of keys, and they serve very different purposes.
## Organization keys
**Prefix:** `chd_org_*`
Organization keys are the master keys. They have full, unrestricted access to everything in your organization: instances, channels, skills, files, billing, other keys — everything.
You get one automatically when you sign up:
```bash theme={"theme":{"light":"github-light","dark":"github-dark-dimmed"}}
curl -X POST https://api.chowder.dev/v1/organization/signup \
-H "Content-Type: application/json" \
-d '{"name": "my-org"}'
# Response:
# {
# "api_key": "chd_org_a1b2c3d4e5f6...",
# "organization": { ... }
# }
```
The raw API key is only shown **once** — at creation. Chowder stores a SHA-256 hash of the key, not the key itself. If you lose it, you'll need to create a new one.
**Use org keys for:**
* Backend services that manage instances
* Admin dashboards
* CI/CD pipelines
* Anything that needs full control
**Never use org keys for:**
* Client-side code
* User-facing frontends
* Anything where the key could be exposed
## Scoped keys
**Prefix:** `chd_sk_*`
Scoped keys are restricted keys. They only work with specific instances and only have specific permissions. This is what you hand out when you don't want to give away the kingdom.
```bash theme={"theme":{"light":"github-light","dark":"github-dark-dimmed"}}
curl -X POST https://api.chowder.dev/v1/keys \
-H "Authorization: Bearer chd_org_abc123..." \
-H "Content-Type: application/json" \
-d '{
"name": "frontend-key",
"instance_ids": ["inst_abc123"],
"permissions": ["read", "interact"]
}'
# Response:
# {
# "id": "key_xyz...",
# "raw_key": "chd_sk_f7g8h9...",
# "name": "frontend-key",
# ...
# }
```
### Permissions
Each scoped key gets a set of permissions that control what it can do:
| Permission | What it allows |
| ----------- | ---------------------------------------- |
| `read` | View instance details and status |
| `interact` | Send messages (call `/responses`) |
| `configure` | Update instance config, start/stop |
| `files` | Read, write, and manage workspace files |
| `channels` | Connect, disconnect, and manage channels |
For a frontend that just needs to send and receive messages:
```json theme={"theme":{"light":"github-light","dark":"github-dark-dimmed"}}
{
"name": "chat-widget",
"instance_ids": ["inst_abc123"],
"permissions": ["read", "interact"]
}
```
For a dashboard that manages channels and config:
```json theme={"theme":{"light":"github-light","dark":"github-dark-dimmed"}}
{
"name": "admin-panel",
"instance_ids": ["inst_abc123", "inst_def456"],
"permissions": ["read", "interact", "configure", "channels"]
}
```
For a service that uploads documents to the workspace:
```json theme={"theme":{"light":"github-light","dark":"github-dark-dimmed"}}
{
"name": "document-uploader",
"instance_ids": ["inst_abc123"],
"permissions": ["files"]
}
```
### Expiration
Scoped keys can optionally expire:
```json theme={"theme":{"light":"github-light","dark":"github-dark-dimmed"}}
{
"name": "temp-access",
"instance_ids": ["inst_abc123"],
"permissions": ["read", "interact"],
"expires_at": "2026-03-01T00:00:00Z"
}
```
After the expiration date, the key returns a `403`.
## How keys are stored
Chowder never stores raw API keys. Here's what happens:
A cryptographically random key is generated with the appropriate prefix (`chd_org_` or `chd_sk_`).
The key is hashed with SHA-256. Only the hash is stored in the database.
The raw key is returned in the creation response. This is the only time you'll see it.
On each request, Chowder hashes the key from the `Authorization` header and looks up the hash in the database.
This means Chowder **cannot** recover your key. If you lose it, revoke it and create a new one. This is by design — it protects you even if the database is compromised.
## Managing keys
```bash theme={"theme":{"light":"github-light","dark":"github-dark-dimmed"}}
# List all scoped keys
curl https://api.chowder.dev/v1/keys \
-H "Authorization: Bearer chd_org_abc123..."
# Get details about a specific key (shows instance access)
curl https://api.chowder.dev/v1/keys/{key_id} \
-H "Authorization: Bearer chd_org_abc123..."
# Revoke a key (immediate, permanent)
curl -X DELETE https://api.chowder.dev/v1/keys/{key_id} \
-H "Authorization: Bearer chd_org_abc123..."
```
Revoking a key is immediate and permanent. Any request using that key will start returning `403` right away.
## Which key should I use?
You're building backend services, admin tools, or anything that runs on infrastructure you control. The key never leaves your server.
You're building user-facing apps, chat widgets, or anything where the key might be visible in network requests. Lock it down to specific instances and minimum permissions.
A good pattern: your backend uses an org key to manage instances and create scoped keys, then hands scoped keys to your frontend so users can chat with specific agents without accessing anything else.
# Channels
Source: https://docs.chowder.dev/concepts/channels
Connect your agent to Telegram, Discord, Slack, WhatsApp, and more. One API call per channel.
# Channels
Channels are messaging platforms wired into your agent. Connect Telegram, and people can DM your bot. Connect Discord, and it joins your server. Connect WhatsApp, and it shows up on someone's phone.
One instance can have multiple channels running simultaneously. Your agent on Telegram, Discord, and Slack — all at once, all from the same instance.
## How channels work
The flow is straightforward:
```bash theme={"theme":{"light":"github-light","dark":"github-dark-dimmed"}}
curl -X POST https://api.chowder.dev/v1/instances/{id}/channels/telegram/connect \
-H "Authorization: Bearer $CHOWDER_KEY" \
-H "Content-Type: application/json" \
-d '{"config": {"token": "123456:ABC-DEF..."}}'
```
Your credentials are written into the OpenClaw configuration file inside the sandbox. The channel is marked as enabled with a default DM policy.
The gateway process restarts to pick up the new channel config. This takes a few seconds.
Your agent is now reachable on that platform. People can message it and get responses.
## Two types of channels
Most channels are token-based. You create a bot on the platform, get a token (or set of tokens), and pass them to Chowder. That's it.
**Token-based channels:** Telegram, Discord, Slack, Google Chat, Signal, Matrix, Microsoft Teams, Mattermost, Nostr, LINE
```bash theme={"theme":{"light":"github-light","dark":"github-dark-dimmed"}}
# Example: Discord
curl -X POST https://api.chowder.dev/v1/instances/{id}/channels/discord/connect \
-H "Authorization: Bearer $CHOWDER_KEY" \
-H "Content-Type: application/json" \
-d '{"config": {"token": "your-discord-bot-token"}}'
```
Some channels require an interactive setup — like scanning a QR code. You call the connect endpoint, and instead of an immediate "connected" response, you get back a QR code to scan.
**Interactive channels:** WhatsApp
```bash theme={"theme":{"light":"github-light","dark":"github-dark-dimmed"}}
# WhatsApp returns a QR code
curl -X POST https://api.chowder.dev/v1/instances/{id}/channels/whatsapp/connect \
-H "Authorization: Bearer $CHOWDER_KEY"
# Response:
# {
# "channel": "whatsapp",
# "status": "awaiting_scan",
# "qr_data": "2@abc...",
# "qr_image_base64": "iVBOR..."
# }
```
Scan the QR with your phone, and the channel goes live.
## Supported channels
Here's the full list, with what you need for each one:
| Channel | Type | Required fields |
| --------------- | ----------- | ---------------------------------------------- |
| **Telegram** | Token | `token` — bot token from BotFather |
| **Discord** | Token | `token` — Discord bot token |
| **Slack** | Token | `bot_token` (xoxb-...), `app_token` (xapp-...) |
| **WhatsApp** | Interactive | None — scan the QR code |
| **Google Chat** | Token | `audience_type`, `audience` |
| **Signal** | Token | `account` — phone number in E.164 format |
| **Matrix** | Token | `homeserver`, `user_id`, `access_token` |
| **MS Teams** | Token | `app_id`, `app_password`, `tenant_id` |
| **Mattermost** | Token | `bot_token`, `base_url` |
| **Nostr** | Token | None — uses auto-generated keys |
| **LINE** | Token | `channel_access_token`, `channel_secret` |
## DM policy and pairing
By default, channels use a **pairing** DM policy. This means unknown users who message your bot get a pairing code, and you have to approve them before they can chat.
This is a security feature — it prevents random people from using your agent (and burning your API credits).
```bash theme={"theme":{"light":"github-light","dark":"github-dark-dimmed"}}
# Approve a pairing code
curl -X POST https://api.chowder.dev/v1/instances/{id}/channels/telegram/pair \
-H "Authorization: Bearer $CHOWDER_KEY" \
-H "Content-Type: application/json" \
-d '{"code": "ABC123"}'
```
The pairing code is shown to the user when they first message the bot. They send it to you (or your admin panel), you approve it, and they're in.
## Managing channels
```bash theme={"theme":{"light":"github-light","dark":"github-dark-dimmed"}}
# List all channels and their status
curl https://api.chowder.dev/v1/instances/{id}/channels \
-H "Authorization: Bearer $CHOWDER_KEY"
# Check a specific channel
curl https://api.chowder.dev/v1/instances/{id}/channels/telegram/status \
-H "Authorization: Bearer $CHOWDER_KEY"
# Disconnect a channel
curl -X POST https://api.chowder.dev/v1/instances/{id}/channels/telegram/disconnect \
-H "Authorization: Bearer $CHOWDER_KEY"
```
Disconnecting disables the channel in the config and restarts the gateway. The bot goes offline on that platform but your credentials are preserved (just marked as disabled).
No. Each channel type can only be connected once per instance. If you need two Telegram bots, create two instances.
Channels go offline when the instance stops (the gateway shuts down). When you start it again, channels reconnect automatically — assuming your tokens haven't been revoked on the platform side.
Yes. You can pass additional config when connecting a channel, or update the instance's OpenClaw config directly via `PATCH /v1/instances/{id}` with the appropriate `openclaw_config` values. Supported policies depend on the OpenClaw version.
# Instances
Source: https://docs.chowder.dev/concepts/instances
Everything you need to know about Chowder instances — what they are, how they're provisioned, and how the lifecycle works.
# Instances
An instance is a running OpenClaw agent. It's the core unit of everything in Chowder.
When you create an instance, Chowder spins up an isolated cloud sandbox, installs OpenClaw, starts the gateway, and hands you back a running agent you can talk to, connect channels to, and extend with skills. Each instance gets its own:
* **Sandbox** — a cloud container with its own filesystem
* **Gateway** — an HTTP server that handles requests, sessions, and channel bridges
* **Workspace** — a persistent directory for files, skills, and configuration
* **Memory** — conversation history and context, scoped per session
Think of an instance as a self-contained AI agent living in its own little world.
## Instance lifecycle
Every instance moves through a predictable set of states:
You call `POST /v1/instances` and Chowder immediately returns a row with `status: "provisioning"`. In the background, it's creating a sandbox, running OpenClaw's onboard process, configuring the gateway, and starting it up.
This takes about **60–90 seconds**. You can poll `GET /v1/instances/{id}` until the status flips to `running`.
The instance is live. You can send it messages, connect channels, install skills, and manage files. The gateway is accepting requests and the sandbox is awake.
You called `POST /v1/instances/{id}/stop`. The sandbox is hibernated — it's still there, but not consuming compute. The gateway is down. No messages can be sent.
Starting it back up is much faster than the initial provision (\~10–15 seconds).
You called `DELETE /v1/instances/{id}`. The sandbox is destroyed, the data is gone, and the instance won't show up in list calls. This is permanent.
If provisioning fails (bad API key, sandbox issue, etc.), the instance enters an `error` state with an `error_message` field explaining what went wrong. You can delete it and try again.
## What happens during provisioning
When you create an instance, here's what Chowder does behind the scenes:
1. **Creates a sandbox** from the `sandbox-medium` snapshot (OpenClaw comes pre-installed)
2. **Runs `openclaw onboard`** with your model provider credentials to write the initial config
3. **Generates a gateway auth token** and configures token-based gateway authentication
4. **Enables the responses endpoint** so the API can proxy messages to your agent
5. **Applies any custom config** you passed in `openclaw_config`
6. **Starts the gateway** in the background
7. **Resolves the public URL** and persists everything to the database
All of this happens asynchronously — the create call returns immediately so you're not blocked waiting.
## Model providers
Each instance is backed by a language model. You choose the provider when creating the instance:
```bash theme={"theme":{"light":"github-light","dark":"github-dark-dimmed"}}
curl -X POST https://api.chowder.dev/v1/instances \
-H "Authorization: Bearer $CHOWDER_KEY" \
-H "Content-Type: application/json" \
-d '{
"name": "my-agent",
"model_provider": "anthropic"
}'
```
Uses your Anthropic API key. Models like Claude Sonnet, Claude Opus, etc.
```bash theme={"theme":{"light":"github-light","dark":"github-dark-dimmed"}}
curl -X POST https://api.chowder.dev/v1/instances \
-H "Authorization: Bearer $CHOWDER_KEY" \
-H "Content-Type: application/json" \
-d '{
"name": "my-agent",
"model_provider": "openai"
}'
```
Uses your OpenAI API key. GPT-4o, o1, o3, etc.
```bash theme={"theme":{"light":"github-light","dark":"github-dark-dimmed"}}
curl -X POST https://api.chowder.dev/v1/instances \
-H "Authorization: Bearer $CHOWDER_KEY" \
-H "Content-Type: application/json" \
-d '{
"name": "my-agent",
"model_provider": "gemini"
}'
```
Uses your Google AI API key. Gemini Pro, Gemini Ultra, etc.
You need to configure your model provider API key on your organization **before** creating an instance. Set it via `PATCH /v1/organization` — otherwise the create call will fail with a 400.
## Instance limits
How many instances you can run depends on your subscription tier:
| Tier | Instance limit |
| ---------------------- | -------------- |
| **Shrimp** (free) | 1 |
| **Crab** | 10 |
| **Lobster** | 500 |
| **Whale** (enterprise) | Unlimited |
Limits are enforced at creation time. If you're at capacity, the API returns a `402` with a message telling you to upgrade. See [Subscriptions & Billing](/concepts/subscriptions) for details.
## Starting and stopping
Stopped instances aren't gone — they're hibernated. The sandbox still exists, all your files and config are preserved, and you can bring it back online whenever you want.
```bash theme={"theme":{"light":"github-light","dark":"github-dark-dimmed"}}
# Stop an instance (sandbox hibernates)
curl -X POST https://api.chowder.dev/v1/instances/{id}/stop \
-H "Authorization: Bearer $CHOWDER_KEY"
# Start it back up (~10-15 seconds)
curl -X POST https://api.chowder.dev/v1/instances/{id}/start \
-H "Authorization: Bearer $CHOWDER_KEY"
```
This is useful for:
* **Cost management** — stopped instances don't consume compute
* **Development workflows** — spin up for testing, stop when you're done
* **Scheduled agents** — start at 9am, stop at 5pm
Starting a stopped instance is significantly faster than provisioning a new one, because the sandbox already exists and OpenClaw is already installed. The gateway just needs to boot.
Not directly. The model provider is set during onboard and baked into the gateway config. If you need a different provider, create a new instance. You can use the files API to migrate workspace data between instances if needed.
Channel connections are preserved in the config, but they go offline when the gateway stops. When you start the instance again, channels will automatically reconnect (assuming your tokens are still valid).
# Sessions
Source: https://docs.chowder.dev/concepts/sessions
How chat sessions work in Chowder — default sessions, named sessions, and why isolation matters.
# Sessions
Sessions let you run multiple independent conversations with the same instance. Each session has its own memory, context, and conversation history — completely isolated from every other session.
This is how you build things like per-user chat threads, project-specific assistants, or multi-persona agents on a single instance.
## The default session
When you send a message to an instance without specifying a session, it goes to the **default session** (called `main`):
```bash theme={"theme":{"light":"github-light","dark":"github-dark-dimmed"}}
# This uses the default session
curl -X POST https://api.chowder.dev/v1/instances/{id}/responses \
-H "Authorization: Bearer $CHOWDER_KEY" \
-H "Content-Type: application/json" \
-d '{"model": "claude-sonnet-4-20250514", "input": "Hey, remember me?"}'
```
The default session is always there. You don't need to create it. If you're building something simple — a single chatbot, a personal assistant — this is all you need.
## Named sessions
For anything more complex, use named sessions. Just include a session ID in the URL:
```bash theme={"theme":{"light":"github-light","dark":"github-dark-dimmed"}}
# This creates (or continues) a session called "user-42"
curl -X POST https://api.chowder.dev/v1/instances/{id}/session/user-42/responses \
-H "Authorization: Bearer $CHOWDER_KEY" \
-H "Content-Type: application/json" \
-d '{"model": "claude-sonnet-4-20250514", "input": "What were we talking about?"}'
```
Sessions are created on first use — there's no explicit "create session" step. Just pick a session ID and start sending messages.
Session IDs can be any string. Use something meaningful: a user ID (`user-42`), a project name (`project-atlas`), a thread ID (`thread-abc123`), whatever maps to your use case.
## Session isolation
This is the important part: **sessions are completely isolated**. A message in one session has zero visibility into what happened in another.
```
Instance "my-agent"
├── main (default) → "Help me write a haiku"
├── user-42 → "Debug my Python code"
├── user-88 → "Translate this to Spanish"
└── project-atlas → "Summarize the Q4 report"
```
Each of these sessions:
* Has its own **conversation history** — the agent doesn't mix up who said what
* Maintains its own **context window** — previous messages from other sessions don't eat into the limit
* Has its own **memory** — things the agent "remembers" are session-specific
Skills and workspace files are shared across sessions — they belong to the instance, not the session. If you install a browser skill, all sessions can use it. If you upload a file, all sessions can access it.
## Use cases
Map each user in your app to a session. User A's conversation is private from User B's. One instance serves all your users.
Different projects, different contexts. The agent in `project-atlas` knows about Atlas. The agent in `project-beacon` knows about Beacon.
Same instance, different system prompts per session. Use the `instructions` field in the request body to give each session its own personality.
Run test conversations in a `test` session without polluting your production `main` session.
## How it works under the hood
You don't need to know this to use sessions, but if you're curious:
Chowder proxies your request to the OpenClaw gateway running inside the instance's sandbox. When you specify a session ID, Chowder includes it as an `x-openclaw-session-key` header on the proxied request. OpenClaw uses this to route the message to the right session context.
You never need to set this header yourself — Chowder handles it based on the URL you call:
| Endpoint | Session used |
| -------------------------------------------------------- | ---------------- |
| `POST /v1/instances/{id}/responses` | `main` (default) |
| `POST /v1/instances/{id}/session/{session_id}/responses` | `{session_id}` |
There's no hard limit from Chowder's side. Sessions are managed by OpenClaw within the instance sandbox. In practice, you're bounded by the sandbox's memory — each active session maintains its own conversation state. For most use cases, you can comfortably run hundreds of sessions per instance.
Not through the Chowder API currently. Sessions are managed internally by OpenClaw. If you need to reset a session, you can send a message with fresh instructions, or create a new instance for a clean slate.
Yes. Each channel connection effectively operates in its own session context. When someone messages your agent on Telegram, it doesn't interfere with a conversation happening on Discord or through the API.
# Skills
Source: https://docs.chowder.dev/concepts/skills
Give your agent superpowers — web browsing, code execution, image generation, and more — by installing skills from ClawHub.
# Skills
Skills are plugins that give your agent new capabilities. Want it to browse the web? Install a skill. Generate images? Install a skill. Execute code, search the internet, read PDFs? Skills.
They come from [ClawHub](https://clawhub.dev) — the package registry for OpenClaw. Think npm, but for agent capabilities.
## Installing a skill
Pass the skill's slug and Chowder handles the rest:
```bash theme={"theme":{"light":"github-light","dark":"github-dark-dimmed"}}
curl -X POST https://api.chowder.dev/v1/instances/{id}/skills \
-H "Authorization: Bearer $CHOWDER_KEY" \
-H "Content-Type: application/json" \
-d '{"slug": "@openclaw/web-search"}'
```
Chowder runs `clawhub install` inside the instance's sandbox, enables the skill in the gateway config, and reports back. The agent can use it immediately.
You can pass environment variables at install time if the skill needs them:
```bash theme={"theme":{"light":"github-light","dark":"github-dark-dimmed"}}
curl -X POST https://api.chowder.dev/v1/instances/{id}/skills \
-H "Authorization: Bearer $CHOWDER_KEY" \
-H "Content-Type: application/json" \
-d '{
"slug": "@openclaw/web-search",
"env": {
"SERP_API_KEY": "your-key-here"
}
}'
```
## Installed vs. ready
There's an important distinction between a skill that's **installed** and one that's **ready**:
The skill's code is in the workspace. It's registered with OpenClaw. But it might not work yet — it could be missing required API keys or configuration.
The skill is installed **and** all its required environment variables are set. The agent can actually use it.
When you install a skill, Chowder reads its `SKILL.md` to figure out what env vars it needs. If any are missing, the response will tell you:
```json theme={"theme":{"light":"github-light","dark":"github-dark-dimmed"}}
{
"skill": "web-search",
"status": "installed_missing_env",
"message": "Missing env vars: SERP_API_KEY",
"required_env": ["SERP_API_KEY"]
}
```
You can set the missing vars later via the skill config endpoint:
```bash theme={"theme":{"light":"github-light","dark":"github-dark-dimmed"}}
curl -X PATCH https://api.chowder.dev/v1/instances/{id}/skills/web-search \
-H "Authorization: Bearer $CHOWDER_KEY" \
-H "Content-Type: application/json" \
-d '{"env": {"SERP_API_KEY": "your-key-here"}}'
```
## Listing skills
```bash theme={"theme":{"light":"github-light","dark":"github-dark-dimmed"}}
# List all skills on an instance
curl https://api.chowder.dev/v1/instances/{id}/skills \
-H "Authorization: Bearer $CHOWDER_KEY"
# Get details about a specific skill
curl https://api.chowder.dev/v1/instances/{id}/skills/web-search \
-H "Authorization: Bearer $CHOWDER_KEY"
```
The list response includes each skill's name, description, source, and whether it's ready:
```json theme={"theme":{"light":"github-light","dark":"github-dark-dimmed"}}
[
{
"name": "web-search",
"description": "Search the web using SERP API",
"source": "@openclaw/web-search",
"ready": true
},
{
"name": "browser",
"description": "Control a headless browser",
"source": "@openclaw/browser",
"ready": true
}
]
```
## Popular skills
Here are some commonly used ones to get you started:
| Skill | Slug | What it does |
| ---------------- | ----------------------- | -------------------------------------------------- |
| Web Search | `@openclaw/web-search` | Search the internet via SERP API |
| Browser | `@openclaw/browser` | Control a headless Chromium browser |
| Code Execution | `@openclaw/code-runner` | Run Python/JS/shell code in a sandbox |
| Image Generation | `@openclaw/image-gen` | Generate images via DALL-E, Stable Diffusion, etc. |
| File Reader | `@openclaw/file-reader` | Parse PDFs, CSVs, DOCX, and other file formats |
The skills available on ClawHub are community-maintained and constantly growing. Check [clawhub.dev](https://clawhub.dev) for the full catalog.
## Uninstalling a skill
```bash theme={"theme":{"light":"github-light","dark":"github-dark-dimmed"}}
curl -X DELETE https://api.chowder.dev/v1/instances/{id}/skills/web-search \
-H "Authorization: Bearer $CHOWDER_KEY"
```
This removes the skill from the workspace, cleans up the ClawHub lockfile, and the agent can no longer use it.
## Skills are instance-scoped
Skills belong to the instance, not to a session. When you install a skill, every session on that instance can use it. There's no way to restrict a skill to a specific session — if you need that level of isolation, use separate instances.
Not through the Chowder API currently. You'd need to use the files API to manually place skill files in the workspace's `skills/` directory and configure them through the instance config. ClawHub is the supported path.
Yes. Skills are installed into the workspace filesystem, which is preserved when the sandbox hibernates. When you start the instance again, all your skills are still there and ready.
The install response includes a `required_env` field listing all required environment variables. You can also check the skill's page on ClawHub or call `GET /v1/instances/{id}/skills/{name}` for details.
# Subscriptions & Billing
Source: https://docs.chowder.dev/concepts/subscriptions
Chowder's subscription tiers, instance limits, and how billing works with Stripe.
# Subscriptions & Billing
Chowder uses a simple tier-based subscription model. Your tier determines how many instances you can run. Billing is handled entirely through Stripe.
## Tiers
| Tier | Instance limit | How to get it |
| ----------- | -------------- | ----------------------- |
| **Shrimp** | 1 | Free — you start here |
| **Crab** | 10 | Self-serve checkout |
| **Lobster** | 500 | Self-serve checkout |
| **Whale** | Unlimited | Contact us (enterprise) |
One instance, no credit card. Great for trying things out, personal projects, or building a proof of concept. You get the full API — just limited to a single instance.
Ten instances. Good for small teams, multi-agent setups, or production apps with moderate scale.
Five hundred instances. For platforms that run agents on behalf of their users. Serious scale.
Unlimited. Enterprise pricing, custom terms. [Reach out](https://chowder.dev/contact) if you're at this level.
## How limits work
Instance limits are enforced at **creation time**. If you're on the Crab tier (10 instances) and already have 10 running, the next `POST /v1/instances` will return a `402`:
```json theme={"theme":{"light":"github-light","dark":"github-dark-dimmed"}}
{
"detail": "Instance limit reached (10/10). Upgrade your subscription."
}
```
Only non-terminated instances count toward your limit. If you delete an instance, that slot opens up immediately. Stopped instances still count — they're hibernated, not gone.
## Upgrading
The upgrade flow uses Stripe Checkout — a hosted payment page that handles all the payment UI for you.
```bash theme={"theme":{"light":"github-light","dark":"github-dark-dimmed"}}
curl -X POST https://api.chowder.dev/v1/subscription/checkout \
-H "Authorization: Bearer chd_org_abc123..." \
-H "Content-Type: application/json" \
-d '{"tier": "crab"}'
```
This returns a Stripe Checkout URL.
Send your user (or yourself) to the returned URL. Stripe handles the payment form, card validation, and receipt.
When payment completes, Stripe fires a `checkout.session.completed` webhook. Chowder processes it and upgrades your organization's tier and instance limit automatically.
No manual intervention needed. Once the webhook lands, you can immediately create more instances.
## Checking your subscription
```bash theme={"theme":{"light":"github-light","dark":"github-dark-dimmed"}}
curl https://api.chowder.dev/v1/subscription \
-H "Authorization: Bearer chd_org_abc123..."
```
```json theme={"theme":{"light":"github-light","dark":"github-dark-dimmed"}}
{
"tier": "crab",
"instance_limit": 10,
"instance_count": 3,
"stripe_customer_id": "cus_...",
"stripe_subscription_id": "sub_..."
}
```
## Managing your subscription
Already on a paid tier and need to change plans, update payment info, or cancel?
```bash theme={"theme":{"light":"github-light","dark":"github-dark-dimmed"}}
curl -X POST https://api.chowder.dev/v1/subscription/portal \
-H "Authorization: Bearer chd_org_abc123..."
```
This returns a Stripe Billing Portal URL where you can:
* Switch between Crab and Lobster
* Update your payment method
* View invoices
* Cancel your subscription
If you cancel your subscription, your organization downgrades to the **Shrimp** tier (1 instance). Existing instances beyond the limit won't be deleted, but you won't be able to create new ones until you're back within the limit.
## Per-instance billing
On paid tiers, Chowder tracks instance count as a line item on your Stripe subscription. When you create an instance, the count goes up. When you delete one, it goes down. This happens automatically — Chowder syncs the quantity with Stripe after every create and delete.
This means your bill reflects what you're actually using, not what your tier allows. Being on the Crab tier doesn't mean you're paying for 10 instances — you're paying for however many you have running.
The instance creation call will fail with a `402` before any sandbox is provisioned. No partial resources, no zombie instances. You'll need to either delete an existing instance or upgrade.
Yes, through the Stripe billing portal. If you have more instances than the new tier allows, you won't be able to create new ones until you're within the limit, but existing instances keep running.
Yes. One instance, full API access, no credit card, no time limit. You can use it forever. If you need more instances, upgrade.
# Build a Frontend
Source: https://docs.chowder.dev/guides/build-a-frontend
Build a chat interface with Next.js that connects to your Chowder instance.
Let's build a chat UI that talks to your Chowder instance. We'll use Next.js with TypeScript, but the patterns here work with any framework — the Chowder API is just REST.
By the end, you'll have a working chat interface with session support and model selection.
This guide shows the key pieces, not a full copy-paste app. For a complete working implementation, check the `example/` folder in the [Chowder repo](https://github.com/chowder-api/chowder/tree/main/example).
## Prerequisites
* A running Chowder instance ([create one first](/guides/your-first-instance))
* A **scoped API key** with `read` and `interact` permissions ([create one](/guides/scoped-keys))
* Node.js 18+ and a Next.js project
Never use your organization key (`chd_org_...`) in frontend code. It has full admin access. Always use a scoped key (`chd_sk_...`) with the minimum permissions needed.
### Set up environment variables
Create a `.env.local` in your Next.js project root:
```bash .env.local theme={"theme":{"light":"github-light","dark":"github-dark-dimmed"}}
NEXT_PUBLIC_API_URL=https://api.chowder.dev
NEXT_PUBLIC_API_KEY=chd_sk_your_scoped_key_here
```
The `NEXT_PUBLIC_` prefix makes these available in client-side code. That's fine for a scoped key with limited permissions — that's exactly what scoped keys are for.
### Create an API helper
Build a typed fetch wrapper that handles auth and errors. This keeps your components clean:
```typescript lib/chowder.ts theme={"theme":{"light":"github-light","dark":"github-dark-dimmed"}}
const BASE = process.env.NEXT_PUBLIC_API_URL || "https://api.chowder.dev";
const KEY = process.env.NEXT_PUBLIC_API_KEY || "";
function headers(): HeadersInit {
return {
"Content-Type": "application/json",
Authorization: `Bearer ${KEY}`,
};
}
async function request(path: string, init?: RequestInit): Promise {
const res = await fetch(`${BASE}${path}`, {
...init,
headers: headers(),
});
if (!res.ok) {
const text = await res.text().catch(() => res.statusText);
throw new Error(text);
}
if (res.status === 204) return undefined as T;
return res.json();
}
```
Now add typed methods for the endpoints you need:
```typescript lib/chowder.ts (continued) theme={"theme":{"light":"github-light","dark":"github-dark-dimmed"}}
// ---------- Types ----------
export interface Instance {
id: string;
name: string;
status: string;
model_provider: string;
gateway_url: string | null;
created_at: string;
}
export interface ResponseOutput {
id: string;
output: {
type: string;
role?: string;
content?: { type: string; text: string }[];
}[];
}
// ---------- API methods ----------
export function getInstance(id: string) {
return request(`/v1/instances/${id}`);
}
export function sendMessage(
instanceId: string,
input: string,
model: string,
sessionId?: string
) {
const path = sessionId
? `/v1/instances/${instanceId}/session/${sessionId}/responses`
: `/v1/instances/${instanceId}/responses`;
return request(path, {
method: "POST",
body: JSON.stringify({ model, input }),
});
}
```
That's the entire API layer. Two functions: get instance info and send messages. The scoped key handles auth automatically.
### Build the chat component
Here's a minimal chat component. It keeps messages in local state, sends them to the API, and renders the response:
```tsx components/Chat.tsx theme={"theme":{"light":"github-light","dark":"github-dark-dimmed"}}
"use client";
import { useState } from "react";
import { sendMessage, type ResponseOutput } from "@/lib/chowder";
interface Message {
role: "user" | "assistant";
text: string;
}
// Extract the assistant's text from the API response
function extractText(res: ResponseOutput): string {
return (
res.output
?.filter((o) => o.type === "message")
.flatMap((o) => o.content ?? [])
.filter((c) => c.type === "output_text")
.map((c) => c.text)
.join("\n") || "(no response)"
);
}
export function Chat({ instanceId }: { instanceId: string }) {
const [messages, setMessages] = useState([]);
const [input, setInput] = useState("");
const [model, setModel] = useState("claude-sonnet-4-20250514");
const [loading, setLoading] = useState(false);
const [sessionId, setSessionId] = useState();
const send = async () => {
const text = input.trim();
if (!text || loading) return;
setMessages((prev) => [...prev, { role: "user", text }]);
setInput("");
setLoading(true);
try {
const res = await sendMessage(instanceId, text, model, sessionId);
setMessages((prev) => [
...prev,
{ role: "assistant", text: extractText(res) },
]);
} catch (e) {
setMessages((prev) => [
...prev,
{
role: "assistant",
text: `Error: ${e instanceof Error ? e.message : "Request failed"}`,
},
]);
} finally {
setLoading(false);
}
};
return (
{/* Messages */}
{messages.map((msg, i) => (
{msg.role}
{msg.text}
))}
{loading &&
Thinking...
}
{/* Input */}
);
}
```
A few things to note about parsing the response:
* `output` is an array — the agent might return multiple output items
* Filter for `type: "message"` to get the assistant's response
* Each message has a `content` array of typed blocks — grab `output_text` for the text
### Handle sessions
Sessions let you maintain separate conversation threads. Add session creation and switching:
```tsx Session management theme={"theme":{"light":"github-light","dark":"github-dark-dimmed"}}
const [sessions, setSessions] = useState(["main"]);
const [activeSession, setActiveSession] = useState("main");
// Create a new session
const createSession = (name: string) => {
setSessions((prev) => [...prev, name]);
setActiveSession(name);
};
// When sending, pass the session ID
const res = await sendMessage(
instanceId,
text,
model,
activeSession === "main" ? undefined : activeSession
);
```
The "main" session uses the default `/responses` endpoint (no session ID). Named sessions use `/session/{name}/responses`. They're created automatically on first use — no setup required.
Render session tabs so users can switch:
```tsx Session tabs theme={"theme":{"light":"github-light","dark":"github-dark-dimmed"}}
{sessions.map((s) => (
))}
```
### Add model selection
Let users pick which model the agent uses. This switches per-request — the agent's memory persists across model changes:
```tsx Model picker theme={"theme":{"light":"github-light","dark":"github-dark-dimmed"}}
const MODELS = [
"claude-sonnet-4-20250514",
"claude-haiku-4-20250414",
"gpt-4o",
"gpt-4o-mini",
"gemini-2.0-flash",
];
```
Available models depend on what your instance's model provider supports. If you created the instance with `model_provider: "anthropic"`, Claude models work out of the box. For GPT or Gemini models, make sure you've configured the corresponding API keys on your organization.
### Error handling patterns
Here are the common errors you'll hit and how to handle them gracefully:
```typescript Error handling theme={"theme":{"light":"github-light","dark":"github-dark-dimmed"}}
async function safeSend(
instanceId: string,
input: string,
model: string,
sessionId?: string
): Promise<{ text: string; error?: boolean }> {
try {
const res = await sendMessage(instanceId, input, model, sessionId);
return { text: extractText(res) };
} catch (e) {
const msg = e instanceof Error ? e.message : "Unknown error";
// Parse the error for common cases
if (msg.includes("401")) {
return { text: "API key is invalid or expired.", error: true };
}
if (msg.includes("403")) {
return {
text: "Permission denied. Your key may not have 'interact' access.",
error: true,
};
}
if (msg.includes("409")) {
return {
text: "Instance is not running. It may need to be started.",
error: true,
};
}
if (msg.includes("502")) {
return {
text: "Gateway unavailable. The instance may be restarting.",
error: true,
};
}
return { text: `Something went wrong: ${msg}`, error: true };
}
}
```
| Status | What it means | User-facing message |
| ------ | ------------------------------------ | ---------------------------------------- |
| `401` | Bad API key | "Please check your API key" |
| `403` | Missing permission or wrong instance | "You don't have access to this" |
| `409` | Instance not running | "The agent is offline. Try again later." |
| `502` | Gateway down or sandbox issue | "The agent is temporarily unavailable." |
## Putting it all together
Here's how you'd wire this into a Next.js page:
```tsx app/chat/page.tsx theme={"theme":{"light":"github-light","dark":"github-dark-dimmed"}}
import { Chat } from "@/components/Chat";
export default function ChatPage() {
// In a real app, you'd get this from your database or URL params
const instanceId = "ead7b76b-f34e-4b91-93e7-9c979cf9e41c";
return (
Chat
);
}
```
## Full example
The patterns above cover the essentials, but a production app needs more — loading states, scroll management, localStorage persistence, keyboard shortcuts, and proper styling.
Check out the complete working example in the repo:
```
example/
├── src/
│ ├── lib/api.ts # Full API client with types
│ ├── components/
│ │ ├── chat.tsx # Chat with sessions, model picker, persistence
│ │ ├── channels.tsx # Channel management UI
│ │ └── skills.tsx # Skill install/uninstall UI
│ └── ...
```
The example uses Next.js 15, shadcn/ui components, and localStorage for session persistence. Clone it, drop in your env vars, and you've got a full dashboard.
## Architecture tips
Call the Chowder API directly from the browser using a scoped key. This is what the example app does.
**Pros**: Simple, no backend needed, instant setup.
**Cons**: API key is visible in browser DevTools (that's why you use a scoped key with minimal permissions).
```
Browser → Chowder API → OpenClaw Instance
```
Proxy requests through your own backend. Your server holds the API key, the browser never sees it.
**Pros**: Key is secret, you can add your own auth/rate limiting/logging.
**Cons**: More infrastructure to maintain.
```
Browser → Your API → Chowder API → OpenClaw Instance
```
With Next.js Route Handlers:
```typescript app/api/chat/route.ts theme={"theme":{"light":"github-light","dark":"github-dark-dimmed"}}
import { NextRequest, NextResponse } from "next/server";
const CHOWDER_URL = process.env.CHOWDER_API_URL!;
const CHOWDER_KEY = process.env.CHOWDER_API_KEY!; // Server-only, no NEXT_PUBLIC_
export async function POST(req: NextRequest) {
const { instanceId, input, model, sessionId } = await req.json();
// Add your own auth check here
// const user = await getUser(req);
const path = sessionId
? `/v1/instances/${instanceId}/session/${sessionId}/responses`
: `/v1/instances/${instanceId}/responses`;
const res = await fetch(`${CHOWDER_URL}${path}`, {
method: "POST",
headers: {
"Content-Type": "application/json",
Authorization: `Bearer ${CHOWDER_KEY}`,
},
body: JSON.stringify({ model, input }),
});
const data = await res.json();
return NextResponse.json(data, { status: res.status });
}
```
## What's next?
Learn more about creating and managing keys for your frontend.
Give your agent capabilities that your chat UI can leverage.
# Connect a Telegram Bot
Source: https://docs.chowder.dev/guides/connect-telegram
Wire up a Telegram bot to your Chowder instance in under 5 minutes.
Want your agent on Telegram? You're about five minutes and three API calls away from having a bot that responds to DMs with the full power of OpenClaw behind it.
Here's the plan: create a Telegram bot, hand Chowder the token, approve the first user, done.
You need a running Chowder instance. If you don't have one yet, follow [Your First Instance](/guides/your-first-instance) to get set up.
## Set up your environment
```bash theme={"theme":{"light":"github-light","dark":"github-dark-dimmed"}}
export CHOWDER_KEY="chd_org_your_key_here"
export INSTANCE_ID="your-instance-id"
```
### Create a Telegram bot
Open Telegram and search for **@BotFather** — it's Telegram's official bot for creating bots (very meta).
1. Send `/newbot`
2. Pick a display name (e.g., "My Chowder Agent")
3. Pick a username ending in `bot` (e.g., `my_chowder_agent_bot`)
4. BotFather replies with your **bot token** — a string like `7123456789:AAF1k2j3h4g5f6d7s8a9...`
Keep this token secret. Anyone with it can control your bot. Don't commit it to version control.
### Copy the bot token
Grab the token from BotFather's message. You'll pass it to Chowder in the next step.
```bash theme={"theme":{"light":"github-light","dark":"github-dark-dimmed"}}
export BOT_TOKEN="7123456789:AAF1k2j3h4g5f6d7s8a9..."
```
### Connect the channel
Call the Telegram connect endpoint with your bot token:
```bash theme={"theme":{"light":"github-light","dark":"github-dark-dimmed"}}
curl -X POST https://api.chowder.dev/v1/instances/$INSTANCE_ID/channels/telegram/connect \
-H "Authorization: Bearer $CHOWDER_KEY" \
-H "Content-Type: application/json" \
-d "{
\"config\": {
\"token\": \"$BOT_TOKEN\"
}
}"
```
```json Response theme={"theme":{"light":"github-light","dark":"github-dark-dimmed"}}
{
"channel": "telegram",
"status": "connected",
"qr_data": null,
"qr_image_base64": null,
"message": null
}
```
That's it — the channel is live. Behind the scenes, Chowder:
1. Wrote your bot token to the instance's `openclaw.json` config under `channels.telegram`
2. Set `enabled: true` and `dmPolicy: "pairing"` (more on that next)
3. Restarted the gateway to pick up the new channel
You can verify this by checking the channel status anytime:
```bash theme={"theme":{"light":"github-light","dark":"github-dark-dimmed"}}
curl https://api.chowder.dev/v1/instances/$INSTANCE_ID/channels/telegram/status \
-H "Authorization: Bearer $CHOWDER_KEY"
```
```json theme={"theme":{"light":"github-light","dark":"github-dark-dimmed"}}
{
"channel": "telegram",
"enabled": true,
"connected": true,
"details": null
}
```
### Understand the DM pairing policy
OpenClaw uses a **pairing** system for DM channels. Here's how it works:
1. Someone sends your bot a message on Telegram
2. OpenClaw generates a **pairing code** and replies with it in the chat
3. You approve that code through the API (or OpenClaw CLI)
4. Once approved, that Telegram user can chat freely with your agent
This prevents random people from racking up your model costs. The first person to message gets a code, and you decide whether to let them in.
The pairing code is shown to the user directly in Telegram. You need to get it from them (or see it in the logs) and then approve it via the API.
### Approve a pairing
Once someone messages your bot and gets a pairing code, approve it:
```bash theme={"theme":{"light":"github-light","dark":"github-dark-dimmed"}}
curl -X POST https://api.chowder.dev/v1/instances/$INSTANCE_ID/channels/telegram/pair \
-H "Authorization: Bearer $CHOWDER_KEY" \
-H "Content-Type: application/json" \
-d '{
"code": "abc123"
}'
```
```json Response theme={"theme":{"light":"github-light","dark":"github-dark-dimmed"}}
{
"channel": "telegram",
"code": "abc123",
"status": "approved",
"output": "Pairing approved for user 123456789."
}
```
Now that user can DM your bot and get full agent responses — tools, memory, the works.
### Check channel status
At any point, you can check whether the Telegram channel is up and healthy:
```bash theme={"theme":{"light":"github-light","dark":"github-dark-dimmed"}}
curl https://api.chowder.dev/v1/instances/$INSTANCE_ID/channels/telegram/status \
-H "Authorization: Bearer $CHOWDER_KEY"
```
```json Response theme={"theme":{"light":"github-light","dark":"github-dark-dimmed"}}
{
"channel": "telegram",
"enabled": true,
"connected": true,
"details": null
}
```
You can also list all channels on your instance to see what's connected:
```bash theme={"theme":{"light":"github-light","dark":"github-dark-dimmed"}}
curl https://api.chowder.dev/v1/instances/$INSTANCE_ID/channels \
-H "Authorization: Bearer $CHOWDER_KEY"
```
```json Response theme={"theme":{"light":"github-light","dark":"github-dark-dimmed"}}
[
{
"channel": "telegram",
"connection_type": "token",
"enabled": true
},
{
"channel": "discord",
"connection_type": "token",
"enabled": false
},
{
"channel": "whatsapp",
"connection_type": "interactive",
"enabled": false
}
]
```
### Disconnect when done
If you want to remove the Telegram channel:
```bash theme={"theme":{"light":"github-light","dark":"github-dark-dimmed"}}
curl -X POST https://api.chowder.dev/v1/instances/$INSTANCE_ID/channels/telegram/disconnect \
-H "Authorization: Bearer $CHOWDER_KEY"
```
This returns `204 No Content`. Chowder sets `enabled: false` in the config and restarts the gateway. The bot token is still in the config file but the channel is no longer active.
## How it works under the hood
When you call the connect endpoint, Chowder does the following on your instance's sandbox:
1. **Reads** the `openclaw.json` config file
2. **Writes** the bot token to `channels.telegram.botToken`
3. **Enables** the channel: `channels.telegram.enabled = true`
4. **Sets** the DM policy: `channels.telegram.dmPolicy = "pairing"`
5. **Restarts** the gateway process so it picks up the new config
The gateway then starts a Telegram bot listener using the [Telegram Bot API](https://core.telegram.org/bots/api). Incoming DMs are routed to the agent, and responses are sent back through the bot.
## Other channels
Telegram is a "token-based" channel — you give it a token, it connects. Chowder supports a bunch of these:
These work the same way as Telegram — pass credentials via the connect endpoint:
| Channel | Required fields |
| --------------- | ------------------------------------------- |
| **Telegram** | `token` (bot token from BotFather) |
| **Discord** | `token` (bot token) |
| **Slack** | `bot_token` + `app_token` (for Socket Mode) |
| **Signal** | `account` (E.164 phone number) |
| **Matrix** | `homeserver` + `user_id` + `access_token` |
| **MS Teams** | `app_id` + `app_password` + `tenant_id` |
| **Mattermost** | `bot_token` + `base_url` |
| **LINE** | `channel_access_token` + `channel_secret` |
| **Google Chat** | `audience_type` + `audience` |
These require a QR code scan or interactive login:
| Channel | How it works |
| ------------ | ---------------------------------------------------- |
| **WhatsApp** | Call connect, get a QR code back, scan with WhatsApp |
The connect response for WhatsApp includes `qr_data` and `qr_image_base64` — display the QR and scan it with your phone.
## What's next?
Give your bot web search, browser control, and more.
Create restricted keys so your bot management doesn't need your org key.
# Install Skills
Source: https://docs.chowder.dev/guides/install-skills
Give your agent new capabilities by installing skills from ClawHub.
Out of the box, your OpenClaw agent can chat — but that's about it. Skills are what turn it from a chatbot into something useful. Web search, browser control, code execution, image generation — they're all skills you can install with a single API call.
Skills come from **ClawHub**, the OpenClaw package registry. Think of it like npm, but for agent capabilities.
Your instance needs to be in `running` status for all of these endpoints to work. If it's stopped, start it first.
## Set up
```bash theme={"theme":{"light":"github-light","dark":"github-dark-dimmed"}}
export CHOWDER_KEY="chd_org_your_key_here"
export INSTANCE_ID="your-instance-id"
```
### Browse available skills
List what's available for your instance. Pass `eligible_only=true` to filter down to skills that are ready to install:
```bash theme={"theme":{"light":"github-light","dark":"github-dark-dimmed"}}
curl "https://api.chowder.dev/v1/instances/$INSTANCE_ID/skills?eligible_only=true" \
-H "Authorization: Bearer $CHOWDER_KEY"
```
```json Response theme={"theme":{"light":"github-light","dark":"github-dark-dimmed"}}
[
{
"name": "web-search",
"description": "Search the web using multiple providers",
"source": "clawhub",
"ready": false
},
{
"name": "browser",
"description": "Full browser control via Playwright",
"source": "clawhub",
"ready": false
},
{
"name": "image-gen",
"description": "Generate images with DALL-E or Stable Diffusion",
"source": "clawhub",
"ready": false
},
{
"name": "code-runner",
"description": "Execute code in a sandboxed environment",
"source": "clawhub",
"ready": false
}
]
```
`ready: false` means it's not installed yet. Let's fix that.
Drop the `eligible_only` param to see *all* skills, including ones already installed.
### Install a skill
Let's install web search. Skills are identified by their ClawHub slug — the format is `@org/skill-name`:
```bash theme={"theme":{"light":"github-light","dark":"github-dark-dimmed"}}
curl -X POST https://api.chowder.dev/v1/instances/$INSTANCE_ID/skills/install \
-H "Authorization: Bearer $CHOWDER_KEY" \
-H "Content-Type: application/json" \
-d '{
"slug": "@openclaw/web-search"
}'
```
```json Response theme={"theme":{"light":"github-light","dark":"github-dark-dimmed"}}
{
"skill": "web-search",
"status": "installed",
"output": "✔ OK. Installed web-search -> /home/sandbox/.openclaw/workspace/skills/web-search",
"env_applied": null,
"required_env": null,
"message": null
}
```
`"status": "installed"` — it's live. The agent can now search the web. Try it:
```bash theme={"theme":{"light":"github-light","dark":"github-dark-dimmed"}}
curl -X POST https://api.chowder.dev/v1/instances/$INSTANCE_ID/responses \
-H "Authorization: Bearer $CHOWDER_KEY" \
-H "Content-Type: application/json" \
-d '{
"model": "claude-sonnet-4-20250514",
"input": "What are the top news stories today?"
}'
```
The agent will use the web-search skill to fetch current results and summarize them.
### Handle skills that need environment variables
Some skills require API keys or config. When that's the case, the install response tells you:
```bash theme={"theme":{"light":"github-light","dark":"github-dark-dimmed"}}
curl -X POST https://api.chowder.dev/v1/instances/$INSTANCE_ID/skills/install \
-H "Authorization: Bearer $CHOWDER_KEY" \
-H "Content-Type: application/json" \
-d '{
"slug": "@openclaw/image-gen"
}'
```
```json Response — needs env vars theme={"theme":{"light":"github-light","dark":"github-dark-dimmed"}}
{
"skill": "image-gen",
"status": "installed_missing_env",
"output": "✔ OK. Installed image-gen -> /home/sandbox/.openclaw/workspace/skills/image-gen",
"env_applied": null,
"required_env": ["OPENAI_API_KEY"],
"message": "Missing env vars: OPENAI_API_KEY"
}
```
Notice `"status": "installed_missing_env"` and the `required_env` array. The skill is installed but won't work until you provide those vars.
A skill in `installed_missing_env` status is installed on disk but won't be invoked by the agent. Set the required env vars to activate it.
### Set environment variables
Use the PATCH endpoint to configure the skill's env vars:
```bash theme={"theme":{"light":"github-light","dark":"github-dark-dimmed"}}
curl -X PATCH https://api.chowder.dev/v1/instances/$INSTANCE_ID/skills/image-gen \
-H "Authorization: Bearer $CHOWDER_KEY" \
-H "Content-Type: application/json" \
-d '{
"apiKey": "sk-your-openai-key-here"
}'
```
This returns `204 No Content`. The key is written to the OpenClaw config and the skill is now fully activated.
You can also pass env vars at install time to skip the extra step:
```bash theme={"theme":{"light":"github-light","dark":"github-dark-dimmed"}}
curl -X POST https://api.chowder.dev/v1/instances/$INSTANCE_ID/skills/install \
-H "Authorization: Bearer $CHOWDER_KEY" \
-H "Content-Type: application/json" \
-d '{
"slug": "@openclaw/image-gen",
"env": {
"OPENAI_API_KEY": "sk-your-openai-key-here"
}
}'
```
```json theme={"theme":{"light":"github-light","dark":"github-dark-dimmed"}}
{
"skill": "image-gen",
"status": "installed",
"output": "✔ OK. Installed image-gen -> ...",
"env_applied": ["OPENAI_API_KEY"],
"required_env": ["OPENAI_API_KEY"],
"message": null
}
```
When `env_applied` matches `required_env`, everything's good.
### Verify a skill is ready
Check the status of any installed skill:
```bash theme={"theme":{"light":"github-light","dark":"github-dark-dimmed"}}
curl https://api.chowder.dev/v1/instances/$INSTANCE_ID/skills/web-search \
-H "Authorization: Bearer $CHOWDER_KEY"
```
```json Response theme={"theme":{"light":"github-light","dark":"github-dark-dimmed"}}
{
"name": "web-search",
"description": "Search the web using multiple providers",
"source": "clawhub",
"path": "/home/sandbox/.openclaw/workspace/skills/web-search",
"ready": true
}
```
`"ready": true` means the skill is installed, enabled, and has all required env vars configured. The agent will use it when relevant.
### Uninstall a skill
Changed your mind? Remove a skill with DELETE:
```bash theme={"theme":{"light":"github-light","dark":"github-dark-dimmed"}}
curl -X DELETE https://api.chowder.dev/v1/instances/$INSTANCE_ID/skills/web-search \
-H "Authorization: Bearer $CHOWDER_KEY"
```
Returns `204 No Content`. The skill files are removed from the workspace and the ClawHub lockfile is updated.
## Enable and disable without uninstalling
Sometimes you want to temporarily disable a skill without removing it entirely. Use the PATCH endpoint:
```bash Disable theme={"theme":{"light":"github-light","dark":"github-dark-dimmed"}}
curl -X PATCH https://api.chowder.dev/v1/instances/$INSTANCE_ID/skills/image-gen \
-H "Authorization: Bearer $CHOWDER_KEY" \
-H "Content-Type: application/json" \
-d '{"enabled": false}'
```
```bash Re-enable theme={"theme":{"light":"github-light","dark":"github-dark-dimmed"}}
curl -X PATCH https://api.chowder.dev/v1/instances/$INSTANCE_ID/skills/image-gen \
-H "Authorization: Bearer $CHOWDER_KEY" \
-H "Content-Type: application/json" \
-d '{"enabled": true}'
```
Both return `204 No Content`. The skill files stay on disk but the agent won't use a disabled skill.
## Quick reference
| Action | Method | Endpoint |
| ------------- | -------- | ---------------------------------------------- |
| List skills | `GET` | `/v1/instances/{id}/skills` |
| List eligible | `GET` | `/v1/instances/{id}/skills?eligible_only=true` |
| Skill info | `GET` | `/v1/instances/{id}/skills/{name}` |
| Install | `POST` | `/v1/instances/{id}/skills/install` |
| Configure | `PATCH` | `/v1/instances/{id}/skills/{name}` |
| Uninstall | `DELETE` | `/v1/instances/{id}/skills/{name}` |
## What's next?
Create restricted keys so integrations can install skills without full org access.
Build a chat UI that leverages your agent's new skills.
# Scoped API Keys
Source: https://docs.chowder.dev/guides/scoped-keys
Create restricted API keys for frontends, integrations, and end users.
Your organization API key (`chd_org_...`) is the master key. It can do everything — create instances, delete them, manage billing. You absolutely should not put it in a frontend, a mobile app, or hand it to a third-party integration.
That's where scoped keys come in. A scoped key (`chd_sk_...`) is locked down to specific instances and specific permissions. It can only do what you allow.
## Why scoped keys?
* **Frontends**: Your chat UI only needs to send messages and read status. Give it `read` + `interact`, nothing else.
* **Integrations**: A Zapier workflow that installs skills? Give it `configure` on one instance.
* **End users**: Give each user a key scoped to their instance with `interact` only. They can chat but can't touch config.
* **Time-limited access**: Set `expires_at` and the key auto-revokes.
## Set up
```bash theme={"theme":{"light":"github-light","dark":"github-dark-dimmed"}}
export CHOWDER_KEY="chd_org_your_key_here"
export INSTANCE_ID="your-instance-id"
```
### Create a scoped key
Let's create a key for a frontend chat widget that can only read instance info and send messages to one specific instance:
```bash theme={"theme":{"light":"github-light","dark":"github-dark-dimmed"}}
curl -X POST https://api.chowder.dev/v1/keys \
-H "Authorization: Bearer $CHOWDER_KEY" \
-H "Content-Type: application/json" \
-d "{
\"name\": \"frontend-chat\",
\"instance_ids\": [\"$INSTANCE_ID\"],
\"permissions\": [\"read\", \"interact\"]
}"
```
```json Response theme={"theme":{"light":"github-light","dark":"github-dark-dimmed"}}
{
"id": "b2f4e8a1-3c7d-4e9f-a5b6-1d8e3f7c2a94",
"organization_id": "4737cccd-3d1d-4790-979b-6825d6a333de",
"name": "frontend-chat",
"key_prefix": "chd_sk_f3a8....",
"is_active": true,
"expires_at": null,
"last_used_at": null,
"created_at": "2026-02-14T12:00:00Z",
"raw_key": "chd_sk_f3a8b2c1d4e5f6a7b8c9d0e1f2a3b4c5d6e7f8a9b0c1d2e3f4a5b6c7d8e9f0"
}
```
The `raw_key` is only shown **once** — in this creation response. Copy it now. If you lose it, you'll need to create a new key. Chowder only stores a hash of the key, not the key itself.
```bash theme={"theme":{"light":"github-light","dark":"github-dark-dimmed"}}
export SCOPED_KEY="chd_sk_f3a8b2c1d4e5f6a7b8c9d0e1f2a3b4c5d6e7f8a9b0c1d2e3f4a5b6c7d8e9f0"
```
You can also set an expiration:
```bash theme={"theme":{"light":"github-light","dark":"github-dark-dimmed"}}
curl -X POST https://api.chowder.dev/v1/keys \
-H "Authorization: Bearer $CHOWDER_KEY" \
-H "Content-Type: application/json" \
-d "{
\"name\": \"temp-demo-key\",
\"instance_ids\": [\"$INSTANCE_ID\"],
\"permissions\": [\"read\", \"interact\"],
\"expires_at\": \"2026-03-01T00:00:00Z\"
}"
```
The key will automatically stop working after the expiration date.
### Use the scoped key to send a message
The scoped key works just like your org key for the permissions you've granted:
```bash theme={"theme":{"light":"github-light","dark":"github-dark-dimmed"}}
curl -X POST https://api.chowder.dev/v1/instances/$INSTANCE_ID/responses \
-H "Authorization: Bearer $SCOPED_KEY" \
-H "Content-Type: application/json" \
-d '{
"model": "claude-sonnet-4-20250514",
"input": "Hello from a scoped key!"
}'
```
```json Response — works fine theme={"theme":{"light":"github-light","dark":"github-dark-dimmed"}}
{
"id": "resp_7a2b3c4d-5e6f-7a8b-9c0d-1e2f3a4b5c6d",
"status": "completed",
"model": "claude-sonnet-4-20250514",
"output": [
{
"type": "message",
"role": "assistant",
"content": [
{
"type": "output_text",
"text": "Hey there! Message received loud and clear."
}
]
}
]
}
```
Reading instance info also works (we granted `read`):
```bash theme={"theme":{"light":"github-light","dark":"github-dark-dimmed"}}
curl https://api.chowder.dev/v1/instances/$INSTANCE_ID \
-H "Authorization: Bearer $SCOPED_KEY"
```
### See it fail for admin operations
Now try something outside the key's permissions — like stopping the instance:
```bash theme={"theme":{"light":"github-light","dark":"github-dark-dimmed"}}
curl -X POST https://api.chowder.dev/v1/instances/$INSTANCE_ID/stop \
-H "Authorization: Bearer $SCOPED_KEY"
```
```json Response — 403 Forbidden theme={"theme":{"light":"github-light","dark":"github-dark-dimmed"}}
{
"detail": "This endpoint requires an organization API key."
}
```
Or try accessing a different instance that the key isn't scoped to:
```bash theme={"theme":{"light":"github-light","dark":"github-dark-dimmed"}}
curl https://api.chowder.dev/v1/instances/some-other-instance-id \
-H "Authorization: Bearer $SCOPED_KEY"
```
```json Response — 403 Forbidden theme={"theme":{"light":"github-light","dark":"github-dark-dimmed"}}
{
"detail": "No access to this instance."
}
```
The key is locked down exactly as configured. It can only do `read` and `interact` on the instance you specified.
### List your keys
See all scoped keys in your organization:
```bash theme={"theme":{"light":"github-light","dark":"github-dark-dimmed"}}
curl https://api.chowder.dev/v1/keys \
-H "Authorization: Bearer $CHOWDER_KEY"
```
```json Response theme={"theme":{"light":"github-light","dark":"github-dark-dimmed"}}
[
{
"id": "b2f4e8a1-3c7d-4e9f-a5b6-1d8e3f7c2a94",
"organization_id": "4737cccd-3d1d-4790-979b-6825d6a333de",
"name": "frontend-chat",
"key_prefix": "chd_sk_f3a8....",
"is_active": true,
"expires_at": null,
"last_used_at": "2026-02-14T12:05:00Z",
"created_at": "2026-02-14T12:00:00Z"
}
]
```
Notice `raw_key` is **not** in the list response — only the `key_prefix` for identification. Chowder never returns the full key after creation.
Get detailed info on a specific key, including which instances and permissions it has:
```bash theme={"theme":{"light":"github-light","dark":"github-dark-dimmed"}}
curl https://api.chowder.dev/v1/keys/b2f4e8a1-3c7d-4e9f-a5b6-1d8e3f7c2a94 \
-H "Authorization: Bearer $CHOWDER_KEY"
```
```json Response theme={"theme":{"light":"github-light","dark":"github-dark-dimmed"}}
{
"id": "b2f4e8a1-3c7d-4e9f-a5b6-1d8e3f7c2a94",
"organization_id": "4737cccd-3d1d-4790-979b-6825d6a333de",
"name": "frontend-chat",
"key_prefix": "chd_sk_f3a8....",
"is_active": true,
"expires_at": null,
"last_used_at": "2026-02-14T12:05:00Z",
"created_at": "2026-02-14T12:00:00Z",
"instances": [
{
"instance_id": "ead7b76b-f34e-4b91-93e7-9c979cf9e41c",
"permissions": ["read", "interact"]
}
]
}
```
### Revoke a key
When a key is compromised or no longer needed:
```bash theme={"theme":{"light":"github-light","dark":"github-dark-dimmed"}}
curl -X DELETE https://api.chowder.dev/v1/keys/b2f4e8a1-3c7d-4e9f-a5b6-1d8e3f7c2a94 \
-H "Authorization: Bearer $CHOWDER_KEY"
```
Returns `204 No Content`. The key is immediately deactivated. Any request using it will get a `403 Forbidden`.
Revoking a key is instant and permanent. Make sure you're ready — any frontend or integration using that key will break immediately.
## Permission reference
Here's every permission you can assign to a scoped key:
| Permission | What it grants |
| ----------- | -------------------------------------------------------------------------------------------- |
| `read` | View instance details and status (`GET /v1/instances/{id}`, `GET /v1/instances/{id}/status`) |
| `interact` | Send messages and receive responses (`POST /v1/instances/{id}/responses`, session responses) |
| `configure` | Update instance config, manage skills (`PATCH /v1/instances/{id}`, skills endpoints) |
| `files` | Read, write, delete, and move files in the instance workspace |
| `channels` | Connect, disconnect, and manage messaging channels |
For a typical chat frontend, `["read", "interact"]` is all you need. Start minimal and add permissions only when required.
## Multi-instance keys
A single scoped key can access multiple instances with the same permissions:
```bash theme={"theme":{"light":"github-light","dark":"github-dark-dimmed"}}
curl -X POST https://api.chowder.dev/v1/keys \
-H "Authorization: Bearer $CHOWDER_KEY" \
-H "Content-Type: application/json" \
-d '{
"name": "dashboard-key",
"instance_ids": [
"ead7b76b-f34e-4b91-93e7-9c979cf9e41c",
"c4a91d3e-8f2b-4e7a-b5c6-2d9f1e8a4b73",
"f7e23b8d-1a4c-4d96-9e5f-3b7c2d8a1e64"
],
"permissions": ["read", "interact", "configure"]
}'
```
This is useful for admin dashboards that need to manage a fleet of instances.
## Common patterns
```json theme={"theme":{"light":"github-light","dark":"github-dark-dimmed"}}
{
"name": "web-chat",
"instance_ids": [""],
"permissions": ["read", "interact"]
}
```
Minimal access. Can read status and send messages. Can't change config, manage channels, or delete anything.
```json theme={"theme":{"light":"github-light","dark":"github-dark-dimmed"}}
{
"name": "admin-panel",
"instance_ids": ["", "", ""],
"permissions": ["read", "interact", "configure", "files", "channels"]
}
```
Full access to specific instances, but still can't create/delete instances or manage billing (that requires the org key).
```json theme={"theme":{"light":"github-light","dark":"github-dark-dimmed"}}
{
"name": "demo-2026-02",
"instance_ids": [""],
"permissions": ["read", "interact"],
"expires_at": "2026-02-28T23:59:59Z"
}
```
Auto-expires at end of month. Great for demos or trial access.
## What's next?
Put that scoped key to use in a Next.js chat interface.
Wire up a messaging channel to your agent.
# Your First Instance
Source: https://docs.chowder.dev/guides/your-first-instance
A complete walkthrough of creating, configuring, and chatting with an OpenClaw instance.
So you've signed up, you've got your API key, and you're staring at a terminal wondering what to do next. Let's fix that.
By the end of this guide, you'll have a running OpenClaw agent that you can talk to, remembers what you said, and supports multiple conversation threads. All through curl.
This guide assumes you already have an organization and API key. If not, hit the [Quickstart](/quickstart) first — it takes 2 minutes.
## Before we start
Set your API key as an environment variable so you don't have to paste it into every command:
```bash theme={"theme":{"light":"github-light","dark":"github-dark-dimmed"}}
export CHOWDER_KEY="chd_org_your_key_here"
```
Every request in this guide uses `$CHOWDER_KEY` for auth.
## Create an instance
### Spin it up
An instance is a fully isolated OpenClaw agent — its own sandbox, workspace, memory, and gateway. Let's create one:
```bash curl theme={"theme":{"light":"github-light","dark":"github-dark-dimmed"}}
curl -X POST https://api.chowder.dev/v1/instances \
-H "Authorization: Bearer $CHOWDER_KEY" \
-H "Content-Type: application/json" \
-d '{
"name": "my-first-agent",
"model_provider": "anthropic"
}'
```
```python python theme={"theme":{"light":"github-light","dark":"github-dark-dimmed"}}
import requests
resp = requests.post(
"https://api.chowder.dev/v1/instances",
headers={"Authorization": f"Bearer {CHOWDER_KEY}"},
json={"name": "my-first-agent", "model_provider": "anthropic"}
)
print(resp.json())
```
```json Response theme={"theme":{"light":"github-light","dark":"github-dark-dimmed"}}
{
"id": "ead7b76b-f34e-4b91-93e7-9c979cf9e41c",
"organization_id": "4737cccd-3d1d-4790-979b-6825d6a333de",
"name": "my-first-agent",
"status": "provisioning",
"model_provider": "anthropic",
"sandbox_provider": "sandbox",
"sandbox_id": null,
"openclaw_version": "v1",
"openclaw_config": null,
"gateway_url": null,
"region": null,
"error_message": null,
"created_at": "2026-02-14T10:30:00Z",
"updated_at": "2026-02-14T10:30:00Z"
}
```
Notice the `"status": "provisioning"`. Behind the scenes, Chowder is creating a cloud sandbox, installing OpenClaw, running the onboarding wizard, and starting the gateway. This takes about 60–90 seconds.
You can also pass `"model_provider": "openai"` or `"model_provider": "gemini"` if that's your jam. Just make sure you've configured the corresponding API key on your organization first via `PATCH /v1/organization`.
Save that `id` — you'll need it for everything else:
```bash theme={"theme":{"light":"github-light","dark":"github-dark-dimmed"}}
export INSTANCE_ID="ead7b76b-f34e-4b91-93e7-9c979cf9e41c"
```
### Wait for it to be ready
Poll the status endpoint until you see `"running"`:
```bash theme={"theme":{"light":"github-light","dark":"github-dark-dimmed"}}
curl https://api.chowder.dev/v1/instances/$INSTANCE_ID/status \
-H "Authorization: Bearer $CHOWDER_KEY"
```
```json Still provisioning... theme={"theme":{"light":"github-light","dark":"github-dark-dimmed"}}
{
"id": "ead7b76b-f34e-4b91-93e7-9c979cf9e41c",
"status": "provisioning",
"gateway_url": null
}
```
Wait a minute, try again:
```json Ready! theme={"theme":{"light":"github-light","dark":"github-dark-dimmed"}}
{
"id": "ead7b76b-f34e-4b91-93e7-9c979cf9e41c",
"status": "running",
"gateway_url": "https://ead7b76b-f34e.preview.sandbox.work"
}
```
Once you see `"status": "running"`, you're good to go. The `gateway_url` is the internal gateway — you don't need to use it directly, Chowder proxies everything for you.
If the status is `"error"`, check the `error_message` field on the full instance object (`GET /v1/instances/{id}`). Common causes: missing model API key on your org, or a transient sandbox provisioning issue. Delete it and try again.
### Send your first message
Now for the fun part. Send a message and specify which model the agent should use for this turn:
```bash theme={"theme":{"light":"github-light","dark":"github-dark-dimmed"}}
curl -X POST https://api.chowder.dev/v1/instances/$INSTANCE_ID/responses \
-H "Authorization: Bearer $CHOWDER_KEY" \
-H "Content-Type: application/json" \
-d '{
"model": "claude-sonnet-4-20250514",
"input": "Hey! Who are you and what can you do?"
}'
```
```json Response theme={"theme":{"light":"github-light","dark":"github-dark-dimmed"}}
{
"id": "resp_238a6f70-1fe6-42fc-8488-6a3762ef7e68",
"status": "completed",
"model": "claude-sonnet-4-20250514",
"output": [
{
"type": "message",
"role": "assistant",
"content": [
{
"type": "output_text",
"text": "Hey! I just came online — fresh instance, blank slate. I can help you with all sorts of things: write code, search the web, manage files in my workspace, run shell commands, and more. What are we building?"
}
]
}
]
}
```
The response follows the OpenAI-compatible Responses API format. The `output` array contains message objects, each with a `content` array of typed blocks.
You can switch models per-request. Use `claude-sonnet-4-20250514` for complex reasoning, `gpt-4o` for general tasks, or `gpt-4o-mini` when you want speed. The agent's memory persists regardless of which model you pick.
### Verify it remembers context
The agent maintains conversational memory. Send a follow-up and watch it reference your first message:
```bash theme={"theme":{"light":"github-light","dark":"github-dark-dimmed"}}
curl -X POST https://api.chowder.dev/v1/instances/$INSTANCE_ID/responses \
-H "Authorization: Bearer $CHOWDER_KEY" \
-H "Content-Type: application/json" \
-d '{
"model": "claude-sonnet-4-20250514",
"input": "What did I just ask you?"
}'
```
```json Response theme={"theme":{"light":"github-light","dark":"github-dark-dimmed"}}
{
"id": "resp_c91f3a10-2b5e-4d8c-a2f7-84fe1e0d37a2",
"status": "completed",
"model": "claude-sonnet-4-20250514",
"output": [
{
"type": "message",
"role": "assistant",
"content": [
{
"type": "output_text",
"text": "You asked me who I am and what I can do. I told you I'm a freshly spun-up instance that can write code, search the web, manage files, and run commands. Still waiting on what we're building together!"
}
]
}
]
}
```
It remembers. That's the OpenClaw memory system at work — conversations persist across requests, not just within a single API call.
### Create a named session
By default, all messages go to the agent's main conversation thread. But what if you want separate conversations? That's what sessions are for.
Create a new session by sending a message to the session endpoint:
```bash theme={"theme":{"light":"github-light","dark":"github-dark-dimmed"}}
curl -X POST https://api.chowder.dev/v1/instances/$INSTANCE_ID/session/project-alpha/responses \
-H "Authorization: Bearer $CHOWDER_KEY" \
-H "Content-Type: application/json" \
-d '{
"model": "claude-sonnet-4-20250514",
"input": "This session is for project alpha. We are building a CLI tool in Rust. Remember that."
}'
```
```json Response theme={"theme":{"light":"github-light","dark":"github-dark-dimmed"}}
{
"id": "resp_f2e81b34-9a1c-4e57-b6d0-3c7a1f8e5d92",
"status": "completed",
"model": "claude-sonnet-4-20250514",
"output": [
{
"type": "message",
"role": "assistant",
"content": [
{
"type": "output_text",
"text": "Got it — project alpha, Rust CLI tool. I'll keep that context for this session. What's the first feature we're tackling?"
}
]
}
]
}
```
The session ID (`project-alpha`) is whatever string you want. It's created automatically the first time you use it.
### Switch between sessions
Each session has its own isolated memory. Your main conversation doesn't know about project-alpha, and vice versa.
Talk to the main session (no session ID in the URL):
```bash theme={"theme":{"light":"github-light","dark":"github-dark-dimmed"}}
curl -X POST https://api.chowder.dev/v1/instances/$INSTANCE_ID/responses \
-H "Authorization: Bearer $CHOWDER_KEY" \
-H "Content-Type: application/json" \
-d '{
"model": "gpt-4o",
"input": "What project are we working on?"
}'
```
```json Response (main session — no project context) theme={"theme":{"light":"github-light","dark":"github-dark-dimmed"}}
{
"id": "resp_a4d29e8f-7c3b-4a1e-9d5f-2b6c8e4f1a73",
"status": "completed",
"model": "gpt-4o",
"output": [
{
"type": "message",
"role": "assistant",
"content": [
{
"type": "output_text",
"text": "You haven't told me about a specific project yet in this conversation. You did ask who I am and what I can do earlier. Want to start something?"
}
]
}
]
}
```
Now ask the same thing in the project-alpha session:
```bash theme={"theme":{"light":"github-light","dark":"github-dark-dimmed"}}
curl -X POST https://api.chowder.dev/v1/instances/$INSTANCE_ID/session/project-alpha/responses \
-H "Authorization: Bearer $CHOWDER_KEY" \
-H "Content-Type: application/json" \
-d '{
"model": "gpt-4o",
"input": "What project are we working on?"
}'
```
```json Response (project-alpha session — remembers!) theme={"theme":{"light":"github-light","dark":"github-dark-dimmed"}}
{
"id": "resp_d8f41c26-3e7a-4b92-8f1d-5a9c2e7b3d04",
"status": "completed",
"model": "gpt-4o",
"output": [
{
"type": "message",
"role": "assistant",
"content": [
{
"type": "output_text",
"text": "We're working on project alpha — a CLI tool in Rust. You set that context at the start of this session. Ready to dive in?"
}
]
}
]
}
```
Completely isolated. This is great for multi-tenant apps where each user gets their own conversation thread.
### Stop and restart
When you're done for a while, stop the instance to free up resources:
```bash theme={"theme":{"light":"github-light","dark":"github-dark-dimmed"}}
curl -X POST https://api.chowder.dev/v1/instances/$INSTANCE_ID/stop \
-H "Authorization: Bearer $CHOWDER_KEY"
```
```json Response theme={"theme":{"light":"github-light","dark":"github-dark-dimmed"}}
{
"id": "ead7b76b-f34e-4b91-93e7-9c979cf9e41c",
"name": "my-first-agent",
"status": "stopped",
"model_provider": "anthropic",
"sandbox_provider": "sandbox",
"openclaw_version": "v1",
"created_at": "2026-02-14T10:30:00Z",
"updated_at": "2026-02-14T11:45:00Z"
}
```
The sandbox is paused but your data and sessions are preserved. Start it back up when you need it:
```bash theme={"theme":{"light":"github-light","dark":"github-dark-dimmed"}}
curl -X POST https://api.chowder.dev/v1/instances/$INSTANCE_ID/start \
-H "Authorization: Bearer $CHOWDER_KEY"
```
The instance comes back with `"status": "running"` and all your conversations are intact.
You can only stop a running instance and only start a stopped one. If the instance is in any other state (like `provisioning` or `error`), you'll get a `409 Conflict`.
### Delete when done
When you're truly finished with an instance:
```bash theme={"theme":{"light":"github-light","dark":"github-dark-dimmed"}}
curl -X DELETE https://api.chowder.dev/v1/instances/$INSTANCE_ID \
-H "Authorization: Bearer $CHOWDER_KEY"
```
This returns `204 No Content`. The sandbox is destroyed and the instance is marked as `terminated`. This is permanent — there's no undo.
## Recap
Here's what you just did:
| Step | Endpoint | What happened |
| ----------- | -------------------------------------------------- | -------------------------------------- |
| Create | `POST /v1/instances` | Provisioned a sandbox + OpenClaw agent |
| Poll status | `GET /v1/instances/{id}/status` | Waited for `running` |
| Chat | `POST /v1/instances/{id}/responses` | Sent messages, got responses |
| Sessions | `POST /v1/instances/{id}/session/{name}/responses` | Created isolated conversation threads |
| Stop/Start | `POST /v1/instances/{id}/stop` and `/start` | Paused and resumed the instance |
| Delete | `DELETE /v1/instances/{id}` | Cleaned up |
## What's next?
Wire up a Telegram bot so users can DM your agent.
Give your agent web search, code execution, and more.
# What is Chowder?
Source: https://docs.chowder.dev/introduction
Deploy and manage OpenClaw instances through a simple REST API. Connect channels, install skills, and build AI-powered platforms without touching infrastructure.
# What is Chowder?
Chowder is a managed platform for [OpenClaw](https://openclaw.ai) — the open-source agentic framework that turns language models into persistent, tool-using agents with memory, chat channels, and a workspace.
Instead of SSH-ing into servers, writing systemd units, and manually configuring gateway files, you call an API. Chowder handles the rest: provisioning cloud sandboxes, deploying OpenClaw, managing the gateway lifecycle, and proxying requests.
## What you can do
Spin up fully configured OpenClaw instances in seconds. Each one gets its own sandbox, gateway, workspace, and memory.
Send messages to your instances through the API. Use sessions to maintain separate conversation threads.
Wire up Telegram, Discord, Slack, WhatsApp, and 8 more messaging platforms. One API call per channel.
Browse ClawHub and install skills — browser control, web search, code execution, and hundreds more.
## How it works
```
Your app --> Chowder API --> Cloud Sandbox --> OpenClaw Gateway
(sandbox) (agents, tools,
channels, memory)
```
1. **You sign up** and get an organization API key (`chd_org_...`).
2. **You create an instance** — Chowder provisions a sandbox, installs OpenClaw, configures the gateway, and gives you a running agent in \~90 seconds.
3. **You interact** — send messages, connect channels, install skills, manage files. Everything through REST.
4. **Your users interact** — through Telegram, Discord, WhatsApp, or your own frontend. The agent persists across conversations.
## Who is this for?
* **Platform builders** who want to offer AI agents as a feature without running infrastructure.
* **Developers** who want a managed OpenClaw deployment with an API they can integrate into anything.
* **Teams** who need multiple isolated agent instances with different configurations, skills, and channel connections.
## Quick example
Create an instance, send it a message, and get a response — all in three API calls:
```bash theme={"theme":{"light":"github-light","dark":"github-dark-dimmed"}}
# 1. Sign up
curl -X POST https://api.chowder.dev/v1/organization/signup \
-H "Content-Type: application/json" \
-d '{"name": "my-org"}'
# Returns: { "api_key": "chd_org_abc123...", "organization": { ... } }
# 2. Create an instance
curl -X POST https://api.chowder.dev/v1/instances \
-H "Authorization: Bearer chd_org_abc123..." \
-H "Content-Type: application/json" \
-d '{"name": "my-agent"}'
# Returns: { "id": "inst_...", "status": "provisioning", ... }
# Wait ~90 seconds for provisioning...
# 3. Chat
curl -X POST https://api.chowder.dev/v1/instances/inst_.../responses \
-H "Authorization: Bearer chd_org_abc123..." \
-H "Content-Type: application/json" \
-d '{"model": "claude-sonnet-4-20250514", "input": "Hello! What can you do?"}'
# Returns: { "output": [{ "content": [{ "text": "Hey! I'm your new agent..." }] }] }
```
Follow the quickstart to deploy your first instance in under 5 minutes.
# Quickstart
Source: https://docs.chowder.dev/quickstart
Go from zero to a running OpenClaw instance in under 5 minutes.
## Prerequisites
You need:
* A terminal with `curl` (or any HTTP client)
* That's it. No Docker, no Node.js, no config files.
## Step 1: Create your organization
Every Chowder account starts with an organization. This call creates one and returns your API key.
Save the `api_key` from this response — it won't be shown again. This is your **organization key** and grants full access to all resources.
```bash theme={"theme":{"light":"github-light","dark":"github-dark-dimmed"}}
curl -X POST https://api.chowder.dev/v1/organization/signup \
-H "Content-Type: application/json" \
-d '{"name": "acme-corp"}'
```
```json theme={"theme":{"light":"github-light","dark":"github-dark-dimmed"}}
{
"organization": {
"id": "4737cccd-3d1d-4790-979b-6825d6a333de",
"name": "acme-corp",
"subscription_tier": "shrimp",
"instance_limit": 1,
"instance_count": 0
},
"api_key": "chd_org_ea3adc9d38e618552a82c7df4ff75428..."
}
```
You're on the **shrimp** tier (free) which gives you 1 instance. That's enough to get started.
## Step 2: Create an instance
An instance is a running OpenClaw agent with its own sandbox, workspace, and gateway.
```bash theme={"theme":{"light":"github-light","dark":"github-dark-dimmed"}}
export CHOWDER_KEY="chd_org_ea3adc9d38e618552a82c7df4ff75428..."
curl -X POST https://api.chowder.dev/v1/instances \
-H "Authorization: Bearer $CHOWDER_KEY" \
-H "Content-Type: application/json" \
-d '{"name": "my-first-agent"}'
```
```json theme={"theme":{"light":"github-light","dark":"github-dark-dimmed"}}
{
"id": "ead7b76b-f34e-4b91-93e7-9c979cf9e41c",
"name": "my-first-agent",
"status": "provisioning",
"model_provider": "anthropic",
"sandbox_provider": "sandbox",
"openclaw_version": "v1"
}
```
The instance starts in `provisioning` status. Behind the scenes, Chowder is:
1. Creating a cloud sandbox
2. Installing OpenClaw
3. Running the onboarding wizard
4. Configuring gateway authentication
5. Starting the gateway
This takes about 60–90 seconds. You can poll for status:
```bash theme={"theme":{"light":"github-light","dark":"github-dark-dimmed"}}
curl https://api.chowder.dev/v1/instances/ead7b76b-.../status \
-H "Authorization: Bearer $CHOWDER_KEY"
```
Once you see `"status": "running"`, you're good to go.
## Step 3: Talk to your agent
Send a message and get a response. You need to specify a model — the agent will use it for this turn.
```bash theme={"theme":{"light":"github-light","dark":"github-dark-dimmed"}}
curl -X POST https://api.chowder.dev/v1/instances/ead7b76b-.../responses \
-H "Authorization: Bearer $CHOWDER_KEY" \
-H "Content-Type: application/json" \
-d '{
"model": "claude-sonnet-4-20250514",
"input": "Hey! Who are you?"
}'
```
```json theme={"theme":{"light":"github-light","dark":"github-dark-dimmed"}}
{
"id": "resp_238a6f70-1fe6-42fc-8488-6a3762ef7e68",
"status": "completed",
"model": "claude-sonnet-4-20250514",
"output": [
{
"type": "message",
"role": "assistant",
"content": [
{
"type": "output_text",
"text": "Hey! I just came online — fresh instance, blank slate. What should I call myself? What kind of creature am I? Let's figure this out together."
}
]
}
]
}
```
The agent remembers this conversation. Send another message and it'll have context from the first one.
## Step 4: Try sessions
By default, all messages go to the agent's main session. To create a separate conversation thread, use the session endpoint:
```bash theme={"theme":{"light":"github-light","dark":"github-dark-dimmed"}}
# This creates a new session called "project-alpha"
curl -X POST https://api.chowder.dev/v1/instances/ead7b76b-.../session/project-alpha/responses \
-H "Authorization: Bearer $CHOWDER_KEY" \
-H "Content-Type: application/json" \
-d '{
"model": "claude-sonnet-4-20250514",
"input": "This session is for planning project alpha. Remember that."
}'
```
Sessions are isolated — the agent maintains separate memory and context for each one. Switch back to the main session anytime by using the regular `/responses` endpoint.
## What's next?
Wire up a Telegram bot so users can chat with your agent directly.
Give your agent superpowers — web browsing, code execution, and more.
Create restricted keys for frontends and third-party integrations.
Build a chat UI with Next.js that talks to your Chowder instance.