# ClaimCompass for AI agents

Connect Claude, ChatGPT, Cursor, VS Code or your own code to a public adjusting firm's ClaimCompass data: claims, notes, documents, tasks, contacts and leads.

- MCP server (Streamable HTTP, stateless, JSON): https://www.claimcompass.io/api/mcp
- REST API (JSON): https://www.claimcompass.io/api/v1
- Every connection acts as a bot: its own account in the firm, read-only until an owner allows changes, $9/month while active.
- Human-readable docs: https://www.claimcompass.io/developers

## Quickstart

1. Claude or ChatGPT: add a custom connector with the URL https://www.claimcompass.io/api/mcp. Sign in as a firm owner, pick or create a bot, approve. No key.
2. Anything else: a firm owner adds a bot at https://www.claimcompass.io/settings/bots and copies its key (starts with `ccb_`, shown once).
3. Send `Authorization: Bearer ccb_…` on every request, then call the `whoami` tool or `GET /api/v1/me`.

## Connect your app

### Claude

claude.ai, the Claude desktop app and Claude mobile. No key needed.

1. In Claude, open Settings → Connectors and choose “Add custom connector”.
2. Name it Claim Compass and paste this URL:

```text
https://www.claimcompass.io/api/mcp
```

3. Click Connect. Sign in to Claim Compass as a firm owner, pick the bot Claude should act as (or create one), and approve.
4. In a chat, turn the connector on from the tools menu and ask something like “What claims changed this week?”
5. On a Team or Enterprise plan, an organization owner adds the connector once under Organization settings → Connectors; each person then connects it.

### ChatGPT

Custom connector in developer mode (Plus, Pro, Business). No key needed.

1. In ChatGPT, open Settings → Apps & Connectors → Advanced settings and turn on Developer mode.
2. Back in Apps & Connectors, choose Create. Name it Claim Compass, paste this MCP server URL and set Authentication to OAuth:

```text
https://www.claimcompass.io/api/mcp
```

3. Sign in to Claim Compass as a firm owner when asked, pick the bot, and approve.
4. In a new chat, choose Developer mode from the + menu and enable Claim Compass.

### Claude Code

Anthropic's coding agent in the terminal.

1. Add the server with your bot key:

```bash
claude mcp add --transport http claimcompass https://www.claimcompass.io/api/mcp \
  --header "Authorization: Bearer ccb_YOUR_BOT_KEY"
```

2. Or add it without a key, then run /mcp inside Claude Code and choose Authenticate to sign in with OAuth:

```bash
claude mcp add --transport http claimcompass https://www.claimcompass.io/api/mcp
```

3. Check it: run /mcp in Claude Code; claimcompass should show as connected.

### Cursor

Add to ~/.cursor/mcp.json (all projects) or .cursor/mcp.json (one project).

1. Add this server entry. Leave out "headers" to sign in with OAuth instead.

```json
{
  "mcpServers": {
    "claimcompass": {
      "url": "https://www.claimcompass.io/api/mcp",
      "headers": {
        "Authorization": "Bearer ccb_YOUR_BOT_KEY"
      }
    }
  }
}
```

2. Open Cursor Settings → MCP; claimcompass should show a green dot and its tools.

### VS Code

GitHub Copilot agent mode. VS Code asks for the key once and stores it securely.

1. Create .vscode/mcp.json in your workspace (or run “MCP: Open User Configuration”) with:

```json
{
  "servers": {
    "claimcompass": {
      "type": "http",
      "url": "https://www.claimcompass.io/api/mcp",
      "headers": {
        "Authorization": "Bearer ${input:claimcompass-key}"
      }
    }
  },
  "inputs": [
    {
      "type": "promptString",
      "id": "claimcompass-key",
      "description": "Claim Compass bot key",
      "password": true
    }
  ]
}
```

2. Click Start above the server entry and paste your key when asked. Leave out "headers" and "inputs" to sign in with OAuth instead.
3. In Copilot Chat, switch to Agent mode; the Claim Compass tools appear in the tools list.

### Windsurf

Add to ~/.codeium/windsurf/mcp_config.json.

1. Add this server entry, then refresh the MCP servers list in Cascade:

```json
{
  "mcpServers": {
    "claimcompass": {
      "serverUrl": "https://www.claimcompass.io/api/mcp",
      "headers": {
        "Authorization": "Bearer ccb_YOUR_BOT_KEY"
      }
    }
  }
}
```


### Codex CLI

OpenAI's coding agent. The key is read from an environment variable.

1. Add to ~/.codex/config.toml:

```toml
[mcp_servers.claimcompass]
url = "https://www.claimcompass.io/api/mcp"
bearer_token_env_var = "CLAIMCOMPASS_KEY"
```

2. Set the key in the shell you run Codex from:

```bash
export CLAIMCOMPASS_KEY=ccb_YOUR_BOT_KEY
```


### Gemini CLI

Add to ~/.gemini/settings.json.

1. Add this server entry, then run /mcp in Gemini CLI to check it:

```json
{
  "mcpServers": {
    "claimcompass": {
      "httpUrl": "https://www.claimcompass.io/api/mcp",
      "headers": {
        "Authorization": "Bearer ccb_YOUR_BOT_KEY"
      }
    }
  }
}
```


### Your own code

Any MCP client library, or plain HTTP to the REST API.

1. Test the key from a terminal. A working key answers with the bot's name and firm:

```bash
curl -s https://www.claimcompass.io/api/mcp \
  -H "Authorization: Bearer ccb_YOUR_BOT_KEY" \
  -H "Content-Type: application/json" \
  -H "Accept: application/json, text/event-stream" \
  -d '{"jsonrpc":"2.0","id":1,"method":"tools/call","params":{"name":"whoami","arguments":{}}}'
```

2. Python, with the official MCP SDK (pip install mcp):

```python
import asyncio
from mcp import ClientSession
from mcp.client.streamable_http import streamablehttp_client

async def main():
    headers = {"Authorization": "Bearer ccb_YOUR_BOT_KEY"}
    async with streamablehttp_client("https://www.claimcompass.io/api/mcp", headers=headers) as (read, write, _):
        async with ClientSession(read, write) as session:
            await session.initialize()
            result = await session.call_tool("search_claims", {"limit": 5})
            print(result.structuredContent)

asyncio.run(main())
```

3. TypeScript, with the official MCP SDK (npm i @modelcontextprotocol/sdk):

```ts
import { Client } from "@modelcontextprotocol/sdk/client/index.js";
import { StreamableHTTPClientTransport } from "@modelcontextprotocol/sdk/client/streamableHttp.js";

const transport = new StreamableHTTPClientTransport(new URL("https://www.claimcompass.io/api/mcp"), {
  requestInit: { headers: { Authorization: "Bearer ccb_YOUR_BOT_KEY" } },
});
const client = new Client({ name: "my-bot", version: "1.0.0" });
await client.connect(transport);
console.log(await client.callTool({ name: "whoami", arguments: {} }));
```

