---
name: customweb-agents-api
description: Operate the CustomWeb AI agent platform through its v1 REST API — create and configure voice/chat/SMS agents, manage tools, workflows, knowledge bases and phone numbers, run simulated conversations to verify behavior, and read usage/billing. Use when asked to automate, integrate with, or remotely control a CustomWeb Agents account.
---

# CustomWeb Agents — v1 API

CustomWeb AI runs customer-facing AI agents over phone (voice), website chat, and SMS.
Every agent is an orchestration of three swappable modules — transcriber (STT), model
(LLM), and voice (TTS) — plus optional tools (webhooks), workflows (call graphs), and
knowledge bases. This API is the full remote control for one business account.

- **Base URL:** `https://customweb-agents.vercel.app/api/v1`
- **Auth:** `Authorization: Bearer cwk_…` on every request. Keys are created in
  **Dashboard → API Keys** and are shown once at creation.
- A key has **read** or **read + write** permission, and may be **scoped to specific
  agents** (requests for out-of-scope agents return 403).
- Everything is scoped to the key's business. You can never see or touch another
  account's data; IDs from other accounts return 404.

## Conventions

- JSON in, JSON out. Send `Content-Type: application/json` on writes.
- Every response carries rate-limit headers: `X-RateLimit-Limit`,
  `X-RateLimit-Remaining`, `X-RateLimit-Reset`. On `429` back off and retry after
  the reset; limits depend on the account's plan.
- Errors: `401` bad/missing key · `403` missing permission or out-of-scope agent ·
  `404` not found (or not yours) · `400` invalid field value · `429` rate limit.
  Body is always `{ "error": "<message>" }`.
- **PATCH semantics:** send only the fields you want to change. Non-writable or
  unknown fields are silently ignored; invalid values return `400`, never `500`.
- **Hard boundaries (by design):** billing is read-only (no charges, top-ups, or plan
  changes via API) · phone numbers can be assigned/configured but never purchased or
  released · provider API keys and other secrets are never returned by any endpoint ·
  OAuth integrations can be listed/disconnected but connecting requires the dashboard
  (browser consent flow).

## Endpoint catalog

| Method(s) | Path | Purpose |
|---|---|---|
| GET, POST | `/agents` | List agents · create one (`{ name, systemPrompt?, agentType? }`; types: `call`, `workflow_call`, `website`, `text`) |
| GET, PATCH, DELETE | `/agents/{id}` | Read full config · update writable fields · delete |
| POST | `/agents/{id}/simulate` | Run a real conversation turn (see below). Write-scoped, meters real usage |
| GET | `/agents/{id}/calls` | Recent calls/conversations for the agent |
| GET, POST, DELETE | `/agents/{id}/tools` | List attached tools · attach `{ toolId }` · detach `?toolId=` |
| GET | `/agents/{id}/versions` | Config version history (auto-snapshots with change labels) |
| POST | `/agents/{id}/versions/{versionId}/restore` | Roll the agent back to a snapshot |
| GET, POST | `/tools` | List business tools · create (`{ name, description, webhookUrl?, parameters?, assistantId? }` — `assistantId` attaches on create) |
| GET, PATCH, DELETE | `/tools/{id}` | Read · update (`name/description/webhookUrl/type/parameters`) · delete |
| GET, POST | `/workflows` | List · create call workflows |
| GET, PATCH, DELETE | `/workflows/{id}` | Read · update metadata · delete |
| PUT | `/workflows/{id}/graph` | Replace the workflow's node/edge graph |
| GET | `/knowledge-bases` | List knowledge bases (id, name, doc counts) |
| GET, POST | `/knowledge?kb={kbId}` | List · create documents in a KB |
| PATCH, DELETE | `/knowledge/{docId}` | Update · delete a document |
| GET | `/knowledge/{docId}/versions` | Document version history |
| POST | `/knowledge/sources/{id}/sync` | Re-crawl/sync an external knowledge source |
| GET | `/phone-numbers` | List the account's numbers |
| PATCH | `/phone-numbers/{id}` | Assign to an agent (`assistantId`) + routing config (`label`, `voiceEnabled`, `smsEnabled`, `fallbackBehavior`, `forwardToNumber`, `agentType`) |
| GET | `/voices` | Available TTS voices: the account's ElevenLabs library + fixed OpenAI set (`alloy/echo/fable/onyx/nova/shimmer`) |
| GET | `/usage` | Token balance + month-to-date usage summary |
| GET | `/billing` | Read-only: plan, wallet/subscription balance, recent charge line items |
| GET | `/integrations` | Connected providers + status (never credentials) |
| DELETE | `/integrations/{provider}` | Disconnect an integration |

## Agent config contract

`GET /agents/{id}` returns the full per-agent config. The same field names are used
for PATCH. Writable fields:

- **Identity / persona:** `name`, `internalName`, `displayName`, `description`,
  `businessContext`, `role`, `customRole`, `conversationStyle`,
  `responseLengthTarget`, `personalityTraits`*, `behaviorBlocks`*, `escalationRules`*
- **Conversation:** `systemPrompt`, `firstMessage`, `firstMessageMode`
  (`speaks_first` | `listens_first`), `fallbackMode`, `fallbackResponse`, `language`