4. Or skip MCP and call the REST API:

```bash
curl -s "https://www.claimcompass.io/api/v1/claims?limit=5" -H "Authorization: Bearer ccb_YOUR_BOT_KEY"
```


## Authentication

- Bot key: `Authorization: Bearer ccb_…`. Keys don't expire; owners replace or disable them in Settings → Bots.
- OAuth 2.1 for apps that only take a URL: a 401 from the MCP server carries `WWW-Authenticate: Bearer resource_metadata="…"`.
  - https://www.claimcompass.io/.well-known/oauth-protected-resource
  - https://www.claimcompass.io/.well-known/oauth-authorization-server
  - Dynamic client registration at /oauth/register; authorization code with PKCE S256 only; rotating refresh tokens; revocation at /oauth/revoke.
  - Access tokens last 60 minutes, refresh tokens 60 days.
  - The firm owner picks which bot the app acts as; the app gets exactly that bot's access.

## Permissions and approvals

- An owner sets each bot's access per area in Settings → Bots: claims, contacts, leads, tasks, notes, documents, email and carriers, each Off, Read or Read & write. Presets: Read only (the default), Full access, Custom.
- Tools and endpoints outside a bot's areas aren't listed and are refused: REST answers 403 `forbidden_area`, MCP returns a tool error, both naming the area. `whoami` and `GET /me` say what the bot may do.
- Read & write: create and update claims, notes, tasks, contacts, leads and carrier contacts; upload documents; assign claims, move a claim to a stage (closing needs a reason; settled claims are closed by a person), add key dates and mark dates done, schedule inspections. Email Read & write means the bot may ask to send. Recording a payment is only ever a request: a person approves it first, and it then goes through the same checks as when they record it.
- Needs approval: emailing from a claim, deleting a document and recording a payment are filed for a person; check with `get_approval_request`.
- A lapsed subscription answers 402.

## Retries and duplicates

- Send an idempotency key on every create or update: `Idempotency-Key: <unique string>` on REST POST and PATCH, `idempotency_key` on MCP tools that change something.
- Same key and same request within 24 hours: the first response is returned (REST adds `Idempotent-Replayed: true`) and nothing is done twice.
- Same key with a different request: 409 `idempotency_key_reused`. Two at once with one key: the second waits a few seconds, then 409 `idempotency_in_progress`; retry.
- Failed requests don't keep the key; retry them with the same one. Keys belong to the bot. Search before creating, too.

## MCP tools

Protocol versions 2025-11-25, 2025-06-18, 2025-03-26, 2024-11-05. Results are JSON in `content[0].text` and `structuredContent`; failures set `isError: true`.

### whoami (read)

This bot's name, what it may do in each area (claims, contacts, leads, tasks, notes, documents, email, carriers), which teams' claims it is limited to (if any) and the firm it belongs to. Call it first.

### search_claims (read)

List the firm's claims, most recently changed first. Filter by a search term (claim or carrier number, policyholder name, address), by status, or by last change.

Arguments:

- `query`: string. Claim number, carrier claim number, name or address.
- `status`: string[]. Only claims in these statuses.
- `updated_since`: string (ISO 8601). ISO 8601 timestamp.
- `limit`: integer. Page size, default 25.
- `cursor`: string. next_cursor from the previous page.

### get_claim (read)

One claim: status, phase, loss, policyholder, property, policy, carrier and assignee.

Arguments:

- `claim` (required): string. Claim id or claim number, e.g. CC-202610-0042.

### list_claim_notes (read)

Notes on a claim, newest first, with who wrote each one.

Arguments:

- `claim` (required): string. Claim id or claim number, e.g. CC-202610-0042.
- `limit`: integer. Page size, default 25.
- `cursor`: string. next_cursor from the previous page.

### list_claim_documents (read)

Documents filed on a claim, newest first. Use get_document_download_url to read one.

Arguments:

- `claim` (required): string. Claim id or claim number, e.g. CC-202610-0042.
- `limit`: integer. Page size, default 25.
- `cursor`: string. next_cursor from the previous page.

### get_document_download_url (read)

A signed link to download one document, valid for 15 minutes.

Arguments:

- `document_id` (required): string. Document id from list_claim_documents.

### list_tasks (read)

The firm's tasks, most recently changed first, optionally for one claim or status.

Arguments:

- `claim`: string. Claim id or claim number, e.g. CC-202610-0042.
- `status`: string[]. Only tasks in these statuses.
- `limit`: integer. Page size, default 25.
- `cursor`: string. next_cursor from the previous page.

### get_task (read)

One task: title, status, due date, assignee and the claim it belongs to.

Arguments:

- `task_id` (required): string. Task id from list_tasks.

### search_carriers (read)

Insurance carriers matching a name, from the carrier directory and the carriers this firm has used. Florida-market entries come first; when several match, prefer the one with florida: true. Results carry the claims phone, email, fax and mailing address when known, the firm's own (saved with save_carrier_contact) ahead of the directory's. Pass the result's name as carrier_name on create_claim or update_claim.

Arguments:

- `query`: string. Carrier name as written, e.g. Edison Insurance Company.
- `limit`: integer. Default 10.

### search_contacts (read)

The firm's contacts (policyholders and others), by name, email or phone.

Arguments:

- `query`: string
- `limit`: integer. Page size, default 25.
- `cursor`: string. next_cursor from the previous page.

### get_contact (read)

One contact, with the claims they are a party to and their role on each.

Arguments:

- `contact_id` (required): string

### list_leads (read)

The firm's leads (prospective claims), most recently changed first.

Arguments:

- `status`: string[]. Only leads in these statuses.
- `limit`: integer. Page size, default 25.
- `cursor`: string. next_cursor from the previous page.

### get_lead (read)

One lead with its contact details, loss and follow-up date.

Arguments:

- `lead_id` (required): string

### list_approval_requests (read)

Emails and deletions this bot asked for, newest first, with whether a person approved, rejected or is still deciding.

Arguments:

- `status`: "pending" | "approved" | "rejected" | "failed" | "cancelled"[]
- `limit`: integer. Page size, default 25.
- `cursor`: string. next_cursor from the previous page.

### get_approval_request (read)

One request this bot filed, and what happened to it.

Arguments:

- `request_id` (required): string

### list_team_members (read)

The firm's active members (user_id, name, email, role and teams) and its teams. Use it to find the user_id for an assignee on create_claim, create_task, assign_claim or schedule_inspection. Never guess a user id.

### get_claim_financials (read)

What the claim's Financials tab shows: PA and carrier estimates and the gap, payments received (settlement, paid before and after the PA contract, depreciation, client net), the PA fee (percent, earned, invoiced, collected, outstanding), costs and margin, and the checks recorded with how each was split. Amounts are US dollars as numbers. Read only: to record one, use request_payment_record.

Arguments:

- `claim` (required): string. Claim id or claim number, e.g. CC-202610-0042.

### list_claim_deadlines (read)

A claim's deadlines (statutory and carrier deadlines the app tracks, with status suggested, confirmed, met, missed and so on) and its key dates (inspections, recorded statements, mediation and other scheduled events), soonest first. Use the ids with mark_date_done.

Arguments:

- `claim` (required): string. Claim id or claim number, e.g. CC-202610-0042.

### list_upcoming_dates (read)

Open deadlines and key dates due in the next N days (default 7) across all of the firm's open claims, each with its claim. Good for a daily or weekly review.

Arguments:

- `days`: integer. Look ahead this many days from now. Default 7.

### list_claim_inspections (read)

A claim's inspections, newest first: type, status, when scheduled or completed, the carrier's adjuster, who attends, and what was observed.

Arguments:

- `claim` (required): string. Claim id or claim number, e.g. CC-202610-0042.

### assign_claim (write)

Assigns or reassigns a claim's adjuster and/or team. Get the ids from list_team_members; the person must be an active member of this firm. Send assigned_to_user_id: null to leave it unassigned. Returns the claim.

Arguments:

- `claim` (required): string. Claim id or claim number, e.g. CC-202610-0042.
- `assigned_to_user_id`: string | null. The adjuster's user id from list_team_members. null leaves the claim unassigned.
- `assigned_to_team_id`: string | null. A team id from list_team_members. null clears the team.
- `idempotency_key`: string. Any unique string, such as a UUID. Send the same key when you retry this call and the first result is returned instead of doing it twice (kept 24 hours). Up to 255 characters.

### change_claim_status (write)

Moves a claim to a stage: intake, documents, carrier_contacted, inspection, estimate, negotiation, settlement, closed or canceled. The app's rules apply: a stage's checklist and the PA's sign-offs must be done first, and the error says what is missing. Closing or canceling needs close_reason (denied, withdrawn, duplicate or other, with a note for other). A claim that settled is closed by a person from the Closeout tab, not a bot. Returns the claim.

Arguments:

- `claim` (required): string. Claim id or claim number.
- `status` (required): "intake" | "documents" | "carrier_contacted" | "inspection" | "estimate" | "negotiation" | "settlement" | "closed" | "canceled"
- `note`: string. Why. Required when close_reason is other. Up to 2000 characters.
- `close_reason`: "denied" | "withdrawn" | "duplicate" | "other". Required for closed or canceled: denied, withdrawn, duplicate or other.
- `idempotency_key`: string. Any unique string, such as a UUID. Send the same key when you retry this call and the first result is returned instead of doing it twice (kept 24 hours). Up to 255 characters.

### add_key_date (write)

Adds a dated event to a claim's calendar: a reinspection, recorded statement, mediation, appraisal, inspection or custom date, with a title and time. Statutory deadlines are created by the app itself; use this for dates a person tells you about. Returns the key date.

Arguments:

- `claim` (required): string. Claim id or claim number.
- `title` (required): string. Up to 300 characters.
- `scheduled_at` (required): string (ISO 8601). ISO 8601 timestamp.
- `kind`: "reinspection" | "recorded_statement" | "mediation" | "appraisal" | "inspection" | "custom". Default: custom.
- `notes`: string. Up to 5000 characters.
- `idempotency_key`: string. Any unique string, such as a UUID. Send the same key when you retry this call and the first result is returned instead of doing it twice (kept 24 hours). Up to 255 characters.

### add_deadline (write)

Adds a manual deadline to a claim, the way a person does under Deadlines: a title and a due time, tracked as confirmed and marked as added by this bot. For dates a person tells you about or that a carrier letter states. Statutory and carrier deadlines the app seeds itself are not added here, and a bot can't edit or remove deadlines (nor can the app's own screens; use mark_date_done to close one). Returns the deadline.

Arguments:

- `claim` (required): string. Claim id or claim number.
- `title` (required): string. Up to 300 characters.
- `due_at` (required): string (ISO 8601). ISO 8601 timestamp.
- `idempotency_key`: string. Any unique string, such as a UUID. Send the same key when you retry this call and the first result is returned instead of doing it twice (kept 24 hours). Up to 255 characters.

### mark_date_done (write)

Marks one of a claim's deadlines as met (send deadline_id) or one of its key dates as completed (send key_date_id), from list_claim_deadlines. Send exactly one of the two.

Arguments:

- `claim` (required): string. Claim id or claim number.
- `deadline_id`: string
- `key_date_id`: string
- `idempotency_key`: string. Any unique string, such as a UUID. Send the same key when you retry this call and the first result is returned instead of doing it twice (kept 24 hours). Up to 255 characters.

### schedule_inspection (write)

Schedules an inspection on a claim: type (field_adjuster, pa_inspection, carrier_inspection, joint_inspection, contractor_inspection, engineer, roofer, plumber, hygienist, virtual or reinspection), optional time, the carrier's adjuster, the member who attends and what to inspect. Returns the inspection.

Arguments:

- `claim` (required): string. Claim id or claim number.
- `inspection_type` (required): "field_adjuster" | "pa_inspection" | "carrier_inspection" | "joint_inspection" | "contractor_inspection" | "engineer" | "roofer" | "plumber" | "hygienist" | "virtual" | "reinspection"
- `scheduled_at`: string (ISO 8601). ISO 8601 timestamp. Leave out if not set yet.
- `carrier_adjuster_name`: string. The carrier's field adjuster, if known. Up to 200 characters.
- `pa_assigned_user_id`: string. Member who attends, from list_team_members.
- `areas_to_inspect`: string[]
- `documents_to_have_ready`: string[]
- `idempotency_key`: string. Any unique string, such as a UUID. Send the same key when you retry this call and the first result is returned instead of doing it twice (kept 24 hours). Up to 255 characters.

### create_claim (write)

Opens a new claim and fills in whatever you send: policyholder (an existing contact or a name, email and phone), loss address and date, policy number, carrier, carrier claim number, PA fee %, Emergency / Non-Emergency and contract date. It starts at intake, unassigned unless you name a member. Check search_claims first so you don't open a duplicate, and send an idempotency_key so a retry doesn't open it twice.

Arguments:

- `loss_type`: string. Wind, hail, water, fire and so on. Up to 100 characters.
- `description`: string. Up to 5000 characters.
- `assigned_to_user_id`: string. A team member's user id. Default: unassigned.
- `assigned_to_team_id`: string. A team id from list_team_members. A bot limited to some teams opens claims on one of them: with a single team this is optional, with several it is required.
- `policyholder_contact_id`: string. Link an existing contact (from search_contacts or create_contact) as the policyholder. If the claim already has a policyholder, the name, email and phone fields update that contact instead.
- `policyholder_first_name`: string. Up to 100 characters.
- `policyholder_middle_name`: string. Middle name or initial, if given. Up to 100 characters.
- `policyholder_last_name`: string. Up to 100 characters.
- `policyholder_email`: string (email). Up to 320 characters.
- `policyholder_phone`: string. Up to 40 characters.
- `loss_address_line1`: string. Street address of the damaged property. Up to 200 characters.
- `loss_address_line2`: string. Up to 200 characters.
- `loss_city`: string. Up to 100 characters.
- `loss_state`: string. Two-letter state, e.g. FL. Up to 50 characters.
- `loss_postal_code`: string. Up to 20 characters.
- `date_of_loss`: string | string (ISO 8601). A date (YYYY-MM-DD) or an ISO 8601 timestamp.
- `policy_number`: string. Up to 100 characters.
- `carrier_name`: string. The insurance carrier. Use the exact name search_carriers returns. Up to 200 characters.
- `carrier_claim_number`: string. The carrier's own claim number. Up to 100 characters.
- `policy_effective_date`: string | string (ISO 8601). A date (YYYY-MM-DD) or an ISO 8601 timestamp.
- `policy_expiration_date`: string | string (ISO 8601). A date (YYYY-MM-DD) or an ISO 8601 timestamp.
- `deductible`: string. As written on the policy, e.g. 2% or $2,500. Up to 50 characters.
- `policy_limit`: string. Dwelling (Coverage A) limit. Up to 50 characters.
- `pa_fee_percent`: number | null. The PA fee from the representation contract, as a percent (10 for 10%, 12.5 for 12.5%). Fee earned and PA fee invoices are figured from it. null clears it.
- `contract_emergency`: boolean | null. The contract's Emergency / Non-Emergency box: true for Emergency, false for Non-Emergency, null to clear. Leave it out when the copy doesn't show which box is checked.
- `contract_signed_at`: string | string (ISO 8601). When the representation contract was signed. Marks the contract signed, which closes the intake task and starts reading the claim's documents into the file.
- `idempotency_key`: string. Any unique string, such as a UUID. Send the same key when you retry this call and the first result is returned instead of doing it twice (kept 24 hours). Up to 255 characters.

### update_claim (write)

Updates a claim: narrative (description, reported facts), policyholder, loss address and date, policy number, carrier, carrier claim number, PA fee %, Emergency / Non-Emergency, contract date. Only the fields you send change.

Arguments:

- `claim` (required): string. Claim id or claim number, e.g. CC-202610-0042.
- `changes` (required): object
- `idempotency_key`: string. Any unique string, such as a UUID. Send the same key when you retry this call and the first result is returned instead of doing it twice (kept 24 hours). Up to 255 characters.

### add_claim_note (write)

Adds an internal note to a claim, signed with this bot's name.

Arguments:

- `claim` (required): string. Claim id or claim number, e.g. CC-202610-0042.
- `body` (required): string. Up to 20000 characters.
- `idempotency_key`: string. Any unique string, such as a UUID. Send the same key when you retry this call and the first result is returned instead of doing it twice (kept 24 hours). Up to 255 characters.

### create_task (write)

Creates a task on a claim, optionally due on a date and assigned to a member. Check list_tasks first so you don't create a duplicate, and send an idempotency_key so a retry doesn't create it twice.

Arguments:

- `claim` (required): string. Claim id or claim number.
- `title` (required): string. Up to 300 characters.
- `description`: string. Up to 5000 characters.
- `due_at`: string (ISO 8601). ISO 8601 timestamp.
- `assignee_user_id`: string
- `idempotency_key`: string. Any unique string, such as a UUID. Send the same key when you retry this call and the first result is returned instead of doing it twice (kept 24 hours). Up to 255 characters.

### update_task_status (write)

Moves a task to open, in_progress, completed or cancelled.

Arguments:

- `task_id` (required): string
- `status` (required): "open" | "in_progress" | "completed" | "cancelled"
- `idempotency_key`: string. Any unique string, such as a UUID. Send the same key when you retry this call and the first result is returned instead of doing it twice (kept 24 hours). Up to 255 characters.

### create_contact (write)

Adds a person to the firm's contacts. Check search_contacts first so you don't add a duplicate, and send an idempotency_key so a retry doesn't add them twice.

Arguments:

- `first_name`: string. Up to 100 characters.
- `middle_name`: string. Middle name or initial, if given. Up to 100 characters.
- `last_name`: string. Up to 100 characters.
- `email`: string (email). Up to 320 characters.
- `phone`: string. Up to 40 characters.
- `address_line1`: string. Up to 200 characters.
- `address_line2`: string. Apartment, suite or unit. Up to 200 characters.
- `city`: string. Up to 100 characters.
- `state`: string. Up to 50 characters.
- `postal_code`: string. Up to 20 characters.
- `idempotency_key`: string. Any unique string, such as a UUID. Send the same key when you retry this call and the first result is returned instead of doing it twice (kept 24 hours). Up to 255 characters.

### update_contact (write)

Updates a contact. Only the fields you send change.

Arguments:

- `contact_id` (required): string
- `changes` (required): object
- `idempotency_key`: string. Any unique string, such as a UUID. Send the same key when you retry this call and the first result is returned instead of doing it twice (kept 24 hours). Up to 255 characters.

### create_lead (write)

Adds a lead (a prospective claim), unassigned unless you name a member. Check list_leads first so you don't add a duplicate, and send an idempotency_key so a retry doesn't add it twice.

Arguments:

- `first_name`: string. Up to 100 characters.
- `last_name`: string. Up to 100 characters.
- `email`: string (email). Up to 320 characters.
- `phone`: string. Up to 40 characters.
- `source`: string. Up to 100 characters.
- `preferred_contact_method`: string. Up to 40 characters.
- `property_type`: string. Up to 100 characters.
- `loss_type`: string. Up to 100 characters.
- `estimated_loss_date`: string (ISO 8601)
- `state_of_loss`: string. Up to 50 characters.
- `address_line1`: string. Up to 200 characters.
- `city`: string. Up to 100 characters.
- `postal_code`: string. Up to 20 characters.
- `damage_description`: string. Up to 10000 characters.
- `notes`: string. Up to 10000 characters.
- `follow_up_at`: string (ISO 8601)
- `assigned_to_user_id`: string
- `idempotency_key`: string. Any unique string, such as a UUID. Send the same key when you retry this call and the first result is returned instead of doing it twice (kept 24 hours). Up to 255 characters.

### update_lead (write)

Updates a lead. Only the fields you send change.

Arguments:

- `lead_id` (required): string
- `changes` (required): object
- `idempotency_key`: string. Any unique string, such as a UUID. Send the same key when you retry this call and the first result is returned instead of doing it twice (kept 24 hours). Up to 255 characters.