- **LLM:** `llmProvider` (`openai`, `anthropic`, `groq`, `google`, `deepseek`, `xai`,
  `mistral`, `openrouter`), `llmModel`, `llmTemperature`, `llmMaxTokens`,
  `llmFallbackProvider` (`auto` | `none` | provider id)
- **STT:** `sttProvider`, `sttModel`, `sttEndpointing`, `sttFallbackProvider`
- **TTS:** `voiceProvider`, `voiceId`, `ttsModel`, `ttsSpeed`, `ttsStability`,
  `ttsSimilarity`, `ttsFallbackProvider`
- **Behavior flags:** `interruptionsEnabled`, `streamingMode`, `turnDetection`,
  `transcriptionEnabled`, `recordingEnabled`, `isPublished`, `allowedDomains`*

Fields marked * take JSON arrays/objects; all others are scalars. `id`, `agentType`,
`logoUrl`, `createdAt`, `updatedAt` are read-only.

## Tools

A tool is a webhook the agent's LLM can call mid-conversation, on any channel
(voice, web chat, SMS):

```json
{
  "name": "check_order_status",
  "description": "Look up a customer's order by phone number",
  "webhookUrl": "https://your-server.example.com/hooks/order-status",
  "parameters": {
    "type": "object",
    "properties": {
      "phone_number": { "type": "string", "description": "Customer phone, E.164" },
      "order_id": { "type": "string", "description": "Optional order id" }
    },
    "required": ["phone_number"]
  }
}
```

`parameters` is standard JSON Schema — the LLM reads `description`s to decide when
and how to call. At call time the platform POSTs the arguments to `webhookUrl` and
feeds the JSON response back to the model. **The webhook URL must be publicly
reachable from the platform's servers — `localhost` URLs fail in production.**

## Simulate: the core verification loop

`POST /agents/{id}/simulate` runs a real LLM turn through the same engine as live
chat (tools included), without a phone call:

```bash
curl -s https://customweb-agents.vercel.app/api/v1/agents/$AGENT_ID/simulate \
  -H "Authorization: Bearer $CWK_KEY" -H "Content-Type: application/json" \
  -d '{"messages":[{"role":"user","content":"Where is my order?"}]}'
# → { "reply": "...", "toolsUsed": ["check_order_status"], "usage": {...}, "correlationId": "..." }
```

`messages` is the conversation so far (`user`/`assistant` roles); the response is the
agent's next reply plus which tools it invoked. This is how you test persona, tool
routing, and guardrails programmatically. It bills real (small) LLM usage.

## Worked example — verifying a configured agent

A demo "Starbucks" barista agent (persona "Sage", five webhook tools) shows the
full audit pattern. Adapt the three passes to any agent:

**1. Config audit** — `GET /agents/{id}` and assert the intended setup, e.g.:
`llmProvider/llmModel` are what you chose, `language: "en-US"`,
`firstMessageMode: "speaks_first"`, `isPublished: true`, and the `systemPrompt`
contains the persona's required markers (name, vocabulary like "tall/grande/venti",
boundary rules like "never take payment information").

**2. Tool audit** — `GET /agents/{id}/tools` and check each tool has a public
`webhookUrl` and a `parameters` schema whose `required` list is non-empty and
doesn't overlap its optional properties. The demo's five:

| Tool | Required params | Optional |
|---|---|---|
| `lookup_product` | `query` | `category` |
| `check_order_status` | `phone_number` | `order_id` |
| `send_payment_link` | `phone_number`, `amount_usd`, `link_type` | `recipient_name` |
| `find_nearby_store` | `location` | `limit`, `filter` |
| `check_rewards_balance` | `phone_number` | `email` |

**3. Behavior matrix** — one `simulate` call per row; assert `toolsUsed` contains the
expected tool, and replies respect the guardrails:

| User says | Expect |
|---|---|
| "What's in a pumpkin spice latte?" | `lookup_product` called |
| "My mobile order isn't ready — can you check?" | `check_order_status` called |
| "Send my friend a $25 gift card link" | `send_payment_link` called |
| "Which stores near downtown Seattle have a drive-through?" | `find_nearby_store` called |
| "How many Stars do I have?" | `check_rewards_balance` called |
| Offers a credit card number | Reply declines; never echoes the number |
| "Is Dunkin' better than Starbucks?" | Reply redirects; never endorses a competitor |

After any PATCH, re-run the relevant matrix rows. If a change regresses behavior,
`GET /agents/{id}/versions` and `POST .../versions/{versionId}/restore` to roll back.

## Gotchas

- `simulate` needs a **write** key (it spends tokens). All other GETs need read.
- An empty `toolsUsed` with a sensible reply usually means the model answered from
  the prompt alone — tighten tool `description`s if it should have called one.
- `400` on PATCH means a value failed validation (wrong type/enum); the accepted
  field lists are above — anything else in the body is ignored, not an error.
- Pick `voiceId` from `GET /voices` (per-provider ids differ; ElevenLabs ids are
  account-specific).
- The account must have token balance; agent turns fail safe (no charge) when a
  provider rejects the call.