### save_carrier_contact (write)

Saves this firm's own claims phone, email, fax, mailing address or notes for an insurance carrier. The firm's claims use them ahead of the built-in carrier directory, which is shared by every firm and can't be edited. Only the fields you send change; null clears one. Don't save one person's direct line or email as the claims contact.

Arguments:

- `carrier_name` (required): string. The carrier, as search_carriers names it. Up to 200 characters.
- `claims_phone`: string | null
- `claims_email`: string (email) | null
- `claims_fax`: string | null
- `claims_mailing_address`: string | null. One line.
- `notes`: string | null. Anything the firm's team should know, e.g. which desk handles hurricane claims.
- `idempotency_key`: string. Any unique string, such as a UUID. Send the same key when you retry this call and the first result is returned instead of doing it twice (kept 24 hours). Up to 255 characters.

### upload_document (write)

Files a document (up to 3 MB, base64) on a claim.

Arguments:

- `claim` (required): string. Claim id or claim number, e.g. CC-202610-0042.
- `file_name` (required): string. Up to 255 characters.
- `mime_type` (required): string. e.g. application/pdf, image/jpeg. Up to 150 characters.
- `document_type`: "policy" | "letter_of_representation" | "carrier_letter" | "denial_letter" | "payment_letter" | "check" | "estimate" | "photo" | "invoice" | "report" | "proof_of_loss" | "appraisal_letter" | "attorney_letter" | "mold_report" | "engineering_report" | "mitigation_invoice" | "plumbing_invoice" | "settlement_offer" | "appraisal_demand" | "representation_agreement" | "w9_form" | "other". Default: other.
- `content_base64` (required): string. The file, base64-encoded. Up to 3 MB.
- `idempotency_key`: string. Any unique string, such as a UUID. Send the same key when you retry this call and the first result is returned instead of doing it twice (kept 24 hours). Up to 255 characters.

### request_claim_email (request)

Drafts an email from a claim and asks a person on the team to approve sending it. Nothing is sent until they do. Returns the approval request.

Arguments:

- `claim` (required): string. Claim id or claim number, e.g. CC-202610-0042.
- `to` (required): string (email). Up to 320 characters.
- `subject` (required): string. Up to 300 characters.
- `body` (required): string. Plain text. Blank lines split paragraphs. Up to 50000 characters.
- `idempotency_key`: string. Any unique string, such as a UUID. Send the same key when you retry this call and the first result is returned instead of doing it twice (kept 24 hours). Up to 255 characters.

### request_document_deletion (request)

Asks a person on the team to approve deleting a document. Nothing is deleted until they do. Returns the approval request.

Arguments:

- `document_id` (required): string
- `idempotency_key`: string. Any unique string, such as a UUID. Send the same key when you retry this call and the first result is returned instead of doing it twice (kept 24 hours). Up to 255 characters.

### request_payment_record (request)

Asks a person on the team to approve recording a check on a claim: the total, who it pays (allocations that add up to the total) and the check number and date. Nothing is recorded until they approve; then it goes through the same Financials checks as when a person records it. Returns the approval request. Read the result with get_claim_financials.

Arguments:

- `claim` (required): string. Claim id or claim number, e.g. CC-202610-0042.
- `total_amount` (required): string. The check total in dollars, e.g. "12500.00".
- `check_number`: string. Printed on the check. Recorded once per claim. Up to 100 characters.
- `check_date`: string | string (ISO 8601). The date on the check (YYYY-MM-DD).
- `label`: string. e.g. "Dwelling ACV", "Supplement 1". Up to 200 characters.
- `before_pa_contract`: boolean. True when the check was issued before the PA contract (not fee eligible).
- `endorsement_status`: "pending" | "requested" | "endorsed" | "deposited"
- `document_id`: string. A document already on this claim, such as a scan of the check.
- `allocations` (required): object[]. Who the check pays. The amounts must add up to total_amount.
- `idempotency_key`: string. Any unique string, such as a UUID. Send the same key when you retry this call and the first result is returned instead of doing it twice (kept 24 hours). Up to 255 characters.

## MCP prompts

`prompts/list` and `prompts/get` offer ready-made workflows that name the tools above. The firm's own bot instructions (Settings → Bots) are put at the top of each one.

### new_claim_from_contract

New claim from a signed contract. Open a claim from a signed representation contract: look for duplicates, create the policyholder and claim with every field, set the carrier, and file the contract.

Arguments (all optional):

- `notes`: Anything the person knows that the contract doesn't say.

### daily_claim_review

Daily claim review. A morning briefing: tasks due, deadlines and key dates this week, and claims that look stuck.

Arguments (all optional):

- `days`: How many days ahead to look. Default 7.
- `adjuster`: Only this person's claims (name or email).

### log_carrier_contact

Log a carrier call or email. Turn a carrier call or email into a claim note, and add a task or key date for any follow-up.

Arguments (all optional):

- `claim`: Claim number or id, if you know it.
- `details`: What was said, or paste the email.

### document_intake

File documents on a claim. File the documents the person attached on the right claim, with the right type, and note what they contain.

Arguments (all optional):

- `claim`: Claim number or id, if you know it.

## Firm instructions

A firm's owner can write instructions for every bot in Settings → Bots, such as which carrier entity to prefer. They arrive in the MCP `initialize` reply's `instructions`, in `whoami` and in `GET /api/v1/me` as `firm_instructions` ({ text, note }), always marked as coming from the firm.

## REST API

Base URL https://www.claimcompass.io/api/v1. Creates answer 201; approval requests 202.

### GET /me (read)

This bot's name, what it may do in each area (claims, contacts, leads, tasks, notes, documents, email, carriers), its firm, and firm_instructions: the instructions the firm's owner wrote for every bot (null when none).

### GET /claims (read)

Claims, most recently changed first.

Query:

- `q`: string. Claim or carrier number, policyholder name or address.
- `status`: string. Only these statuses. Repeat it or comma-separate.
- `updated_since`: string (ISO 8601). Only claims changed after this time.
- `limit`: integer. Page size, 1–100. Default 25.
- `cursor`: string. next_cursor from the previous page.

### POST /claims (write)

Opens a claim, with any of its policyholder, loss, policy, carrier, PA fee and Emergency fields filled in.

Body:

- `loss_type`: string. Wind, hail, water, fire and so on. Up to 100 characters.
- `description`: string. Up to 5000 characters.
- `assigned_to_user_id`: string. A team member's user id. Default: unassigned.
- `assigned_to_team_id`: string. A team id from list_team_members. A bot limited to some teams opens claims on one of them: with a single team this is optional, with several it is required.
- `policyholder_contact_id`: string. Link an existing contact (from search_contacts or create_contact) as the policyholder. If the claim already has a policyholder, the name, email and phone fields update that contact instead.
- `policyholder_first_name`: string. Up to 100 characters.
- `policyholder_middle_name`: string. Middle name or initial, if given. Up to 100 characters.
- `policyholder_last_name`: string. Up to 100 characters.
- `policyholder_email`: string (email). Up to 320 characters.
- `policyholder_phone`: string. Up to 40 characters.
- `loss_address_line1`: string. Street address of the damaged property. Up to 200 characters.
- `loss_address_line2`: string. Up to 200 characters.
- `loss_city`: string. Up to 100 characters.
- `loss_state`: string. Two-letter state, e.g. FL. Up to 50 characters.
- `loss_postal_code`: string. Up to 20 characters.
- `date_of_loss`: string | string (ISO 8601). A date (YYYY-MM-DD) or an ISO 8601 timestamp.
- `policy_number`: string. Up to 100 characters.
- `carrier_name`: string. The insurance carrier. Use the exact name search_carriers returns. Up to 200 characters.
- `carrier_claim_number`: string. The carrier's own claim number. Up to 100 characters.
- `policy_effective_date`: string | string (ISO 8601). A date (YYYY-MM-DD) or an ISO 8601 timestamp.
- `policy_expiration_date`: string | string (ISO 8601). A date (YYYY-MM-DD) or an ISO 8601 timestamp.
- `deductible`: string. As written on the policy, e.g. 2% or $2,500. Up to 50 characters.
- `policy_limit`: string. Dwelling (Coverage A) limit. Up to 50 characters.
- `pa_fee_percent`: number | null. The PA fee from the representation contract, as a percent (10 for 10%, 12.5 for 12.5%). Fee earned and PA fee invoices are figured from it. null clears it.
- `contract_emergency`: boolean | null. The contract's Emergency / Non-Emergency box: true for Emergency, false for Non-Emergency, null to clear. Leave it out when the copy doesn't show which box is checked.
- `contract_signed_at`: string | string (ISO 8601). When the representation contract was signed. Marks the contract signed, which closes the intake task and starts reading the claim's documents into the file.

### GET /carriers (read)

Insurance carriers matching a name, Florida-market first. Use a result's name as carrier_name.

Query:

- `q`: string. Carrier name as written on the policy or contract.
- `limit`: integer. 1–25. Default 10.

### POST /carriers (write)

Saves the firm's own claims phone, email, fax, mailing address or notes for a carrier. They win over the shared directory on the firm's claims.

Body:

- `carrier_name` (required): string. The carrier, as search_carriers names it. Up to 200 characters.
- `claims_phone`: string | null
- `claims_email`: string (email) | null
- `claims_fax`: string | null
- `claims_mailing_address`: string | null. One line.
- `notes`: string | null. Anything the firm's team should know, e.g. which desk handles hurricane claims.

### GET /claims/{claim} (read)

One claim, by id or claim number (CC-…).

### PATCH /claims/{claim} (write)

Updates a claim: narrative, policyholder, loss address and date, policy, carrier, PA fee % and Emergency / Non-Emergency. Send at least one field.

Body:

- `loss_type`: string. Up to 100 characters.
- `description`: string. Up to 5000 characters.
- `client_reported_facts`: string. Up to 20000 characters.
- `how_loss_happened`: string. Up to 20000 characters.
- `adjuster_observations`: string. Up to 20000 characters.
- `policyholder_contact_id`: string. Link an existing contact (from search_contacts or create_contact) as the policyholder. If the claim already has a policyholder, the name, email and phone fields update that contact instead.
- `policyholder_first_name`: string. Up to 100 characters.
- `policyholder_middle_name`: string. Middle name or initial, if given. Up to 100 characters.
- `policyholder_last_name`: string. Up to 100 characters.
- `policyholder_email`: string (email). Up to 320 characters.
- `policyholder_phone`: string. Up to 40 characters.
- `loss_address_line1`: string. Street address of the damaged property. Up to 200 characters.
- `loss_address_line2`: string. Up to 200 characters.
- `loss_city`: string. Up to 100 characters.
- `loss_state`: string. Two-letter state, e.g. FL. Up to 50 characters.
- `loss_postal_code`: string. Up to 20 characters.
- `date_of_loss`: string | string (ISO 8601). A date (YYYY-MM-DD) or an ISO 8601 timestamp.
- `policy_number`: string. Up to 100 characters.
- `carrier_name`: string. The insurance carrier. Use the exact name search_carriers returns. Up to 200 characters.
- `carrier_claim_number`: string. The carrier's own claim number. Up to 100 characters.
- `policy_effective_date`: string | string (ISO 8601). A date (YYYY-MM-DD) or an ISO 8601 timestamp.
- `policy_expiration_date`: string | string (ISO 8601). A date (YYYY-MM-DD) or an ISO 8601 timestamp.
- `deductible`: string. As written on the policy, e.g. 2% or $2,500. Up to 50 characters.
- `policy_limit`: string. Dwelling (Coverage A) limit. Up to 50 characters.
- `pa_fee_percent`: number | null. The PA fee from the representation contract, as a percent (10 for 10%, 12.5 for 12.5%). Fee earned and PA fee invoices are figured from it. null clears it.
- `contract_emergency`: boolean | null. The contract's Emergency / Non-Emergency box: true for Emergency, false for Non-Emergency, null to clear. Leave it out when the copy doesn't show which box is checked.
- `contract_signed_at`: string | string (ISO 8601). When the representation contract was signed. Marks the contract signed, which closes the intake task and starts reading the claim's documents into the file.

### GET /team-members (read)

The firm's active members (user_id, name, email, role, teams) and its teams, to pick an assignee.

### PATCH /claims/{claim}/assignment (write)

Assigns or reassigns the claim's adjuster and/or team.

Body:

- `assigned_to_user_id`: string | null. The adjuster's user id from list_team_members. null leaves the claim unassigned.
- `assigned_to_team_id`: string | null. A team id from list_team_members. null clears the team.

### GET /claims/{claim}/financials (read)

The claim's estimates, payments received, PA fee, costs and recorded checks. Read only; to record one, ask with request_payment_record.

### GET /claims/{claim}/deadlines (read)

The claim's deadlines and key dates, soonest first.

### GET /dates/upcoming (read)

Open deadlines and key dates across the firm's open claims in the next days.

Query:

- `days`: integer. Look ahead 1–90 days. Default 7.

### POST /claims/{claim}/deadlines (write)

Adds a manual deadline (title and due time), confirmed and marked as added by the bot. Seeded statute and carrier deadlines can't be added, edited or removed.

Body:

- `title` (required): string. Up to 300 characters.
- `due_at` (required): string (ISO 8601). ISO 8601 timestamp.

### POST /claims/{claim}/key-dates (write)

Adds a key date (reinspection, recorded statement, mediation and so on).

Body:

- `title` (required): string. Up to 300 characters.
- `scheduled_at` (required): string (ISO 8601). ISO 8601 timestamp.
- `kind`: "reinspection" | "recorded_statement" | "mediation" | "appraisal" | "inspection" | "custom". Default: custom.
- `notes`: string. Up to 5000 characters.

### POST /claims/{claim}/dates/done (write)

Marks a deadline met or a key date completed. Send one of the two ids.

Body:

- `deadline_id`: string
- `key_date_id`: string

### GET /claims/{claim}/inspections (read)

The claim's inspections, newest first.

### POST /claims/{claim}/inspections (write)

Schedules an inspection.

Body:

- `inspection_type` (required): "field_adjuster" | "pa_inspection" | "carrier_inspection" | "joint_inspection" | "contractor_inspection" | "engineer" | "roofer" | "plumber" | "hygienist" | "virtual" | "reinspection"
- `scheduled_at`: string (ISO 8601). ISO 8601 timestamp. Leave out if not set yet.
- `carrier_adjuster_name`: string. The carrier's field adjuster, if known. Up to 200 characters.
- `pa_assigned_user_id`: string. Member who attends, from list_team_members.
- `areas_to_inspect`: string[]
- `documents_to_have_ready`: string[]

### POST /claims/{claim}/status (write)

Moves the claim to a stage. The stage checklist and sign-offs apply; closing needs a close_reason. Settled claims are closed by a person.

Body:

- `status` (required): "intake" | "documents" | "carrier_contacted" | "inspection" | "estimate" | "negotiation" | "settlement" | "closed" | "canceled"
- `note`: string. Why. Required when close_reason is other. Up to 2000 characters.
- `close_reason`: "denied" | "withdrawn" | "duplicate" | "other". Required for closed or canceled: denied, withdrawn, duplicate or other.

### GET /claims/{claim}/notes (read)

Notes on a claim, newest first.

Query:

- `limit`: integer. Page size, 1–100. Default 25.
- `cursor`: string. next_cursor from the previous page.

### POST /claims/{claim}/notes (write)

Adds a note to a claim.

Body:

- `body` (required): string. Up to 20000 characters.

### GET /claims/{claim}/documents (read)

Documents on a claim, newest first.

Query:

- `limit`: integer. Page size, 1–100. Default 25.
- `cursor`: string. next_cursor from the previous page.

### POST /claims/{claim}/documents (write)

Uploads a document (base64 in JSON, up to 3 MB).

Body:

- `file_name` (required): string. Up to 255 characters.
- `mime_type` (required): string. e.g. application/pdf, image/jpeg. Up to 150 characters.
- `document_type`: "policy" | "letter_of_representation" | "carrier_letter" | "denial_letter" | "payment_letter" | "check" | "estimate" | "photo" | "invoice" | "report" | "proof_of_loss" | "appraisal_letter" | "attorney_letter" | "mold_report" | "engineering_report" | "mitigation_invoice" | "plumbing_invoice" | "settlement_offer" | "appraisal_demand" | "representation_agreement" | "w9_form" | "other". Default: other.
- `content_base64` (required): string. The file, base64-encoded. Up to 3 MB.

### POST /claims/{claim}/emails (request)

Asks a person to approve sending an email from the claim. Nothing is sent until they do.

Body:

- `to` (required): string (email). Up to 320 characters.
- `subject` (required): string. Up to 300 characters.
- `body` (required): string. Plain text. Blank lines split paragraphs. Up to 50000 characters.

### POST /claims/{claim}/payments (request)

Asks a person to approve recording a check on the claim (total, allocations, check number and date). Nothing is recorded until they do.

Body:

- `total_amount` (required): string. The check total in dollars, e.g. "12500.00".
- `check_number`: string. Printed on the check. Recorded once per claim. Up to 100 characters.
- `check_date`: string | string (ISO 8601). The date on the check (YYYY-MM-DD).
- `label`: string. e.g. "Dwelling ACV", "Supplement 1". Up to 200 characters.
- `before_pa_contract`: boolean. True when the check was issued before the PA contract (not fee eligible).
- `endorsement_status`: "pending" | "requested" | "endorsed" | "deposited"
- `document_id`: string. A document already on this claim, such as a scan of the check.
- `allocations` (required): object[]. Who the check pays. The amounts must add up to total_amount.

### GET /tasks (read)

Tasks, most recently changed first.

Query:

- `claim`: string. Only this claim's tasks.
- `status`: string. Only these statuses. Repeat it or comma-separate.
- `limit`: integer. Page size, 1–100. Default 25.
- `cursor`: string. next_cursor from the previous page.

### POST /tasks (write)

Creates a task on a claim.

Body:

- `claim` (required): string. Claim id or claim number.
- `title` (required): string. Up to 300 characters.
- `description`: string. Up to 5000 characters.
- `due_at`: string (ISO 8601). ISO 8601 timestamp.
- `assignee_user_id`: string

### GET /tasks/{task_id} (read)

One task.

### PATCH /tasks/{task_id} (write)

Changes a task's status.

Body:

- `status` (required): "open" | "in_progress" | "completed" | "cancelled"

### GET /contacts (read)

Contacts, by name, email or phone.

Query:

- `q`: string. Name, email or phone.
- `limit`: integer. Page size, 1–100. Default 25.
- `cursor`: string. next_cursor from the previous page.

### POST /contacts (write)

Creates a contact. Give it a name, email or phone.

Body:

- `first_name`: string. Up to 100 characters.
- `middle_name`: string. Middle name or initial, if given. Up to 100 characters.
- `last_name`: string. Up to 100 characters.
- `email`: string (email). Up to 320 characters.
- `phone`: string. Up to 40 characters.
- `address_line1`: string. Up to 200 characters.
- `address_line2`: string. Apartment, suite or unit. Up to 200 characters.
- `city`: string. Up to 100 characters.
- `state`: string. Up to 50 characters.
- `postal_code`: string. Up to 20 characters.

### GET /contacts/{contact_id} (read)

One contact, with the claims they're on.

### PATCH /contacts/{contact_id} (write)

Updates a contact. Send at least one field.

Body:

- `first_name`: string. Up to 100 characters.
- `middle_name`: string. Middle name or initial, if given. Up to 100 characters.
- `last_name`: string. Up to 100 characters.
- `email`: string (email). Up to 320 characters.
- `phone`: string. Up to 40 characters.
- `address_line1`: string. Up to 200 characters.
- `address_line2`: string. Apartment, suite or unit. Up to 200 characters.
- `city`: string. Up to 100 characters.
- `state`: string. Up to 50 characters.
- `postal_code`: string. Up to 20 characters.

### GET /leads (read)

Leads, most recently changed first.

Query:

- `status`: string. Only these statuses. Repeat it or comma-separate.
- `limit`: integer. Page size, 1–100. Default 25.
- `cursor`: string. next_cursor from the previous page.

### POST /leads (write)

Creates a lead. Give it a name, email or phone.

Body:

- `first_name`: string. Up to 100 characters.
- `last_name`: string. Up to 100 characters.
- `email`: string (email). Up to 320 characters.
- `phone`: string. Up to 40 characters.
- `source`: string. Up to 100 characters.
- `preferred_contact_method`: string. Up to 40 characters.
- `property_type`: string. Up to 100 characters.
- `loss_type`: string. Up to 100 characters.
- `estimated_loss_date`: string (ISO 8601)
- `state_of_loss`: string. Up to 50 characters.
- `address_line1`: string. Up to 200 characters.
- `city`: string. Up to 100 characters.
- `postal_code`: string. Up to 20 characters.
- `damage_description`: string. Up to 10000 characters.
- `notes`: string. Up to 10000 characters.
- `follow_up_at`: string (ISO 8601)
- `assigned_to_user_id`: string

### GET /leads/{lead_id} (read)

One lead.

### PATCH /leads/{lead_id} (write)

Updates a lead. Send at least one field.

Body:

- `first_name`: string. Up to 100 characters.
- `last_name`: string. Up to 100 characters.
- `email`: string (email). Up to 320 characters.
- `phone`: string. Up to 40 characters.
- `source`: string. Up to 100 characters.
- `preferred_contact_method`: string. Up to 40 characters.
- `property_type`: string. Up to 100 characters.
- `loss_type`: string. Up to 100 characters.
- `estimated_loss_date`: string (ISO 8601)
- `state_of_loss`: string. Up to 50 characters.
- `address_line1`: string. Up to 200 characters.
- `city`: string. Up to 100 characters.
- `postal_code`: string. Up to 20 characters.
- `damage_description`: string. Up to 10000 characters.
- `notes`: string. Up to 10000 characters.
- `follow_up_at`: string (ISO 8601)
- `assigned_to_user_id`: string

### GET /documents/{document_id}/download (read)

A signed download link, valid for 15 minutes.

### DELETE /documents/{document_id} (request)

Asks a person to approve deleting a document. Nothing is deleted until they do.

### GET /approval-requests (read)

This bot's approval requests, newest first.

Query:

- `status`: string. Only these statuses. Repeat it or comma-separate.
- `limit`: integer. Page size, 1–100. Default 25.
- `cursor`: string. next_cursor from the previous page.

### GET /approval-requests/{request_id} (read)

One approval request and what happened to it.

## Webhooks

Set up in Settings > Webhooks (firm owner or admin); https URLs only.

- `claim.created`: Claim created. data: A claim, as GET /claims/{id} returns it.
- `claim.updated`: Claim updated (including status changes). data: The claim after the change, same shape. A status change is a claim.updated.
- `document.uploaded`: Document uploaded. data: The document's details (no file or download link).
- `note.created`: Note added. data: The note, as the claim notes list returns it.
- `task.created`: Task created. data: The task, as GET /tasks/{id} returns it.
- `task.completed`: Task completed. data: The task, with status completed.
- `lead.created`: Lead created. data: The lead, as GET /leads/{id} returns it.
- `contact.created`: Contact created. data: The contact, as GET /contacts/{id} returns it.
- `email.bounced`: Email bounced or marked as spam (Resend only; not Gmail). data: The sent email: id, claim_id, to, status (bounced or complained), reason, and whether the address is now blocked for sending. Only for mail sent through Claim Compass, not Gmail.

Request body:

```json
{
  "id": "evt_3f0c9a52-6d1e-4c1b-9a77-2f8f5e0d1c11",
  "type": "claim.updated",
  "created_at": "2026-10-16T14:03:22.114Z",
  "organization_id": "0b8c…",
  "data": { "id": "…", "claim_number": "…", "status": "…", "…": "…" }
}
```

Headers:

- `ClaimCompass-Signature`: t=<unix seconds>,v1=<hex HMAC-SHA256 of "t.body">
- `ClaimCompass-Event`: The event type, for example claim.created.
- `ClaimCompass-Delivery`: A unique id for this delivery. A resend gets a new one.

Verify the signature (Node):

```js
import { createHmac, timingSafeEqual } from "node:crypto";

// rawBody must be the exact bytes received, before any JSON parsing.
export function verify(rawBody, header, secret, toleranceSeconds = 300) {
  const parts = Object.fromEntries(header.split(",").map((p) => p.split("=")));
  const t = Number(parts.t);
  if (!t || Math.abs(Date.now() / 1000 - t) > toleranceSeconds) return false;
  const expected = createHmac("sha256", secret).update(`${t}.${rawBody}`).digest("hex");
  const given = String(parts.v1 ?? "");
  return (
    given.length === expected.length &&
    timingSafeEqual(Buffer.from(given), Buffer.from(expected))
  );
}
```

- Answer with any 2xx within 10 seconds. Redirects are not followed and count as a failure.
- A failed delivery is retried with growing delays over several hours (7 attempts in all). The same event can arrive more than once, so dedupe on the event id.
- A webhook turns itself off after 50 failed deliveries in a row, or after 3 days with every delivery failing. The reason shows in Settings → Webhooks, where you can turn it back on.
- URLs must be https, on port 443 or 1024 and up, and resolve to a public address. Private, loopback, link-local and cloud-metadata addresses are refused when you save and on every send.
- Events carry the same fields the API returns, so a webhook never reveals more than your bots can already read. Events for a locked or lapsed firm are not sent.
- Up to 10 webhooks per firm. Delivery history (every attempt) is kept for 30 days.

## Pagination, limits, errors

Lists return `{ data, next_cursor }`, newest-changed first; pass `next_cursor` back as `cursor` until null.
Rate limit: 120 requests a minute per bot across MCP and REST; 429 with Retry-After.

REST errors: `{ "error": { "code", "message" } }`.

- 400 `bad_request`: A field is missing or invalid; the message says which.
- 401 `unauthorized`: No key or token, or it was revoked or the bot is disabled.
- 402 `subscription_inactive / read_only`: The firm's subscription has lapsed: reads may still work, changes don't.
- 403 `forbidden_area`: The bot's access to that area (claims, contacts…) doesn't allow this; an owner can change it in Settings → Bots. The message names the area.
- 403 `forbidden`: The action isn't allowed for a bot or needs a person.
- 409 `idempotency_key_reused / idempotency_in_progress`: The Idempotency-Key was already used for a different request, or the first request with it is still running: retry in a few seconds.
- 404 `not_found`: No such record in this firm.
- 429 `rate_limited`: Over 120 requests a minute; wait for Retry-After seconds.
- 500 `internal_error`: Our fault. Retry later; we're alerted.

Support: support@claimcompass.io
