{"openapi":"3.1.0","info":{"title":"Finn API","version":"v1","summary":"Create voice agents, place calls, read and update CRM records, start Finn Operator cases and decide approvals.","description":"The Finn API is a REST API for building on Finn voice agents (\"Finns\"): create and configure Finns, rent phone numbers, upload audiences, start inbound and outbound deployments, place single calls, read call results and the wallet ledger, and receive a signed `call.completed` webhook when each call ends.\n\nThe API is in **beta**. Every operation here is live; shapes can still change before general availability.\n\n## What this API covers\nThis API covers Finn's voice agents (scopes `voice:read`, `voice:write`), CRM records (Records), and Finn Operator cases and approvals (Cases, Approvals), and the chat inbox (Chat, beta: switched on per organization). AI assistants can also reach Finn agents and the CRM through the [MCP server](https://www.hirefinn.ai/docs/mcp).\n\n## Getting started\n1. Create an API key in the dashboard under **Settings → Integrations → API keys**.\n2. Pick a voice with `GET /voices`.\n3. Create a Finn with `POST /finns` (`name` and `voice` are required).\n4. Place a test call to your own phone with `POST /calls`, or create an audience with `POST /audiences` and start an outbound deployment with `POST /deployments` from a number listed by `GET /phone_numbers`.\n5. Add a webhook endpoint in the dashboard to receive `call.completed` after each call, and read any call with `GET /calls/{call_uuid}`.\n\nThe step-by-step version with full requests is the [API quickstart](https://www.hirefinn.ai/docs/api-quickstart).\n\n## Authentication\nSend an API key as a bearer token: `Authorization: Bearer finn_live_...`. Create and revoke keys in the dashboard under **Settings → Integrations → API keys**; there is no API for managing keys. Each key belongs to one organization and reaches only that organization's data, limited by the key's scopes. Scopes are `product:access` (`voice:read`, `crm:write`, `operator:read`, `approvals:write`...; `chat:read` and `chat:write` are for the separate Chat API); the names keys were made with before (`voice`, `cases:read`, `cases:write`, `dnc:write`) still work and mean what they did. A new key gets `voice` (read and write) unless you pick others; a key made before scopes existed keeps what it had. A missing scope returns `403 insufficient_scope`. See the `ApiKey` security scheme and [scopes](https://www.hirefinn.ai/docs/api-scopes). Every key is live: there is no sandbox, so calls are real and billed.\n\nCall the API from your server. A request carrying a browser `Origin` that Finn does not allow is rejected with `403 origin_not_allowed`.\n\n## Requests and responses\nRequest bodies are JSON (`Content-Type: application/json`, at most 10 MB). Successful responses have the shape `{ \"success\": true, \"data\": ... }`; list endpoints add `limit` and `offset`. Records, Cases and Approvals don't use this envelope: they return the object itself (for example `{ \"contact\": ... }`). IDs are UUIDs, timestamps are ISO 8601 and phone numbers are E.164 where noted. A resource in another organization returns `404`, the same as one that does not exist.\n\n## Errors\nErrors return an HTTP status and `{ \"error\": \"<code>\", \"message\": \"...\" }`. Check `error`, not `message`. `internal_error` has no `message`. Errors that need action in the dashboard (compliance, billing, API keys) include an `action_url`.\n\nA few failures happen before an endpoint runs and use a different shape: a body that is not valid JSON returns `400` with `{ \"success\": false, \"error\": \"<parser message>\" }` (even without a key), a body over 10 MB returns `413`, and an unknown path returns `404` with `{ \"success\": false, \"message\": \"...\" }`.\n\n## Rate limits\nLimits are counted per API key in fixed 60-second windows, per endpoint group, and shared across all of Finn's servers; each operation lists its limit. Responses carry `RateLimit-Policy`, `RateLimit-Limit`, `RateLimit-Remaining` and `RateLimit-Reset` (seconds until the window resets). Over the limit returns `429 rate_limited` with `Retry-After`; nothing was done, so it is safe to retry after waiting.\n\n## Pagination\nList endpoints take `limit` and `offset` and return no total count (except audience contacts and the do-not-call list). Keep requesting with a larger `offset` until a page returns fewer than `limit` items. Records use `page` and `page_size`; cases use `before`; approvals use `cursor`.\n\n## Retries\nThe writes that spend money (`POST /calls`, `POST /deployments`, `POST /deployments/inbound`, `POST /phone_numbers`) and starting a case accept an `Idempotency-Key` header (Chat sends require one): a retry with the same key and body returns the first response instead of doing the work twice. Other writes are not idempotent; check state before retrying them after a timeout or a `5xx`.\n\n## Webhooks\nAdd webhook endpoints in the dashboard under **Settings → Integrations → Webhooks**; there is no API for managing them. See the `call.completed` webhook for the signature and retry behaviour; `contact.updated`, `case.created`, `case.completed` and `approval.requested` are also described here. The full list of 14 events is in [webhook-events](https://www.hirefinn.ai/docs/webhook-events)."},"servers":[{"url":"https://api.hirefinn.ai/api/v1","description":"Production"}],"security":[{"ApiKey":[]}],"tags":[{"name":"Calls","description":"Place a single outbound call and read the record of any call."},{"name":"Finns","description":"Create, read, update and delete Finns (voice agents), including their call settings."},{"name":"Voices","description":"Every voice your organization can give a Finn, from all the voice providers your plan includes."},{"name":"Phone numbers","description":"List, search, rent and release phone numbers. Assign a number to a Finn by deploying."},{"name":"Audiences","description":"Contact lists that outbound deployments dial."},{"name":"Deployments","description":"Put a Finn to work on a number: outbound deployments dial an audience; inbound deployments answer calls."},{"name":"Wallet","description":"Read the credit balance and the ledger. Top-ups are made in the dashboard."},{"name":"Do not call","description":"Numbers Finn must never call."},{"name":"Records","description":"CRM records (contacts), their kinds, fields, timeline and notes. Scopes `crm:read`, `crm:write`, `crm:sensitive:read`."},{"name":"Cases","description":"Finn Operator cases: list, read, read the event log, and start one on an agent. Scopes `operator:read`, `operator:write` (older keys: `cases:read`, `cases:write`)."},{"name":"Approvals","description":"Approvals a Finn Operator case is waiting on. Read with `approvals:read` (older keys: `cases:read`); decide with `approvals:write` (single approvals only, never payments, never an edited proposal)."},{"name":"Agents","description":"Build Finn Operator agents by API key: list, AI-draft, create, edit and sandbox-test. Needs `operator:build` and the feature turned on for your organization. Publishing always needs a person in Finn."},{"name":"Chat","description":"The chat inbox: channels, conversations, messages and chat contacts. Scopes `chat:read`, `chat:write`. Beta, switched on per organization. Lists use `{ data, next_cursor, has_more }` and errors use the `V1Error` body."},{"name":"Webhooks","description":"Webhook endpoints: where Finn sends events (`call.completed`, record and Operator events), signed with the endpoint's `whsec_` secret. Scope `admin:webhooks`, which a key gets only when it is picked. Switched on per organization. Lists use `{ data, next_cursor, has_more }` and errors use the `V1Error` body. The events and their payloads are under **Webhooks** in this document."},{"name":"Admin","description":"Read your organization's profile, members, integrations and API key metadata. Scope `admin:read`, organization API keys (and, where turned on, org admins' own OAuth or MCP tokens). Beta, switched on per organization. Lists use `{ data, next_cursor, has_more }` and errors use the `V1Error` body."},{"name":"Sales","description":"Sales AI: plays, runs, prospects, drafts and threads; run a play, edit or skip a draft. Never approves or sends. Scopes `sales:read`, `sales:write`. Beta, switched on per organization (off: 404)."},{"name":"Dialer","description":"Parallel dialer reads: sessions, analytics, follow-ups and cached lead briefs. Scope `dialer:read`. Beta, switched on per organization (off: 404)."},{"name":"CRM","description":"The CRM as a person in Finn: the operations the Finn MCP CRM tools use, for a member's OAuth token or MCP key. Scopes `crm:read`, `crm:sensitive:read`, `crm:write`, `crm:stage`, `crm:docs:write`, `crm:import`, `crm:share`. Beta, switched on per organization. Objects are the CRM's own (camelCase); lists use `{ data, next_cursor, has_more }`; errors use the `V1Error` body."}],"paths":{"/calls":{"post":{"tags":["Calls"],"operationId":"createCall","summary":"Place a call","description":"Places one outbound call with a Finn. The Finn supplies the voice, prompt, language, transfer number and call flow; you supply the destination.\n\nIf the Finn's `call_flow` is turned on, the call runs it with its tools (end call, call transfer, Cal.com booking), as deployment calls do. The call uses the Finn's `system_prompt` (added after `identity_text`) but not `max_call_duration_minutes`, `idle_reminder_message`, `idle_reminder_after_seconds` or `end_call_on_silence_seconds`: those apply only to calls from deployments, and a single call runs with Finn's defaults for them.\n\nThe response is `202 Accepted` once the call has been accepted for dialing. It does not mean anyone answered: the outcome arrives later on the call record (`GET /calls/{call_uuid}`) and in the `call.completed` webhook.\n\nCalls placed with this endpoint use a Finn-managed caller ID that you cannot choose. To call from a number you rent, or to call a list of contacts, create an outbound deployment instead.\n\nSend an `Idempotency-Key` header so a retry after a timeout or a `5xx` returns the first response instead of placing a second, billed call. Without the header, retrying a request that reached the server places a second call.\n\n**Rate limit:** 60 requests per minute per API key.","requestBody":{"required":true,"content":{"application/json":{"schema":{"$ref":"#/components/schemas/CreateCallRequest"},"example":{"finn_id":"8f14e45f-ceea-467a-9f6a-1c0e5b2a77d1","to_number":"9887729247","country_code":"91"}}}},"responses":{"202":{"description":"The call was accepted for dialing. Or a `PendingApproval` when the request is held for a person to approve (nothing has run yet).","headers":{"RateLimit-Policy":{"$ref":"#/components/headers/RateLimitPolicy"},"RateLimit-Limit":{"$ref":"#/components/headers/RateLimitLimit"},"RateLimit-Remaining":{"$ref":"#/components/headers/RateLimitRemaining"},"RateLimit-Reset":{"$ref":"#/components/headers/RateLimitReset"},"Idempotent-Replayed":{"$ref":"#/components/headers/IdempotentReplayed"}},"content":{"application/json":{"schema":{"oneOf":[{"type":"object","required":["success","data"],"properties":{"success":{"type":"boolean","const":true},"data":{"$ref":"#/components/schemas/CallCreated"}}},{"$ref":"#/components/schemas/PendingApproval"}]},"example":{"success":true,"data":{"call_uuid":"b7e3c9a2-4d1f-4e8b-9a6c-2f5d8e1b3c70","finn_id":"8f14e45f-ceea-467a-9f6a-1c0e5b2a77d1","to_number":"4155550142","status":"initiated"}}}}},"400":{"description":"The request failed validation. Also returned when `Idempotency-Key` is not 1 to 255 visible ASCII characters.","content":{"application/json":{"schema":{"allOf":[{"$ref":"#/components/schemas/Error"},{"type":"object","properties":{"error":{"type":"string","enum":["invalid_request"]}}}]},"examples":{"invalid_request_finn_id":{"summary":"finn_id is not a UUID","value":{"error":"invalid_request","message":"finn_id must be a valid UUID"}},"invalid_request_to_number":{"summary":"to_number is missing","value":{"error":"invalid_request","message":"to_number is required"}},"invalid_request_country_code":{"summary":"country_code is missing","value":{"error":"invalid_request","message":"country_code is required: the calling code, e.g. \"1\" or \"+91\""}}}}}},"401":{"$ref":"#/components/responses/Unauthorized"},"402":{"description":"The wallet balance is too low to place the call.","content":{"application/json":{"schema":{"allOf":[{"$ref":"#/components/schemas/Error"},{"type":"object","properties":{"error":{"type":"string","enum":["insufficient_credits"]}}}]},"examples":{"insufficient_credits":{"summary":"insufficient_credits","value":{"error":"insufficient_credits","message":"Wallet balance is too low to place this call. Top up in the Finn dashboard (see action_url).","action_url":"https://www.hirefinn.ai/dashboard/settings?tab=billing"}}}}}},"404":{"description":"No Finn with that ID in your organization. The body is the flat `Error` shape; with the organization's `v1_calls_unified_not_found` switch on (off by default) it is the unified `V1Error` shape, `error.code` `finn_not_found`.","content":{"application/json":{"schema":{"oneOf":[{"allOf":[{"$ref":"#/components/schemas/Error"},{"type":"object","properties":{"error":{"type":"string","enum":["finn_not_found"]}}}]},{"$ref":"#/components/schemas/V1Error"}]},"examples":{"finn_not_found":{"summary":"finn_not_found","value":{"error":"finn_not_found","message":"No Finn with that id in this organization"}},"finn_not_found_unified":{"summary":"finn_not_found (v1_calls_unified_not_found on)","value":{"error":{"code":"finn_not_found","message":"No Finn with that id in this organization","fields":[],"request_id":"req_4f6c1d2e9a7b4c3d8e5f6a7b8c9d0e1f","doc_url":"https://api.hirefinn.ai/docs/errors#finn_not_found"}}}}}}},"409":{"description":"Conflict. `idempotency_in_progress`: a request with the same `Idempotency-Key` is still running; retry after `Retry-After` seconds to get its result.","headers":{"Retry-After":{"$ref":"#/components/headers/RetryAfter"}},"content":{"application/json":{"schema":{"$ref":"#/components/schemas/Error"},"example":{"error":"idempotency_in_progress","message":"A request with this Idempotency-Key is still running. Retry shortly to get its result."}}}},"422":{"description":"The request cannot be carried out. `idempotency_key_reused`: this `Idempotency-Key` was already used with a different request.","content":{"application/json":{"schema":{"$ref":"#/components/schemas/Error"},"example":{"error":"idempotency_key_reused","message":"This Idempotency-Key was already used with a different request. Use a new key for a new request."}}}},"423":{"$ref":"#/components/responses/AiAppsPaused"},"429":{"$ref":"#/components/responses/RateLimited"},"500":{"$ref":"#/components/responses/InternalError"},"502":{"description":"The call could not be placed. Try again; contact Finn support if it keeps failing.","content":{"application/json":{"schema":{"allOf":[{"$ref":"#/components/schemas/Error"},{"type":"object","properties":{"error":{"type":"string","enum":["call_failed"]}}}]},"examples":{"call_failed":{"summary":"call_failed","value":{"error":"call_failed","message":"The call could not be placed. Try again, or contact Finn support if it keeps failing."}}}}}}},"x-required-scope":"voice:write","x-legacy-scope":"voice","parameters":[{"$ref":"#/components/parameters/IdempotencyKey"}],"x-mcp-tool":"place_call","x-finn-hold":{"action":"call.create","class":"outward"}},"get":{"tags":["Calls"],"operationId":"listCalls","x-mcp-tool":"list_calls","summary":"List calls","description":"The calls your organization has placed or received, newest first.\n\nThis is how you discover `call_uuid`s. Without it the only ways to learn one are the\nresponse to `POST /calls` and the `call.completed` webhook, so a deployment that placed\na thousand calls could not be read back.\n\nCombine filters freely; they AND together. `deployment_id` that is not yours returns an\nempty page rather than a 404.\n\n**Rate limit:** 300 requests per minute per API key.","parameters":[{"name":"deployment_id","in":"query","description":"Only calls from this deployment.","schema":{"type":"string","format":"uuid"}},{"name":"finn_id","in":"query","description":"Only calls made by this Finn.","schema":{"type":"string","format":"uuid"}},{"name":"status","in":"query","description":"One or more statuses, comma-separated, e.g. `completed,voicemail`.","schema":{"type":"string"},"example":"completed,voicemail"},{"name":"direction","in":"query","schema":{"type":"string","enum":["inbound","outbound"]}},{"name":"from","in":"query","description":"Only calls created at or after this ISO-8601 time.","schema":{"type":"string","format":"date-time"},"example":"2026-10-01T00:00:00Z"},{"name":"to","in":"query","description":"Only calls created at or before this ISO-8601 time.","schema":{"type":"string","format":"date-time"},"example":"2026-10-09T00:00:00Z"},{"name":"limit","in":"query","description":"1-200.","schema":{"type":"integer","minimum":1,"maximum":200,"default":50}},{"name":"offset","in":"query","schema":{"type":"integer","minimum":0,"default":0}}],"responses":{"200":{"description":"The matching calls.","headers":{"RateLimit-Policy":{"$ref":"#/components/headers/RateLimitPolicy"},"RateLimit-Limit":{"$ref":"#/components/headers/RateLimitLimit"},"RateLimit-Remaining":{"$ref":"#/components/headers/RateLimitRemaining"},"RateLimit-Reset":{"$ref":"#/components/headers/RateLimitReset"}},"content":{"application/json":{"schema":{"type":"object","required":["success","data","total","limit","offset"],"properties":{"success":{"type":"boolean","const":true},"data":{"type":"array","items":{"$ref":"#/components/schemas/CallSummary"}},"total":{"type":"integer","description":"Calls matching the filters, before paging."},"limit":{"type":"integer"},"offset":{"type":"integer"}}},"example":{"success":true,"data":[{"call_uuid":"cd175e28-8fd3-40f2-93b2-e2a40a33d11e","finn_id":"8f14e45f-ceea-467a-9f6a-1c0e5b2a77d1","deployment_id":"6b1f0c2e-3d4a-4f5b-9c8d-2e7a1b9f4c03","status":"completed","disconnect_reason":"agent_ended","direction":"outbound","from_number":"+918035737946","to_number":"+919887729247","duration_seconds":95,"user_sentiment":"Positive","call_successful":true,"created_at":"2026-10-08T11:42:48.000Z","updated_at":"2026-10-08T11:44:23.000Z"}],"total":1,"limit":50,"offset":0}}}},"400":{"description":"The request failed validation. `message` names the problem.","content":{"application/json":{"schema":{"$ref":"#/components/schemas/Error"}}}},"401":{"$ref":"#/components/responses/Unauthorized"},"429":{"$ref":"#/components/responses/RateLimited"}},"x-required-scope":"voice:read","x-legacy-scope":"voice"},"parameters":[{"$ref":"#/components/parameters/FinnOrg"},{"$ref":"#/components/parameters/FinnVersion"}]},"/calls/{call_uuid}":{"get":{"tags":["Calls"],"operationId":"retrieveCall","x-mcp-tool":"get_call","summary":"Retrieve a call","description":"Returns any call in your organization: calls placed with `POST /calls`, calls dialed by deployments and inbound calls.\n\nThe record appears shortly after the call ends, so a lookup right after a call can return `404 call_not_found` for a few minutes; retry with backoff or wait for the `call.completed` webhook. `analysis` stays empty until post-call analysis has run.\n\n`billing` is an estimate computed when you read the call (`ceil(duration_seconds / 60)` credits at your current rate). The wallet ledger (`GET /wallet/transactions`) is the record of what was charged.\n\n**Rate limit:** 300 requests per minute per API key, shared with `GET /calls/{call_uuid}`, `GET /finns`, `GET /finns/{finn_id}` and `GET /wallet`.\n\nIncludes the transcript and the analyzer's summary and scores. Both are null until the\nrespective job has run, so a call that has only just finished is still a valid response.","parameters":[{"name":"call_uuid","in":"path","required":true,"description":"The call ID returned by `POST /calls` or delivered in `call.completed`.","schema":{"type":"string","format":"uuid"},"example":"b7e3c9a2-4d1f-4e8b-9a6c-2f5d8e1b3c70"}],"responses":{"200":{"description":"The call.","headers":{"RateLimit-Policy":{"$ref":"#/components/headers/RateLimitPolicy"},"RateLimit-Limit":{"$ref":"#/components/headers/RateLimitLimit"},"RateLimit-Remaining":{"$ref":"#/components/headers/RateLimitRemaining"},"RateLimit-Reset":{"$ref":"#/components/headers/RateLimitReset"}},"content":{"application/json":{"schema":{"type":"object","required":["success","data"],"properties":{"success":{"type":"boolean","const":true},"data":{"$ref":"#/components/schemas/Call"}}},"example":{"success":true,"data":{"call_uuid":"b7e3c9a2-4d1f-4e8b-9a6c-2f5d8e1b3c70","finn_id":"8f14e45f-ceea-467a-9f6a-1c0e5b2a77d1","deployment_id":"0e8a4c71-9b2d-4f36-a5e1-7c3d9b2f6a10","status":"completed","disconnect_reason":"hangup","direction":"outbound","from_number":"+14155550123","to_number":"+14155550142","duration_seconds":97,"user_sentiment":"positive","call_successful":true,"created_at":"2026-10-02T10:42:02.000Z","updated_at":"2026-10-02T10:43:41.000Z","recording_url":"https://storage.example.com/recordings/b7e3c9a2-4d1f-4e8b-9a6c-2f5d8e1b3c70.wav?sig=...","billing":{"credits_charged":2,"price_per_credit":0.1,"currency":"USD","estimated_cost":0.2},"analysis":[{"question_name":"Issue resolved","question_type":"Yes/No","answer":"Yes","needs_review":false,"reasoning":"Caller confirmed the replacement order went through.","extracted_at":"2026-10-02T10:44:10.000Z"}]}}}}},"400":{"description":"The request failed validation. `message` names the problem.","content":{"application/json":{"schema":{"allOf":[{"$ref":"#/components/schemas/Error"},{"type":"object","properties":{"error":{"type":"string","enum":["invalid_request"]}}}]},"examples":{"invalid_request":{"summary":"invalid_request","value":{"error":"invalid_request","message":"call_uuid must be a valid UUID"}}}}}},"401":{"$ref":"#/components/responses/Unauthorized"},"404":{"description":"No call with that ID in your organization, or the record has not been written yet.","content":{"application/json":{"schema":{"allOf":[{"$ref":"#/components/schemas/Error"},{"type":"object","properties":{"error":{"type":"string","enum":["call_not_found"]}}}]},"examples":{"call_not_found":{"summary":"call_not_found","value":{"error":"call_not_found","message":"No call with that id in this organization"}}}}}},"429":{"$ref":"#/components/responses/RateLimited"},"500":{"$ref":"#/components/responses/InternalError"}},"x-required-scope":"voice:read","x-legacy-scope":"voice"},"parameters":[{"$ref":"#/components/parameters/FinnOrg"},{"$ref":"#/components/parameters/FinnVersion"}]},"/finns":{"get":{"tags":["Finns"],"operationId":"listFinns","summary":"List Finns","description":"Returns the Finns in your organization, newest first, as full Finn objects. There is no total count: request the next page with a larger `offset` until a page has fewer than `limit` items.\n\n**Rate limit:** 300 requests per minute per API key, shared with `GET /calls/{call_uuid}`, `GET /finns`, `GET /finns/{finn_id}` and `GET /wallet`.","parameters":[{"name":"limit","in":"query","description":"Page size, 1 to 200. Out-of-range values are clamped rather than rejected.","schema":{"type":"integer","minimum":1,"maximum":200,"default":50}},{"name":"offset","in":"query","description":"Number of items to skip.","schema":{"type":"integer","minimum":0,"default":0}},{"name":"scope","in":"query","required":false,"description":"For a member's OAuth token or MCP key: mine (default) lists the Finns you created; org lists every Finn in the organization. An org API key always lists the whole organization.","schema":{"type":"string","enum":["mine","org"],"default":"mine"}}],"responses":{"200":{"description":"A page of Finns.","headers":{"RateLimit-Policy":{"$ref":"#/components/headers/RateLimitPolicy"},"RateLimit-Limit":{"$ref":"#/components/headers/RateLimitLimit"},"RateLimit-Remaining":{"$ref":"#/components/headers/RateLimitRemaining"},"RateLimit-Reset":{"$ref":"#/components/headers/RateLimitReset"}},"content":{"application/json":{"schema":{"type":"object","required":["success","data","limit","offset"],"properties":{"success":{"type":"boolean","const":true},"data":{"type":"array","items":{"$ref":"#/components/schemas/Finn"}},"limit":{"type":"integer","description":"The page size that was applied."},"offset":{"type":"integer","description":"The offset that was applied."}}},"example":{"success":true,"data":[{"id":"8f14e45f-ceea-467a-9f6a-1c0e5b2a77d1","name":"Renewal reminders","status":"draft","voice":"f786b574-daa5-4673-aa0c-cbe3e8534c02","language":"English","language_group":"global","time_zone":"America/New_York","use_case":"Remind customers about their plan renewal","identity_text":"You are Maya, calling from Acme about the customer's plan renewal.","style_guardrails":null,"response_guidelines":null,"begin_message":"Hi {{ name }}, this is Maya from Acme. Do you have a minute to talk about your renewal?","system_prompt":"Confirm the customer still wants the plan. If they do, confirm the renewal date. If they want to cancel, offer a transfer to the account team.","max_call_duration_minutes":5,"idle_reminder_message":null,"idle_reminder_after_seconds":null,"end_call_on_silence_seconds":30,"handoff_number":{"country_code":"+1","phone_number":"4155550100"},"post_call_analysis":[{"id":1759742062000,"name":"Wants to renew","type":"Yes/No","description":"Did the customer confirm the renewal?"}],"call_flow":null,"created_at":"2026-09-30T09:14:22.000Z","updated_at":"2026-09-30T09:14:22.000Z"}],"limit":20,"offset":0}}}},"401":{"$ref":"#/components/responses/Unauthorized"},"429":{"$ref":"#/components/responses/RateLimited"},"500":{"$ref":"#/components/responses/InternalError"}},"x-required-scope":"voice:read","x-legacy-scope":"voice","x-mcp-tool":"list_finns"},"post":{"tags":["Finns"],"operationId":"createFinn","summary":"Create a Finn","description":"Creates a Finn with status `draft`. It does not place or answer calls until you deploy it (or place a single call with it). Only `name` and `voice` are required.\n\n`voice` must be a voice your organization can use, from `GET /voices`; the Finn is put on that voice's provider. Speech-to-text and the language model are set by Finn and are not part of the API. Set the call flow, including the built-in end call, call transfer and Cal.com tools, with `call_flow`. Add knowledge base documents with `POST /finns/{finn_id}/knowledge_base`. Variables are configured in the dashboard.\n\n`max_call_duration_minutes`, `idle_reminder_message`, `idle_reminder_after_seconds` and `end_call_on_silence_seconds` apply to calls from deployments only; calls placed with `POST /calls` run with Finn's defaults for them.\n\nThe Finn is attributed to the user who created the API key. A retried create makes a second Finn.\n\n**Rate limit:** 60 requests per minute per API key, shared by `POST`, `PATCH` and `DELETE` on Finns.","requestBody":{"required":true,"content":{"application/json":{"schema":{"$ref":"#/components/schemas/CreateFinnRequest"},"example":{"name":"Renewal reminders","voice":"f786b574-daa5-4673-aa0c-cbe3e8534c02","language":"English","time_zone":"Asia/Kolkata","use_case":"Remind customers about their plan renewal","identity_text":"You are Maya, calling from Acme about the customer's plan renewal.","style_guardrails":"Warm and brief. Never pushy. One question at a time.","response_guidelines":"Keep replies under two sentences. Never promise a discount.","begin_message":"Hi {{ name }}, this is Maya from Acme. Do you have a minute to talk about your renewal?","system_prompt":"Confirm the customer still wants the plan. If they do, confirm the renewal date. If they want to cancel, offer a transfer to the account team.","max_call_duration_minutes":5,"idle_reminder_message":"Take your time. I just need the renewal date.","idle_reminder_after_seconds":10,"end_call_on_silence_seconds":30,"handoff_number":{"country_code":"+91","phone_number":"9887729247"},"post_call_analysis":[{"name":"Wants to renew","type":"Yes/No","description":"Did the customer confirm they want to renew the plan?"},{"name":"Reason for declining","type":"Selector","description":"If they declined, which reason did they give?","choices":["Too expensive","Switching provider","No longer needed","Wants to think about it"]},{"name":"Renewal date","type":"Text","description":"The date the customer agreed to renew, exactly as they said it.","formatExamples":["12 March","next Tuesday","end of the month"]},{"name":"Seats requested","type":"Number","description":"How many seats did the customer ask for? Leave empty if not discussed.","formatExamples":["5","12"]}]}}}},"responses":{"201":{"description":"The created Finn.","headers":{"RateLimit-Policy":{"$ref":"#/components/headers/RateLimitPolicy"},"RateLimit-Limit":{"$ref":"#/components/headers/RateLimitLimit"},"RateLimit-Remaining":{"$ref":"#/components/headers/RateLimitRemaining"},"RateLimit-Reset":{"$ref":"#/components/headers/RateLimitReset"}},"content":{"application/json":{"schema":{"type":"object","required":["success","data"],"properties":{"success":{"type":"boolean","const":true},"data":{"$ref":"#/components/schemas/Finn"}}},"example":{"success":true,"data":{"id":"8f14e45f-ceea-467a-9f6a-1c0e5b2a77d1","name":"Renewal reminders","status":"draft","voice":"f786b574-daa5-4673-aa0c-cbe3e8534c02","language":"English","language_group":"global","time_zone":"America/New_York","use_case":"Remind customers about their plan renewal","identity_text":"You are Maya, calling from Acme about the customer's plan renewal.","style_guardrails":null,"response_guidelines":null,"begin_message":"Hi {{ name }}, this is Maya from Acme. Do you have a minute to talk about your renewal?","system_prompt":"Confirm the customer still wants the plan. If they do, confirm the renewal date. If they want to cancel, offer a transfer to the account team.","max_call_duration_minutes":5,"idle_reminder_message":null,"idle_reminder_after_seconds":null,"end_call_on_silence_seconds":30,"handoff_number":{"country_code":"+1","phone_number":"4155550100"},"post_call_analysis":[{"id":1759742062000,"name":"Wants to renew","type":"Yes/No","description":"Did the customer confirm the renewal?"}],"call_flow":null,"created_at":"2026-09-30T09:14:22.000Z","updated_at":"2026-09-30T09:14:22.000Z"}}}}},"400":{"description":"The body is not a JSON object, has an unknown field, is missing `name` or `voice`, a value breaks a rule, or `voice` is not a voice your organization can use.","content":{"application/json":{"schema":{"allOf":[{"$ref":"#/components/schemas/Error"},{"type":"object","properties":{"error":{"type":"string","enum":["invalid_request"]}}}]},"examples":{"invalid_request_missing":{"summary":"Missing required field","value":{"error":"invalid_request","message":"Missing required field(s): voice"}},"invalid_request_unknown":{"summary":"Unknown field","value":{"error":"invalid_request","message":"Unknown field(s): tts_service"}},"invalid_request_voice":{"summary":"Voice not available","value":{"error":"invalid_request","message":"voice is not one of the voices from GET /voices"}},"invalid_request_setting":{"summary":"Call setting out of range","value":{"error":"invalid_request","message":"max_call_duration_minutes must be one of 1, 3, 5, 10, 15, 30, 60 or null"}}}}}},"401":{"$ref":"#/components/responses/Unauthorized"},"403":{"description":"Your plan includes no voice provider.","content":{"application/json":{"schema":{"allOf":[{"$ref":"#/components/schemas/Error"},{"type":"object","properties":{"error":{"type":"string","enum":["feature_not_allowed"]}}}]},"examples":{"feature_not_allowed":{"summary":"feature_not_allowed","value":{"error":"feature_not_allowed","message":"Your plan does not include any voice provider. Upgrade in the Finn dashboard (see action_url).","action_url":"https://www.hirefinn.ai/dashboard/settings?tab=billing"}}}}}},"409":{"description":"The user who created the API key has been deleted or is no longer a member of your organization.","content":{"application/json":{"schema":{"allOf":[{"$ref":"#/components/schemas/Error"},{"type":"object","properties":{"error":{"type":"string","enum":["api_key_owner_required"]}}}]},"examples":{"api_key_owner_required":{"summary":"api_key_owner_required","value":{"error":"api_key_owner_required","message":"The user who created this API key no longer exists. Create a new key from an active account to create Finns.","action_url":"https://www.hirefinn.ai/dashboard/settings?tab=integrations"}}}}}},"423":{"$ref":"#/components/responses/AiAppsPaused"},"429":{"$ref":"#/components/responses/RateLimited"},"500":{"$ref":"#/components/responses/InternalError"},"502":{"description":"The voice couldn't be checked because the voice catalog couldn't be loaded. Nothing was saved; retry later.","content":{"application/json":{"schema":{"allOf":[{"$ref":"#/components/schemas/Error"},{"type":"object","properties":{"error":{"type":"string","enum":["voices_unavailable"]}}}]},"examples":{"voices_unavailable":{"summary":"voices_unavailable","value":{"error":"voices_unavailable","message":"Could not load voices"}}}}}}},"x-required-scope":"voice:write","x-legacy-scope":"voice"},"parameters":[{"$ref":"#/components/parameters/FinnOrg"},{"$ref":"#/components/parameters/FinnVersion"}]},"/finns/{finn_id}":{"get":{"tags":["Finns"],"operationId":"retrieveFinn","summary":"Retrieve a Finn","description":"Returns one Finn as a full Finn object.\n\n**Rate limit:** 300 requests per minute per API key, shared with `GET /calls/{call_uuid}`, `GET /finns`, `GET /finns/{finn_id}` and `GET /wallet`.","parameters":[{"name":"finn_id","in":"path","required":true,"description":"The Finn ID.","schema":{"type":"string","format":"uuid"},"example":"8f14e45f-ceea-467a-9f6a-1c0e5b2a77d1"}],"responses":{"200":{"description":"The Finn.","headers":{"RateLimit-Policy":{"$ref":"#/components/headers/RateLimitPolicy"},"RateLimit-Limit":{"$ref":"#/components/headers/RateLimitLimit"},"RateLimit-Remaining":{"$ref":"#/components/headers/RateLimitRemaining"},"RateLimit-Reset":{"$ref":"#/components/headers/RateLimitReset"}},"content":{"application/json":{"schema":{"type":"object","required":["success","data"],"properties":{"success":{"type":"boolean","const":true},"data":{"$ref":"#/components/schemas/Finn"}}},"example":{"success":true,"data":{"id":"8f14e45f-ceea-467a-9f6a-1c0e5b2a77d1","name":"Renewal reminders","status":"draft","voice":"f786b574-daa5-4673-aa0c-cbe3e8534c02","language":"English","language_group":"global","time_zone":"America/New_York","use_case":"Remind customers about their plan renewal","identity_text":"You are Maya, calling from Acme about the customer's plan renewal.","style_guardrails":null,"response_guidelines":null,"begin_message":"Hi {{ name }}, this is Maya from Acme. Do you have a minute to talk about your renewal?","system_prompt":"Confirm the customer still wants the plan. If they do, confirm the renewal date. If they want to cancel, offer a transfer to the account team.","max_call_duration_minutes":5,"idle_reminder_message":null,"idle_reminder_after_seconds":null,"end_call_on_silence_seconds":30,"handoff_number":{"country_code":"+1","phone_number":"4155550100"},"post_call_analysis":[{"id":1759742062000,"name":"Wants to renew","type":"Yes/No","description":"Did the customer confirm the renewal?"}],"call_flow":null,"created_at":"2026-09-30T09:14:22.000Z","updated_at":"2026-09-30T09:14:22.000Z"}}}}},"400":{"description":"The request failed validation. `message` names the problem.","content":{"application/json":{"schema":{"allOf":[{"$ref":"#/components/schemas/Error"},{"type":"object","properties":{"error":{"type":"string","enum":["invalid_request"]}}}]},"examples":{"invalid_request":{"summary":"invalid_request","value":{"error":"invalid_request","message":"finn_id must be a valid UUID"}}}}}},"401":{"$ref":"#/components/responses/Unauthorized"},"404":{"description":"No Finn with that ID in your organization.","content":{"application/json":{"schema":{"allOf":[{"$ref":"#/components/schemas/Error"},{"type":"object","properties":{"error":{"type":"string","enum":["finn_not_found"]}}}]},"examples":{"finn_not_found":{"summary":"finn_not_found","value":{"error":"finn_not_found","message":"No Finn with that id in this organization"}}}}}},"429":{"$ref":"#/components/responses/RateLimited"},"500":{"$ref":"#/components/responses/InternalError"}},"x-required-scope":"voice:read","x-legacy-scope":"voice","x-mcp-tool":"get_finn"},"patch":{"tags":["Finns"],"operationId":"updateFinn","summary":"Update a Finn","description":"Updates only the fields you send; at least one is required. Fields you don't send keep their values, including dashboard settings the API doesn't expose. Send `null` to clear a field (except `name` and `voice`).\n\nSending `voice` checks it against the voices your organization can use and moves the Finn onto that voice's provider, as picking a voice in the dashboard does.\n\nLive Finns can be edited: the change applies to the next call placed with the Finn, including the next calls a running deployment dials. Calls in progress are not affected.\n\n**Rate limit:** 60 requests per minute per API key, shared by `POST`, `PATCH` and `DELETE` on Finns.","parameters":[{"name":"finn_id","in":"path","required":true,"description":"The Finn ID.","schema":{"type":"string","format":"uuid"},"example":"8f14e45f-ceea-467a-9f6a-1c0e5b2a77d1"}],"requestBody":{"required":true,"content":{"application/json":{"schema":{"$ref":"#/components/schemas/UpdateFinnRequest"},"example":{"name":"Renewal reminders (Hindi)","voice":"95d51f79-c397-46f9-b49a-23763d3eaa2d","language":"Hindi","time_zone":"Asia/Kolkata","use_case":"Remind customers about their plan renewal","identity_text":"You are Arushi, calling from Acme about the renewal.","style_guardrails":"Warm and brief. Never pushy.","response_guidelines":"Keep replies under two sentences.","begin_message":"Namaste {{ name }}, main Arushi bol rahi hoon Acme se.","system_prompt":"Confirm the renewal date, then thank them and end the call.","max_call_duration_minutes":10,"idle_reminder_message":"Aap sun rahe hain?","idle_reminder_after_seconds":8,"end_call_on_silence_seconds":30,"handoff_number":{"country_code":"+91","phone_number":"9887729247"},"post_call_analysis":[{"name":"Wants to renew","type":"Yes/No","description":"Did the customer confirm they want to renew the plan?"},{"name":"Reason for declining","type":"Selector","description":"If they declined, which reason did they give?","choices":["Too expensive","Switching provider","No longer needed","Wants to think about it"]},{"name":"Renewal date","type":"Text","description":"The date the customer agreed to renew, exactly as they said it.","formatExamples":["12 March","next Tuesday","end of the month"]},{"name":"Seats requested","type":"Number","description":"How many seats did the customer ask for? Leave empty if not discussed.","formatExamples":["5","12"]}]}}}},"responses":{"200":{"description":"The updated Finn.","headers":{"RateLimit-Policy":{"$ref":"#/components/headers/RateLimitPolicy"},"RateLimit-Limit":{"$ref":"#/components/headers/RateLimitLimit"},"RateLimit-Remaining":{"$ref":"#/components/headers/RateLimitRemaining"},"RateLimit-Reset":{"$ref":"#/components/headers/RateLimitReset"}},"content":{"application/json":{"schema":{"type":"object","required":["success","data"],"properties":{"success":{"type":"boolean","const":true},"data":{"$ref":"#/components/schemas/Finn"}}},"example":{"success":true,"data":{"id":"8f14e45f-ceea-467a-9f6a-1c0e5b2a77d1","name":"Renewal reminders","status":"draft","voice":"a0e99841-438c-4a64-b679-ae501e7d6091","language":"English","language_group":"global","time_zone":"America/New_York","use_case":"Remind customers about their plan renewal","identity_text":"You are Maya, calling from Acme about the customer's plan renewal.","style_guardrails":null,"response_guidelines":null,"begin_message":"Hi {{ name }}, this is Maya from Acme. Do you have a minute to talk about your renewal?","system_prompt":"Confirm the customer still wants the plan. If they do, confirm the renewal date. If they want to cancel, offer a transfer to the account team.","max_call_duration_minutes":null,"idle_reminder_message":"Take your time. I just need the renewal date.","idle_reminder_after_seconds":8,"end_call_on_silence_seconds":30,"handoff_number":{"country_code":"+1","phone_number":"4155550100"},"post_call_analysis":[{"id":1759742062000,"name":"Wants to renew","type":"Yes/No","description":"Did the customer confirm the renewal?"}],"call_flow":null,"created_at":"2026-09-30T09:14:22.000Z","updated_at":"2026-10-01T15:02:47.000Z"}}}}},"400":{"description":"`finn_id` is not a UUID, the body is empty or not a JSON object, has an unknown field, a value breaks a rule, or `voice` is not a voice your organization can use.","content":{"application/json":{"schema":{"allOf":[{"$ref":"#/components/schemas/Error"},{"type":"object","properties":{"error":{"type":"string","enum":["invalid_request"]}}}]},"examples":{"invalid_request_empty":{"summary":"Empty update","value":{"error":"invalid_request","message":"Provide at least one field to update"}},"invalid_request_null":{"summary":"Field cannot be cleared","value":{"error":"invalid_request","message":"name cannot be null"}},"invalid_request_voice":{"summary":"Voice not available","value":{"error":"invalid_request","message":"voice is not one of the voices from GET /voices"}},"invalid_request_setting":{"summary":"Call setting out of range","value":{"error":"invalid_request","message":"max_call_duration_minutes must be one of 1, 3, 5, 10, 15, 30, 60 or null"}}}}}},"401":{"$ref":"#/components/responses/Unauthorized"},"403":{"description":"`voice` was sent and your plan includes no voice provider.","content":{"application/json":{"schema":{"allOf":[{"$ref":"#/components/schemas/Error"},{"type":"object","properties":{"error":{"type":"string","enum":["feature_not_allowed"]}}}]},"examples":{"feature_not_allowed":{"summary":"feature_not_allowed","value":{"error":"feature_not_allowed","message":"Your plan does not include any voice provider. Upgrade in the Finn dashboard (see action_url).","action_url":"https://www.hirefinn.ai/dashboard/settings?tab=billing"}}}}}},"404":{"description":"No Finn with that ID in your organization.","content":{"application/json":{"schema":{"allOf":[{"$ref":"#/components/schemas/Error"},{"type":"object","properties":{"error":{"type":"string","enum":["finn_not_found"]}}}]},"examples":{"finn_not_found":{"summary":"finn_not_found","value":{"error":"finn_not_found","message":"No Finn with that id in this organization"}}}}}},"423":{"$ref":"#/components/responses/AiAppsPaused"},"429":{"$ref":"#/components/responses/RateLimited"},"500":{"$ref":"#/components/responses/InternalError"},"502":{"description":"`voice` was sent and couldn't be checked because the voice catalog couldn't be loaded. Nothing was saved; retry later.","content":{"application/json":{"schema":{"allOf":[{"$ref":"#/components/schemas/Error"},{"type":"object","properties":{"error":{"type":"string","enum":["voices_unavailable"]}}}]},"examples":{"voices_unavailable":{"summary":"voices_unavailable","value":{"error":"voices_unavailable","message":"Could not load voices"}}}}}}},"x-required-scope":"voice:write","x-legacy-scope":"voice"},"delete":{"tags":["Finns"],"operationId":"deleteFinn","summary":"Delete a Finn","description":"Permanently deletes a Finn. Allowed only when the Finn is a `draft`, has never had a deployment (stopped deployments count) and has no phone number assigned. In practice, a Finn that has been deployed cannot be deleted.\n\n**Rate limit:** 60 requests per minute per API key, shared by `POST`, `PATCH` and `DELETE` on Finns.","parameters":[{"name":"finn_id","in":"path","required":true,"description":"The Finn ID.","schema":{"type":"string","format":"uuid"},"example":"8f14e45f-ceea-467a-9f6a-1c0e5b2a77d1"}],"responses":{"200":{"description":"The Finn was deleted.","headers":{"RateLimit-Policy":{"$ref":"#/components/headers/RateLimitPolicy"},"RateLimit-Limit":{"$ref":"#/components/headers/RateLimitLimit"},"RateLimit-Remaining":{"$ref":"#/components/headers/RateLimitRemaining"},"RateLimit-Reset":{"$ref":"#/components/headers/RateLimitReset"}},"content":{"application/json":{"schema":{"type":"object","required":["success","data"],"properties":{"success":{"type":"boolean","const":true},"data":{"$ref":"#/components/schemas/DeletedFinn"}}},"example":{"success":true,"data":{"id":"8f14e45f-ceea-467a-9f6a-1c0e5b2a77d1","deleted":true}}}}},"400":{"description":"The request failed validation. `message` names the problem.","content":{"application/json":{"schema":{"allOf":[{"$ref":"#/components/schemas/Error"},{"type":"object","properties":{"error":{"type":"string","enum":["invalid_request"]}}}]},"examples":{"invalid_request":{"summary":"invalid_request","value":{"error":"invalid_request","message":"finn_id must be a valid UUID"}}}}}},"401":{"$ref":"#/components/responses/Unauthorized"},"404":{"description":"No Finn with that ID in your organization.","content":{"application/json":{"schema":{"allOf":[{"$ref":"#/components/schemas/Error"},{"type":"object","properties":{"error":{"type":"string","enum":["finn_not_found"]}}}]},"examples":{"finn_not_found":{"summary":"finn_not_found","value":{"error":"finn_not_found","message":"No Finn with that id in this organization"}}}}}},"409":{"description":"The Finn is not a draft, or it has deployments or an assigned number.","content":{"application/json":{"schema":{"allOf":[{"$ref":"#/components/schemas/Error"},{"type":"object","properties":{"error":{"type":"string","enum":["finn_not_draft","finn_in_use"]}}}]},"examples":{"finn_not_draft":{"summary":"finn_not_draft","value":{"error":"finn_not_draft","message":"Only draft Finns can be deleted"}},"finn_in_use":{"summary":"finn_in_use","value":{"error":"finn_in_use","message":"This Finn is referenced by deployments and cannot be deleted."}}}}}},"423":{"$ref":"#/components/responses/AiAppsPaused"},"429":{"$ref":"#/components/responses/RateLimited"},"500":{"$ref":"#/components/responses/InternalError"}},"x-required-scope":"voice:write","x-legacy-scope":"voice"},"parameters":[{"$ref":"#/components/parameters/FinnOrg"},{"$ref":"#/components/parameters/FinnVersion"}]},"/finns/{finn_id}/knowledge_base":{"parameters":[{"name":"finn_id","in":"path","required":true,"schema":{"type":"string","format":"uuid"}},{"$ref":"#/components/parameters/FinnOrg"},{"$ref":"#/components/parameters/FinnVersion"}],"get":{"tags":["Finns"],"operationId":"listKnowledgeBase","summary":"List a Finn's knowledge base documents","description":"Documents the Finn searches during calls, newest first. Documents can't be deleted through the API.\n\n**Rate limit:** 300 requests per minute per API key, shared with `GET /finns`.","responses":{"200":{"description":"The documents.","headers":{"RateLimit-Policy":{"$ref":"#/components/headers/RateLimitPolicy"},"RateLimit-Limit":{"$ref":"#/components/headers/RateLimitLimit"},"RateLimit-Remaining":{"$ref":"#/components/headers/RateLimitRemaining"},"RateLimit-Reset":{"$ref":"#/components/headers/RateLimitReset"}},"content":{"application/json":{"schema":{"type":"object","required":["success","data"],"properties":{"success":{"type":"boolean","const":true},"data":{"type":"array","items":{"$ref":"#/components/schemas/KnowledgeBaseDocument"}}}},"example":{"success":true,"data":[{"id":"0b6f4c1e-3a8d-4f5e-9c2b-7d1a6e4f8b30","name":"Refund policy.docx","type":"docx","created_at":"2026-10-17T09:14:22.000Z"}]}}}},"400":{"description":"`finn_id` is not a valid UUID.","content":{"application/json":{"schema":{"allOf":[{"$ref":"#/components/schemas/Error"},{"type":"object","properties":{"error":{"type":"string","enum":["invalid_request"]}}}]}}}},"401":{"$ref":"#/components/responses/Unauthorized"},"404":{"description":"No Finn with that ID in your organization.","content":{"application/json":{"schema":{"allOf":[{"$ref":"#/components/schemas/Error"},{"type":"object","properties":{"error":{"type":"string","enum":["finn_not_found"]}}}]},"examples":{"finn_not_found":{"summary":"finn_not_found","value":{"error":"finn_not_found","message":"No Finn with that id in this organization"}}}}}},"429":{"$ref":"#/components/responses/RateLimited"},"500":{"$ref":"#/components/responses/InternalError"}},"x-required-scope":"voice:read","x-legacy-scope":"voice"},"post":{"tags":["Finns"],"operationId":"addKnowledgeBaseDocument","summary":"Add a knowledge base document","description":"Adds one document to the Finn's knowledge base, through the same upload as the dashboard. Send exactly one source: JSON `{ \"text\", \"name\"? }` (stored as DOCX), JSON `{ \"url\" }` (one public page, stored as PDF; can take up to a minute), or multipart `file` (PDF, DOCX or TXT, up to 20 MB) with optional `name`.\n\n**Rate limit:** 10 requests per minute per API key.","requestBody":{"required":true,"content":{"application/json":{"schema":{"oneOf":[{"type":"object","required":["text"],"additionalProperties":false,"properties":{"text":{"type":"string","minLength":1,"maxLength":200000},"name":{"type":"string","minLength":1,"maxLength":200}}},{"type":"object","required":["url"],"additionalProperties":false,"properties":{"url":{"type":"string","format":"uri","maxLength":2048,"description":"A public http(s) URL."}}}]},"examples":{"text":{"value":{"name":"Refund policy","text":"Refunds are issued within 14 days of purchase."}},"url":{"value":{"url":"https://example.com/pricing"}}}},"multipart/form-data":{"schema":{"type":"object","required":["file"],"properties":{"file":{"type":"string","format":"binary","description":"PDF, DOCX or TXT, up to 20 MB (50 MB where the organization has streamed uploads on)."},"name":{"type":"string","minLength":1,"maxLength":200}}}}}},"responses":{"201":{"description":"The document was added.","headers":{"RateLimit-Policy":{"$ref":"#/components/headers/RateLimitPolicy"},"RateLimit-Limit":{"$ref":"#/components/headers/RateLimitLimit"},"RateLimit-Remaining":{"$ref":"#/components/headers/RateLimitRemaining"},"RateLimit-Reset":{"$ref":"#/components/headers/RateLimitReset"}},"content":{"application/json":{"schema":{"type":"object","required":["success","data"],"properties":{"success":{"type":"boolean","const":true},"data":{"$ref":"#/components/schemas/KnowledgeBaseDocument"}}},"example":{"success":true,"data":{"id":"0b6f4c1e-3a8d-4f5e-9c2b-7d1a6e4f8b30","name":"Refund policy.docx","type":"docx","created_at":"2026-10-17T09:14:22.000Z"}}}}},"400":{"description":"`finn_id` is not a valid UUID, not exactly one source was sent, a field breaks a rule, the URL is not public, or the file type is not supported.","content":{"application/json":{"schema":{"allOf":[{"$ref":"#/components/schemas/Error"},{"type":"object","properties":{"error":{"type":"string","enum":["invalid_request"]}}}]}}}},"401":{"$ref":"#/components/responses/Unauthorized"},"404":{"description":"No Finn with that ID in your organization.","content":{"application/json":{"schema":{"allOf":[{"$ref":"#/components/schemas/Error"},{"type":"object","properties":{"error":{"type":"string","enum":["finn_not_found"]}}}]},"examples":{"finn_not_found":{"summary":"finn_not_found","value":{"error":"finn_not_found","message":"No Finn with that id in this organization"}}}}}},"423":{"$ref":"#/components/responses/AiAppsPaused"},"429":{"$ref":"#/components/responses/RateLimited"},"500":{"$ref":"#/components/responses/InternalError"},"502":{"description":"The page could not be loaded (`website_import_failed`) or the document could not be processed (`knowledge_base_upload_failed`). Nothing was saved.","content":{"application/json":{"schema":{"allOf":[{"$ref":"#/components/schemas/Error"},{"type":"object","properties":{"error":{"type":"string","enum":["website_import_failed","knowledge_base_upload_failed"]}}}]}}}},"503":{"description":"Adding documents is temporarily disabled.","content":{"application/json":{"schema":{"allOf":[{"$ref":"#/components/schemas/Error"},{"type":"object","properties":{"error":{"type":"string","enum":["knowledge_base_unavailable"]}}}]}}}}},"x-required-scope":"voice:write","x-legacy-scope":"voice"}},"/voices":{"get":{"tags":["Voices"],"operationId":"listVoices","summary":"List voices","description":"Lists the voices your organization can give a Finn.\n\nPass a `voice_id` from here as `voice` when you create or update a Finn. **That is the only voice field there is** — the TTS provider is not part of the API and must not be sent. It is derived from the voice ID, whose format is unambiguous:\n\n| Provider | `voice_id` format | Example |\n|---|---|---|\n| Cartesia (default) | UUID, hyphenated | `f786b574-daa5-4673-aa0c-cbe3e8534c02` |\n| ElevenLabs | alphanumeric, no hyphen | `FGY2WhTYpPnrIDTdsKH5` |\n\nCartesia is the platform default and is listed first, so it is what you get unless you deliberately pick an ElevenLabs voice.\n\nNarrow the list with `language` (a name such as `Hindi` or a code such as `hi`; region subtags match, so `en` also matches `en-IN`), `search`, and `limit`/`offset`. `total` is the count after filtering, before paging.\n\nIf one provider's catalog can't be loaded, the response still succeeds with the other provider's voices. If neither can be loaded, it returns `502 voices_unavailable`.\n\n**Rate limit:** 60 requests per minute per API key, shared by `GET /voices` and `GET /voices/{voice_id}`.","parameters":[{"name":"language","in":"query","description":"Only voices that speak this language. Accepts a name (`Hindi`, `English`) or a code (`hi`, `en`, `en-IN`). Region subtags match in both directions, so `en` matches `en-IN` and `en-IN` matches `en`. `Hinglish` matches Hindi and Indian-English voices, since Hinglish is code-switched.","schema":{"type":"string","maxLength":50},"example":"Hindi"},{"name":"search","in":"query","description":"Filters by name, voice ID or description. Up to 100 characters.","schema":{"type":"string","maxLength":100}},{"name":"limit","in":"query","description":"How many voices to return, 1-500.","schema":{"type":"integer","minimum":1,"maximum":500,"default":200}},{"name":"offset","in":"query","description":"How many voices to skip.","schema":{"type":"integer","minimum":0,"default":0}}],"responses":{"200":{"description":"The matching voices.","headers":{"RateLimit-Policy":{"$ref":"#/components/headers/RateLimitPolicy"},"RateLimit-Limit":{"$ref":"#/components/headers/RateLimitLimit"},"RateLimit-Remaining":{"$ref":"#/components/headers/RateLimitRemaining"},"RateLimit-Reset":{"$ref":"#/components/headers/RateLimitReset"}},"content":{"application/json":{"schema":{"type":"object","required":["success","data","total","limit","offset"],"properties":{"success":{"type":"boolean","const":true},"data":{"type":"array","items":{"$ref":"#/components/schemas/Voice"}},"total":{"type":"integer","description":"Voices matching the filters, before `limit`/`offset`."},"limit":{"type":"integer"},"offset":{"type":"integer"}}},"example":{"success":true,"data":[{"voice_id":"95d51f79-c397-46f9-b49a-23763d3eaa2d","name":"Arushi","description":"Warm Indian female voice, natural in Hindi and Hinglish.","preview_url":"https://example.com/voices/arushi-preview.mp3","category":"premade","labels":{"accent":"indian","gender":"female","age":"young"},"languages":["hi","en-IN"]},{"voice_id":"f786b574-daa5-4673-aa0c-cbe3e8534c02","name":"Katie","description":"Clear English female voice.","preview_url":"https://example.com/voices/katie-preview.mp3","category":"premade","labels":{"accent":"american","gender":"female","age":"young"},"languages":["en"]}],"total":2,"limit":200,"offset":0}}}},"400":{"description":"The request failed validation. `message` names the problem.","content":{"application/json":{"schema":{"allOf":[{"$ref":"#/components/schemas/Error"},{"type":"object","properties":{"error":{"type":"string","enum":["invalid_request"]}}}]},"examples":{"invalid_request":{"summary":"invalid_request","value":{"error":"invalid_request","message":"search must be at most 100 characters"}}}}}},"401":{"$ref":"#/components/responses/Unauthorized"},"403":{"description":"Your plan includes no voice provider.","content":{"application/json":{"schema":{"allOf":[{"$ref":"#/components/schemas/Error"},{"type":"object","properties":{"error":{"type":"string","enum":["feature_not_allowed"]}}}]},"examples":{"feature_not_allowed":{"summary":"feature_not_allowed","value":{"error":"feature_not_allowed","message":"Your plan does not include any voice provider. Upgrade in the Finn dashboard (see action_url).","action_url":"https://www.hirefinn.ai/dashboard/settings?tab=billing"}}}}}},"429":{"$ref":"#/components/responses/RateLimited"},"500":{"$ref":"#/components/responses/InternalError"},"502":{"description":"No voice catalog could be loaded; retry later. Also returned when your organization is limited to a voice provider whose voices can't be listed through the API; retrying does not help then.","content":{"application/json":{"schema":{"allOf":[{"$ref":"#/components/schemas/Error"},{"type":"object","properties":{"error":{"type":"string","enum":["voices_unavailable"]}}}]},"examples":{"voices_unavailable":{"summary":"voices_unavailable","value":{"error":"voices_unavailable","message":"Could not load voices"}}}}}}},"x-required-scope":"voice:read","x-legacy-scope":"voice","x-mcp-tool":"list_voices"},"parameters":[{"$ref":"#/components/parameters/FinnOrg"},{"$ref":"#/components/parameters/FinnVersion"}]},"/voices/{voice_id}":{"get":{"tags":["Voices"],"operationId":"retrieveVoice","summary":"Retrieve a voice","description":"Returns one voice, from whichever of your organization's voice providers has it. Some providers have more voices than their list shows, so this can find a voice `GET /voices` didn't return. `POST /finns` and `PATCH /finns/{finn_id}` check `voice` the same way.\n\n**Rate limit:** 60 requests per minute per API key, shared by `GET /voices` and `GET /voices/{voice_id}`.","parameters":[{"name":"voice_id","in":"path","required":true,"description":"The voice ID: 1 to 128 letters, digits, `_`, `:`, `.` or `-`.","schema":{"type":"string","pattern":"^[A-Za-z0-9_:.-]{1,128}$"},"example":"f786b574-daa5-4673-aa0c-cbe3e8534c02"}],"responses":{"200":{"description":"The voice.","headers":{"RateLimit-Policy":{"$ref":"#/components/headers/RateLimitPolicy"},"RateLimit-Limit":{"$ref":"#/components/headers/RateLimitLimit"},"RateLimit-Remaining":{"$ref":"#/components/headers/RateLimitRemaining"},"RateLimit-Reset":{"$ref":"#/components/headers/RateLimitReset"}},"content":{"application/json":{"schema":{"type":"object","required":["success","data"],"properties":{"success":{"type":"boolean","const":true},"data":{"$ref":"#/components/schemas/Voice"}}},"example":{"success":true,"data":{"voice_id":"f786b574-daa5-4673-aa0c-cbe3e8534c02","name":"Rachel","description":"Calm, young American female voice.","preview_url":"https://example.com/voices/rachel-preview.mp3","category":"premade","labels":{"accent":"american","gender":"female","age":"young"},"languages":["en"]}}}}},"400":{"description":"The request failed validation. `message` names the problem.","content":{"application/json":{"schema":{"allOf":[{"$ref":"#/components/schemas/Error"},{"type":"object","properties":{"error":{"type":"string","enum":["invalid_request"]}}}]},"examples":{"invalid_request":{"summary":"invalid_request","value":{"error":"invalid_request","message":"voice_id is not valid"}}}}}},"401":{"$ref":"#/components/responses/Unauthorized"},"403":{"description":"Your plan includes no voice provider.","content":{"application/json":{"schema":{"allOf":[{"$ref":"#/components/schemas/Error"},{"type":"object","properties":{"error":{"type":"string","enum":["feature_not_allowed"]}}}]},"examples":{"feature_not_allowed":{"summary":"feature_not_allowed","value":{"error":"feature_not_allowed","message":"Your plan does not include any voice provider. Upgrade in the Finn dashboard (see action_url).","action_url":"https://www.hirefinn.ai/dashboard/settings?tab=billing"}}}}}},"404":{"description":"None of your organization's voice providers has a voice with that ID.","content":{"application/json":{"schema":{"allOf":[{"$ref":"#/components/schemas/Error"},{"type":"object","properties":{"error":{"type":"string","enum":["voice_not_found"]}}}]},"examples":{"voice_not_found":{"summary":"voice_not_found","value":{"error":"voice_not_found","message":"No voice with that id"}}}}}},"429":{"$ref":"#/components/responses/RateLimited"},"500":{"$ref":"#/components/responses/InternalError"},"502":{"description":"The voice wasn't found while a voice catalog couldn't be loaded, so Finn can't tell whether it exists; retry later. Also returned when your organization is limited to a voice provider whose voices can't be listed through the API.","content":{"application/json":{"schema":{"allOf":[{"$ref":"#/components/schemas/Error"},{"type":"object","properties":{"error":{"type":"string","enum":["voices_unavailable"]}}}]},"examples":{"voices_unavailable":{"summary":"voices_unavailable","value":{"error":"voices_unavailable","message":"Could not load voices"}}}}}}},"x-required-scope":"voice:read","x-legacy-scope":"voice"},"parameters":[{"$ref":"#/components/parameters/FinnOrg"},{"$ref":"#/components/parameters/FinnVersion"}]},"/phone_numbers":{"get":{"tags":["Phone numbers"],"operationId":"listPhoneNumbers","summary":"List phone numbers","description":"Returns the numbers your organization owns, newest first. There is no total count: page with `offset` until a page has fewer than `limit` items.\n\n**Rate limit:** 300 requests per minute per API key, shared by `GET /phone_numbers` and `GET /phone_numbers/{id}`.\n\n**Members' tokens:** a member's OAuth token or MCP key (with `voice:read`) may also call this where the organization has it switched on; otherwise it answers 403 `credential_not_supported`.","parameters":[{"name":"limit","in":"query","description":"Page size, 1 to 200. Out-of-range values are clamped rather than rejected.","schema":{"type":"integer","minimum":1,"maximum":200,"default":50}},{"name":"offset","in":"query","description":"Number of items to skip.","schema":{"type":"integer","minimum":0,"default":0}}],"responses":{"200":{"description":"A page of phone numbers.","headers":{"RateLimit-Policy":{"$ref":"#/components/headers/RateLimitPolicy"},"RateLimit-Limit":{"$ref":"#/components/headers/RateLimitLimit"},"RateLimit-Remaining":{"$ref":"#/components/headers/RateLimitRemaining"},"RateLimit-Reset":{"$ref":"#/components/headers/RateLimitReset"}},"content":{"application/json":{"schema":{"type":"object","required":["success","data","limit","offset"],"properties":{"success":{"type":"boolean","const":true},"data":{"type":"array","items":{"$ref":"#/components/schemas/PhoneNumber"}},"limit":{"type":"integer","description":"The page size that was applied."},"offset":{"type":"integer","description":"The offset that was applied."}}},"example":{"success":true,"data":[{"id":"6b1f0c2e-3d4a-4f5b-9c8d-2e7a1b9f4c03","phone_number":"+14155550123","status":"Live","provider":"twilio","country":"US","capabilities":{"voice":true,"sms":true,"mms":false},"allocation":{"finn_id":"8f14e45f-ceea-467a-9f6a-1c0e5b2a77d1","call_type":"outbound"},"created_at":"2026-09-02T11:20:41.000Z","updated_at":"2026-09-02T11:21:05.000Z"}],"limit":50,"offset":0}}}},"401":{"$ref":"#/components/responses/Unauthorized"},"429":{"$ref":"#/components/responses/RateLimited"},"500":{"$ref":"#/components/responses/InternalError"}},"x-required-scope":"voice:read","x-legacy-scope":"voice","x-mcp-tool":"list_phone_numbers"},"post":{"tags":["Phone numbers"],"operationId":"rentPhoneNumber","summary":"Rent a phone number","description":"Rents a number found with `GET /phone_numbers/available`. **Your wallet is debited when the request is accepted.** The carrier purchase then runs in the background: the response has no `id`, so poll `GET /phone_numbers` until the number appears. If the purchase fails, the debit is refunded and the number never appears.\n\nRequires an approved compliance application (completed in the dashboard). The rental is attributed to the user who created the API key, who must still be a member of your organization with an email on record. Send an `Idempotency-Key` header so a retry after a timeout or a `5xx` returns the first response instead of recording another purchase. Without the header, a retry for the same number does not debit the wallet twice, but check `GET /phone_numbers` before you retry.\n\n**Rate limit:** 10 requests per minute per API key, shared by `POST /phone_numbers` and `DELETE /phone_numbers/{id}`.","requestBody":{"required":true,"content":{"application/json":{"schema":{"$ref":"#/components/schemas/RentPhoneNumberRequest"},"example":{"phone_number":"+14155550177","capabilities":{"voice":true}}}}},"responses":{"202":{"description":"The wallet was debited and provisioning is queued. Or a `PendingApproval` when the request is held for a person to approve (nothing has run yet).","headers":{"RateLimit-Policy":{"$ref":"#/components/headers/RateLimitPolicy"},"RateLimit-Limit":{"$ref":"#/components/headers/RateLimitLimit"},"RateLimit-Remaining":{"$ref":"#/components/headers/RateLimitRemaining"},"RateLimit-Reset":{"$ref":"#/components/headers/RateLimitReset"},"Idempotent-Replayed":{"$ref":"#/components/headers/IdempotentReplayed"}},"content":{"application/json":{"schema":{"oneOf":[{"type":"object","required":["success","data"],"properties":{"success":{"type":"boolean","const":true},"data":{"$ref":"#/components/schemas/PhoneNumberRental"}}},{"$ref":"#/components/schemas/PendingApproval"}]},"example":{"success":true,"data":{"status":"provisioning","phone_number":"+14155550177","transaction_id":"txn_1759740062114_4f9c2a1e","price":1.15,"currency":"USD","credits_debited":12,"remaining_credits":4163}}}}},"400":{"description":"`phone_number`, `provider` or `capabilities` is invalid, or the number is priced in a different currency from your wallet. Also returned when `Idempotency-Key` is not 1 to 255 visible ASCII characters.","content":{"application/json":{"schema":{"allOf":[{"$ref":"#/components/schemas/Error"},{"type":"object","properties":{"error":{"type":"string","enum":["invalid_request","price_currency_mismatch"]}}}]},"examples":{"invalid_request":{"summary":"invalid_request","value":{"error":"invalid_request","message":"phone_number must be a number from GET /phone_numbers/available"}},"price_currency_mismatch":{"summary":"price_currency_mismatch","value":{"error":"price_currency_mismatch","message":"The number is priced in a different currency from your wallet."}}}}}},"401":{"$ref":"#/components/responses/Unauthorized"},"402":{"description":"The wallet cannot cover the rental. The body adds `required_credits` and `available_credits`.","content":{"application/json":{"schema":{"allOf":[{"$ref":"#/components/schemas/Error"},{"type":"object","properties":{"error":{"type":"string","enum":["insufficient_credits"]}}}]},"examples":{"insufficient_credits":{"summary":"insufficient_credits","value":{"error":"insufficient_credits","message":"Wallet balance is too low to rent this number. Top up in the Finn dashboard (see action_url).","action_url":"https://www.hirefinn.ai/dashboard/settings?tab=billing","required_credits":12,"available_credits":4}}}}}},"403":{"description":"Your compliance application is not approved, or your organization cannot rent numbers.","content":{"application/json":{"schema":{"allOf":[{"$ref":"#/components/schemas/Error"},{"type":"object","properties":{"error":{"type":"string","enum":["compliance_required","rental_unavailable"]}}}]},"examples":{"compliance_required":{"summary":"compliance_required","value":{"error":"compliance_required","message":"Compliance approval is required. Complete the compliance application in the Finn dashboard (see action_url), then retry.","action_url":"https://www.hirefinn.ai/dashboard/settings?tab=telephony-account"}},"rental_unavailable":{"summary":"rental_unavailable","value":{"error":"rental_unavailable","message":"Number rental is not available for this organization. Contact Finn support."}}}}}},"409":{"description":"The key's creator is no longer a member of your organization or has no email on record. `idempotency_in_progress`: a request with the same `Idempotency-Key` is still running; retry after `Retry-After` seconds to get its result.","content":{"application/json":{"schema":{"allOf":[{"$ref":"#/components/schemas/Error"},{"type":"object","properties":{"error":{"type":"string","enum":["api_key_owner_required"]}}}]},"examples":{"api_key_owner_required":{"summary":"api_key_owner_required","value":{"error":"api_key_owner_required","message":"This API key's creator is no longer a member of this organization or has no email on record, which number rentals require. Create a new key from the dashboard.","action_url":"https://www.hirefinn.ai/dashboard/settings?tab=integrations"}}}}}},"422":{"description":"The request cannot be carried out. `idempotency_key_reused`: this `Idempotency-Key` was already used with a different request.","content":{"application/json":{"schema":{"$ref":"#/components/schemas/Error"},"example":{"error":"idempotency_key_reused","message":"This Idempotency-Key was already used with a different request. Use a new key for a new request."}}}},"423":{"$ref":"#/components/responses/AiAppsPaused"},"429":{"$ref":"#/components/responses/RateLimited"},"500":{"description":"Provisioning could not be queued and the debit was refunded (`rent_failed`, with `transaction_id`), or an unexpected error occurred.","content":{"application/json":{"schema":{"allOf":[{"$ref":"#/components/schemas/Error"},{"type":"object","properties":{"error":{"type":"string","enum":["rent_failed","internal_error"]}}}]},"examples":{"rent_failed":{"summary":"rent_failed","value":{"error":"rent_failed","message":"Provisioning could not be queued; the debit was refunded.","transaction_id":"txn_1759740062114_4f9c2a1e"}},"internal_error":{"summary":"internal_error","value":{"error":"internal_error"}}}}}},"502":{"description":"The rental price could not be confirmed with the carrier. Nothing was charged; retry later.","content":{"application/json":{"schema":{"allOf":[{"$ref":"#/components/schemas/Error"},{"type":"object","properties":{"error":{"type":"string","enum":["price_unavailable"]}}}]},"examples":{"price_unavailable":{"summary":"price_unavailable","value":{"error":"price_unavailable","message":"Could not confirm the rental price for this number. Please try again."}}}}}}},"x-required-scope":"voice:write","x-legacy-scope":"voice","parameters":[{"$ref":"#/components/parameters/IdempotencyKey"}],"x-finn-hold":{"action":"phone_number.rent","class":"money"}},"parameters":[{"$ref":"#/components/parameters/FinnOrg"},{"$ref":"#/components/parameters/FinnVersion"}]},"/phone_numbers/available":{"get":{"tags":["Phone numbers"],"operationId":"searchAvailablePhoneNumbers","summary":"Search available numbers","description":"Searches carrier inventory for numbers you can rent. Each search is a live carrier query; the result is not paginated, and an empty `data` array means nothing matched.\n\n`price` is set only for numbers priced per number (Exotel, in INR). For other carriers it is `null`, and the amount charged is returned by `POST /phone_numbers`.\n\nSearching India (`IN`) inventory or Exotel inventory requires an approved compliance application, otherwise `409 compliance_required`.\n\n**Rate limit:** 30 requests per minute per API key.","parameters":[{"name":"country","in":"query","required":true,"description":"ISO 3166-1 alpha-2 country code.","schema":{"type":"string","pattern":"^[A-Za-z]{2}$"},"example":"US"},{"name":"search","in":"query","description":"Passed to the carrier to filter numbers, for example by digits.","schema":{"type":"string","maxLength":20}},{"name":"provider","in":"query","description":"Carrier. Only `exotel` changes the search (it searches Exotel inventory); India is otherwise searched on Plivo and every other country on Twilio. Omitting it searches Exotel when your organization's default carrier is Exotel.","schema":{"type":"string","enum":["plivo","twilio","exotel"]}}],"responses":{"200":{"description":"Numbers available to rent.","headers":{"RateLimit-Policy":{"$ref":"#/components/headers/RateLimitPolicy"},"RateLimit-Limit":{"$ref":"#/components/headers/RateLimitLimit"},"RateLimit-Remaining":{"$ref":"#/components/headers/RateLimitRemaining"},"RateLimit-Reset":{"$ref":"#/components/headers/RateLimitReset"}},"content":{"application/json":{"schema":{"type":"object","required":["success","data"],"properties":{"success":{"type":"boolean","const":true},"data":{"type":"array","items":{"$ref":"#/components/schemas/AvailablePhoneNumber"}}}},"example":{"success":true,"data":[{"phone_number":"+14155550177","country":"US","region":"CA","locality":"San Francisco","number_type":"local","capabilities":{"voice":true,"sms":true,"mms":false},"price":null,"currency":null}]}}}},"400":{"description":"The request failed validation. `message` names the problem.","content":{"application/json":{"schema":{"allOf":[{"$ref":"#/components/schemas/Error"},{"type":"object","properties":{"error":{"type":"string","enum":["invalid_request"]}}}]},"examples":{"invalid_request":{"summary":"invalid_request","value":{"error":"invalid_request","message":"country must be a 2-letter ISO country code (e.g. IN, US)"}}}}}},"401":{"$ref":"#/components/responses/Unauthorized"},"409":{"description":"This inventory requires an approved compliance application.","content":{"application/json":{"schema":{"allOf":[{"$ref":"#/components/schemas/Error"},{"type":"object","properties":{"error":{"type":"string","enum":["compliance_required"]}}}]},"examples":{"compliance_required":{"summary":"compliance_required","value":{"error":"compliance_required","message":"Compliance approval is required. Complete the compliance application in the Finn dashboard (see action_url), then retry.","action_url":"https://www.hirefinn.ai/dashboard/settings?tab=telephony-account"}}}}}},"429":{"$ref":"#/components/responses/RateLimited"},"500":{"$ref":"#/components/responses/InternalError"},"502":{"description":"The carrier could not be reached. Retry later.","content":{"application/json":{"schema":{"allOf":[{"$ref":"#/components/schemas/Error"},{"type":"object","properties":{"error":{"type":"string","enum":["provider_unavailable"]}}}]},"examples":{"provider_unavailable":{"summary":"provider_unavailable","value":{"error":"provider_unavailable","message":"Unable to fetch numbers from the carrier right now. Please try again."}}}}}}},"x-required-scope":"voice:read","x-legacy-scope":"voice"},"parameters":[{"$ref":"#/components/parameters/FinnOrg"},{"$ref":"#/components/parameters/FinnVersion"}]},"/phone_numbers/{id}":{"get":{"tags":["Phone numbers"],"operationId":"retrievePhoneNumber","summary":"Retrieve a phone number","description":"Returns one number your organization owns.\n\n**Rate limit:** 300 requests per minute per API key, shared by `GET /phone_numbers` and `GET /phone_numbers/{id}`.\n\n**Members' tokens:** a member's OAuth token or MCP key (with `voice:read`) may also call this where the organization has it switched on; otherwise it answers 403 `credential_not_supported`.","parameters":[{"name":"id","in":"path","required":true,"description":"The phone number ID.","schema":{"type":"string","format":"uuid"},"example":"6b1f0c2e-3d4a-4f5b-9c8d-2e7a1b9f4c03"}],"responses":{"200":{"description":"The phone number.","headers":{"RateLimit-Policy":{"$ref":"#/components/headers/RateLimitPolicy"},"RateLimit-Limit":{"$ref":"#/components/headers/RateLimitLimit"},"RateLimit-Remaining":{"$ref":"#/components/headers/RateLimitRemaining"},"RateLimit-Reset":{"$ref":"#/components/headers/RateLimitReset"}},"content":{"application/json":{"schema":{"type":"object","required":["success","data"],"properties":{"success":{"type":"boolean","const":true},"data":{"$ref":"#/components/schemas/PhoneNumber"}}},"example":{"success":true,"data":{"id":"6b1f0c2e-3d4a-4f5b-9c8d-2e7a1b9f4c03","phone_number":"+14155550123","status":"Live","provider":"twilio","country":"US","capabilities":{"voice":true,"sms":true,"mms":false},"allocation":{"finn_id":"8f14e45f-ceea-467a-9f6a-1c0e5b2a77d1","call_type":"outbound"},"created_at":"2026-09-02T11:20:41.000Z","updated_at":"2026-09-02T11:21:05.000Z"}}}}},"400":{"description":"The request failed validation. `message` names the problem.","content":{"application/json":{"schema":{"allOf":[{"$ref":"#/components/schemas/Error"},{"type":"object","properties":{"error":{"type":"string","enum":["invalid_request"]}}}]},"examples":{"invalid_request":{"summary":"invalid_request","value":{"error":"invalid_request","message":"id must be a valid UUID"}}}}}},"401":{"$ref":"#/components/responses/Unauthorized"},"404":{"description":"No number with that ID in your organization.","content":{"application/json":{"schema":{"allOf":[{"$ref":"#/components/schemas/Error"},{"type":"object","properties":{"error":{"type":"string","enum":["phone_number_not_found"]}}}]},"examples":{"phone_number_not_found":{"summary":"phone_number_not_found","value":{"error":"phone_number_not_found","message":"No phone number with that id in this organization"}}}}}},"429":{"$ref":"#/components/responses/RateLimited"},"500":{"$ref":"#/components/responses/InternalError"}},"x-required-scope":"voice:read","x-legacy-scope":"voice","x-mcp-tool":"get_phone_number"},"delete":{"tags":["Phone numbers"],"operationId":"releasePhoneNumber","summary":"Release a phone number","description":"Returns the number to the carrier and deletes it from your organization. **There is no refund.**\n\nRefused with `409 number_in_use` when the number is used by a deployment that has not ended (as its number or in its `number_pool`), when it has ever been a deployment's `phone_number_id` (contact support to release it), or when it has assignment records and `force` is not `true`.\n\n**Rate limit:** 10 requests per minute per API key, shared by `POST /phone_numbers` and `DELETE /phone_numbers/{id}`.","parameters":[{"name":"id","in":"path","required":true,"description":"The phone number ID.","schema":{"type":"string","format":"uuid"},"example":"6b1f0c2e-3d4a-4f5b-9c8d-2e7a1b9f4c03"},{"name":"force","in":"query","description":"`true` removes the number's assignment records (current and past) before releasing it.","schema":{"type":"boolean","default":false}}],"responses":{"200":{"description":"The number was released.","headers":{"RateLimit-Policy":{"$ref":"#/components/headers/RateLimitPolicy"},"RateLimit-Limit":{"$ref":"#/components/headers/RateLimitLimit"},"RateLimit-Remaining":{"$ref":"#/components/headers/RateLimitRemaining"},"RateLimit-Reset":{"$ref":"#/components/headers/RateLimitReset"}},"content":{"application/json":{"schema":{"type":"object","required":["success","data"],"properties":{"success":{"type":"boolean","const":true},"data":{"$ref":"#/components/schemas/ReleasedPhoneNumber"}}},"example":{"success":true,"data":{"id":"6b1f0c2e-3d4a-4f5b-9c8d-2e7a1b9f4c03","released":true}}}}},"400":{"description":"The request failed validation. `message` names the problem.","content":{"application/json":{"schema":{"allOf":[{"$ref":"#/components/schemas/Error"},{"type":"object","properties":{"error":{"type":"string","enum":["invalid_request"]}}}]},"examples":{"invalid_request":{"summary":"invalid_request","value":{"error":"invalid_request","message":"id must be a valid UUID"}}}}}},"401":{"$ref":"#/components/responses/Unauthorized"},"404":{"description":"No number with that ID in your organization.","content":{"application/json":{"schema":{"allOf":[{"$ref":"#/components/schemas/Error"},{"type":"object","properties":{"error":{"type":"string","enum":["phone_number_not_found"]}}}]},"examples":{"phone_number_not_found":{"summary":"phone_number_not_found","value":{"error":"phone_number_not_found","message":"No phone number with that id in this organization"}}}}}},"409":{"description":"The number is in use.","content":{"application/json":{"schema":{"allOf":[{"$ref":"#/components/schemas/Error"},{"type":"object","properties":{"error":{"type":"string","enum":["number_in_use"]}}}]},"examples":{"number_in_use":{"summary":"number_in_use","value":{"error":"number_in_use","message":"This number is in the number pool of a running deployment. Stop the deployment, then release the number."}}}}}},"423":{"$ref":"#/components/responses/AiAppsPaused"},"429":{"$ref":"#/components/responses/RateLimited"},"500":{"$ref":"#/components/responses/InternalError"},"502":{"description":"The carrier could not be reached. Retry later.","content":{"application/json":{"schema":{"allOf":[{"$ref":"#/components/schemas/Error"},{"type":"object","properties":{"error":{"type":"string","enum":["provider_unavailable"]}}}]},"examples":{"provider_unavailable":{"summary":"provider_unavailable","value":{"error":"provider_unavailable","message":"Could not release the number at the carrier. Please try again."}}}}}}},"x-required-scope":"voice:write","x-legacy-scope":"voice"},"parameters":[{"$ref":"#/components/parameters/FinnOrg"},{"$ref":"#/components/parameters/FinnVersion"}]},"/audiences":{"get":{"tags":["Audiences"],"operationId":"listAudiences","summary":"List audiences","description":"Returns your organization's audiences, newest first. Archived audiences are not returned. There is no total count: page with `offset` until a page has fewer than `limit` items.\n\n**Rate limit:** 300 requests per minute per API key, shared by all audience reads.\n\n**Members' tokens:** a member's OAuth token or MCP key (with `voice:read`) may also call this where the organization has it switched on; otherwise it answers 403 `credential_not_supported`.","parameters":[{"name":"limit","in":"query","description":"Page size, 1 to 200. Out-of-range values are clamped rather than rejected.","schema":{"type":"integer","minimum":1,"maximum":200,"default":50}},{"name":"offset","in":"query","description":"Number of items to skip.","schema":{"type":"integer","minimum":0,"default":0}}],"responses":{"200":{"description":"A page of audiences.","headers":{"RateLimit-Policy":{"$ref":"#/components/headers/RateLimitPolicy"},"RateLimit-Limit":{"$ref":"#/components/headers/RateLimitLimit"},"RateLimit-Remaining":{"$ref":"#/components/headers/RateLimitRemaining"},"RateLimit-Reset":{"$ref":"#/components/headers/RateLimitReset"}},"content":{"application/json":{"schema":{"type":"object","required":["success","data","limit","offset"],"properties":{"success":{"type":"boolean","const":true},"data":{"type":"array","items":{"$ref":"#/components/schemas/Audience"}},"limit":{"type":"integer","description":"The page size that was applied."},"offset":{"type":"integer","description":"The offset that was applied."}}},"example":{"success":true,"data":[{"id":"c2d9e7a4-51b8-4e0f-8a63-9f1d2b7c5e48","name":"October renewals","contact_count":2,"source":"api","is_dynamic":false,"created_at":"2026-10-01T08:30:12.000Z","updated_at":null}],"limit":50,"offset":0}}}},"401":{"$ref":"#/components/responses/Unauthorized"},"429":{"$ref":"#/components/responses/RateLimited"},"500":{"$ref":"#/components/responses/InternalError"}},"x-required-scope":"voice:read","x-legacy-scope":"voice","x-mcp-tool":"list_audiences"},"post":{"tags":["Audiences"],"operationId":"createAudience","summary":"Create an audience","description":"Creates an audience, optionally with up to 5,000 contacts. Omit `contacts` to create an empty audience and add contacts later. If any contact fails validation, nothing is created and the `400` response lists up to 50 failures in `details`. Duplicate numbers are skipped and counted in `duplicates_skipped`.\n\nA retried create makes a second audience.\n\n**Rate limit:** 60 requests per minute per API key, shared by all audience writes.","requestBody":{"required":true,"content":{"application/json":{"schema":{"$ref":"#/components/schemas/CreateAudienceRequest"},"example":{"name":"October renewals","contacts":[{"name":"Asha Rao","phone_number":"9876543210","country_code":"+91","plan":"gold"},{"name":"Dan Hill","phone_number":"+14155550142","country_code":"1","plan":"silver"}]}}}},"responses":{"201":{"description":"The created audience.","headers":{"RateLimit-Policy":{"$ref":"#/components/headers/RateLimitPolicy"},"RateLimit-Limit":{"$ref":"#/components/headers/RateLimitLimit"},"RateLimit-Remaining":{"$ref":"#/components/headers/RateLimitRemaining"},"RateLimit-Reset":{"$ref":"#/components/headers/RateLimitReset"}},"content":{"application/json":{"schema":{"type":"object","required":["success","data","contacts_added","duplicates_skipped"],"properties":{"success":{"type":"boolean","const":true},"data":{"$ref":"#/components/schemas/Audience"},"contacts_added":{"type":"integer","description":"Contacts stored."},"duplicates_skipped":{"type":"integer","description":"Contacts skipped because their number was already in the request."}}},"example":{"success":true,"data":{"id":"c2d9e7a4-51b8-4e0f-8a63-9f1d2b7c5e48","name":"October renewals","contact_count":2,"source":"api","is_dynamic":false,"created_at":"2026-10-01T08:30:12.000Z","updated_at":null},"contacts_added":2,"duplicates_skipped":0}}}},"400":{"description":"`name` is missing or too long, `contacts` is not an array or has more than 5,000 items, or a contact failed validation (listed in `details`).","content":{"application/json":{"schema":{"allOf":[{"$ref":"#/components/schemas/Error"},{"type":"object","properties":{"error":{"type":"string","enum":["invalid_request"]}}}]},"examples":{"invalid_request":{"summary":"invalid_request","value":{"error":"invalid_request","message":"1 contact(s) failed validation","details":[{"index":1,"message":"country_code is required"}]}}}}}},"401":{"$ref":"#/components/responses/Unauthorized"},"423":{"$ref":"#/components/responses/AiAppsPaused"},"429":{"$ref":"#/components/responses/RateLimited"},"500":{"$ref":"#/components/responses/InternalError"}},"x-required-scope":"voice:write","x-legacy-scope":"voice"},"parameters":[{"$ref":"#/components/parameters/FinnOrg"},{"$ref":"#/components/parameters/FinnVersion"}]},"/audiences/{audience_id}":{"get":{"tags":["Audiences"],"operationId":"retrieveAudience","summary":"Retrieve an audience","description":"Returns one audience.\n\n**Rate limit:** 300 requests per minute per API key, shared by all audience reads.\n\n**Members' tokens:** a member's OAuth token or MCP key (with `voice:read`) may also call this where the organization has it switched on; otherwise it answers 403 `credential_not_supported`.","parameters":[{"name":"audience_id","in":"path","required":true,"description":"The audience ID.","schema":{"type":"string","format":"uuid"},"example":"c2d9e7a4-51b8-4e0f-8a63-9f1d2b7c5e48"}],"responses":{"200":{"description":"The audience.","headers":{"RateLimit-Policy":{"$ref":"#/components/headers/RateLimitPolicy"},"RateLimit-Limit":{"$ref":"#/components/headers/RateLimitLimit"},"RateLimit-Remaining":{"$ref":"#/components/headers/RateLimitRemaining"},"RateLimit-Reset":{"$ref":"#/components/headers/RateLimitReset"}},"content":{"application/json":{"schema":{"type":"object","required":["success","data"],"properties":{"success":{"type":"boolean","const":true},"data":{"$ref":"#/components/schemas/Audience"}}},"example":{"success":true,"data":{"id":"c2d9e7a4-51b8-4e0f-8a63-9f1d2b7c5e48","name":"October renewals","contact_count":2,"source":"api","is_dynamic":false,"created_at":"2026-10-01T08:30:12.000Z","updated_at":null}}}}},"400":{"description":"The request failed validation. `message` names the problem.","content":{"application/json":{"schema":{"allOf":[{"$ref":"#/components/schemas/Error"},{"type":"object","properties":{"error":{"type":"string","enum":["invalid_request"]}}}]},"examples":{"invalid_request":{"summary":"invalid_request","value":{"error":"invalid_request","message":"audience_id must be a valid UUID"}}}}}},"401":{"$ref":"#/components/responses/Unauthorized"},"404":{"description":"No active audience with that ID in your organization.","content":{"application/json":{"schema":{"allOf":[{"$ref":"#/components/schemas/Error"},{"type":"object","properties":{"error":{"type":"string","enum":["audience_not_found"]}}}]},"examples":{"audience_not_found":{"summary":"audience_not_found","value":{"error":"audience_not_found","message":"No audience with that id in this organization"}}}}}},"429":{"$ref":"#/components/responses/RateLimited"},"500":{"$ref":"#/components/responses/InternalError"}},"x-required-scope":"voice:read","x-legacy-scope":"voice","x-mcp-tool":"get_audience"},"delete":{"tags":["Audiences"],"operationId":"archiveAudience","summary":"Archive an audience","description":"Archives the audience, as the dashboard does. It disappears from every endpoint and cannot be used for new deployments. Archiving does not stop a deployment that is already dialing it.\n\n**Rate limit:** 60 requests per minute per API key, shared by all audience writes.","parameters":[{"name":"audience_id","in":"path","required":true,"description":"The audience ID.","schema":{"type":"string","format":"uuid"},"example":"c2d9e7a4-51b8-4e0f-8a63-9f1d2b7c5e48"}],"responses":{"200":{"description":"The audience was archived.","headers":{"RateLimit-Policy":{"$ref":"#/components/headers/RateLimitPolicy"},"RateLimit-Limit":{"$ref":"#/components/headers/RateLimitLimit"},"RateLimit-Remaining":{"$ref":"#/components/headers/RateLimitRemaining"},"RateLimit-Reset":{"$ref":"#/components/headers/RateLimitReset"}},"content":{"application/json":{"schema":{"type":"object","required":["success","data"],"properties":{"success":{"type":"boolean","const":true},"data":{"$ref":"#/components/schemas/ArchivedAudience"}}},"example":{"success":true,"data":{"id":"c2d9e7a4-51b8-4e0f-8a63-9f1d2b7c5e48","archived":true}}}}},"400":{"description":"The request failed validation. `message` names the problem.","content":{"application/json":{"schema":{"allOf":[{"$ref":"#/components/schemas/Error"},{"type":"object","properties":{"error":{"type":"string","enum":["invalid_request"]}}}]},"examples":{"invalid_request":{"summary":"invalid_request","value":{"error":"invalid_request","message":"audience_id must be a valid UUID"}}}}}},"401":{"$ref":"#/components/responses/Unauthorized"},"404":{"description":"No active audience with that ID in your organization.","content":{"application/json":{"schema":{"allOf":[{"$ref":"#/components/schemas/Error"},{"type":"object","properties":{"error":{"type":"string","enum":["audience_not_found"]}}}]},"examples":{"audience_not_found":{"summary":"audience_not_found","value":{"error":"audience_not_found","message":"No audience with that id in this organization"}}}}}},"423":{"$ref":"#/components/responses/AiAppsPaused"},"429":{"$ref":"#/components/responses/RateLimited"},"500":{"$ref":"#/components/responses/InternalError"}},"x-required-scope":"voice:write","x-legacy-scope":"voice"},"parameters":[{"$ref":"#/components/parameters/FinnOrg"},{"$ref":"#/components/parameters/FinnVersion"}]},"/audiences/{audience_id}/contacts":{"get":{"tags":["Audiences"],"operationId":"listAudienceContacts","summary":"List contacts","description":"Returns the audience's contacts in file order, with every value as a string. `total` is the number of contacts in the audience.\n\nTo keep the contact file safe to open in a spreadsheet, a `name` or custom field value that starts with `=`, `@`, `+` or `-` (and does not look like a number) is stored with a leading `'`. Audiences uploaded in the dashboard keep their own column names, for example `phone` instead of `phone_number`.\n\n**Rate limit:** 300 requests per minute per API key, shared by all audience reads.","parameters":[{"name":"audience_id","in":"path","required":true,"description":"The audience ID.","schema":{"type":"string","format":"uuid"},"example":"c2d9e7a4-51b8-4e0f-8a63-9f1d2b7c5e48"},{"name":"limit","in":"query","description":"Page size, 1 to 1000. Out-of-range values are clamped rather than rejected.","schema":{"type":"integer","minimum":1,"maximum":1000,"default":100}},{"name":"offset","in":"query","description":"Number of items to skip.","schema":{"type":"integer","minimum":0,"default":0}}],"responses":{"200":{"description":"A page of contacts.","headers":{"RateLimit-Policy":{"$ref":"#/components/headers/RateLimitPolicy"},"RateLimit-Limit":{"$ref":"#/components/headers/RateLimitLimit"},"RateLimit-Remaining":{"$ref":"#/components/headers/RateLimitRemaining"},"RateLimit-Reset":{"$ref":"#/components/headers/RateLimitReset"}},"content":{"application/json":{"schema":{"type":"object","required":["success","data","total","limit","offset"],"properties":{"success":{"type":"boolean","const":true},"data":{"type":"array","items":{"$ref":"#/components/schemas/Contact"}},"total":{"type":"integer","description":"Number of contacts in the audience."},"limit":{"type":"integer","description":"The page size that was applied."},"offset":{"type":"integer","description":"The offset that was applied."}}},"example":{"success":true,"data":[{"name":"Asha Rao","phone_number":"9876543210","country_code":"+91","plan":"gold"},{"name":"Dan Hill","phone_number":"4155550142","country_code":"+1","plan":"silver"}],"total":2,"limit":100,"offset":0}}}},"400":{"description":"The request failed validation. `message` names the problem.","content":{"application/json":{"schema":{"allOf":[{"$ref":"#/components/schemas/Error"},{"type":"object","properties":{"error":{"type":"string","enum":["invalid_request"]}}}]},"examples":{"invalid_request":{"summary":"invalid_request","value":{"error":"invalid_request","message":"audience_id must be a valid UUID"}}}}}},"401":{"$ref":"#/components/responses/Unauthorized"},"404":{"description":"No active audience with that ID, or its contact file no longer exists.","content":{"application/json":{"schema":{"allOf":[{"$ref":"#/components/schemas/Error"},{"type":"object","properties":{"error":{"type":"string","enum":["audience_not_found","contacts_file_missing"]}}}]},"examples":{"audience_not_found":{"summary":"audience_not_found","value":{"error":"audience_not_found","message":"No audience with that id in this organization"}},"contacts_file_missing":{"summary":"contacts_file_missing","value":{"error":"contacts_file_missing","message":"The contact file for this audience no longer exists"}}}}}},"429":{"$ref":"#/components/responses/RateLimited"},"500":{"$ref":"#/components/responses/InternalError"}},"x-required-scope":"voice:read","x-legacy-scope":"voice"},"post":{"tags":["Audiences"],"operationId":"addAudienceContacts","summary":"Add contacts","description":"Appends 1 to 5,000 contacts. Validation works as on create: one invalid contact rejects the whole request. Numbers already in the audience are skipped. New custom field names become new columns. An audience holds at most 50,000 contacts.\n\nRefused with `409 audience_in_use` while a running deployment (status `in_progress`, `active`, `running` or `live`) uses the audience.\n\n**Rate limit:** 60 requests per minute per API key, shared by all audience writes.","parameters":[{"name":"audience_id","in":"path","required":true,"description":"The audience ID.","schema":{"type":"string","format":"uuid"},"example":"c2d9e7a4-51b8-4e0f-8a63-9f1d2b7c5e48"}],"requestBody":{"required":true,"content":{"application/json":{"schema":{"$ref":"#/components/schemas/AddContactsRequest"},"example":{"contacts":[{"name":"Ravi Menon","phone_number":"9123456780","country_code":"+91","plan":"gold"}]}}}},"responses":{"200":{"description":"The updated audience.","headers":{"RateLimit-Policy":{"$ref":"#/components/headers/RateLimitPolicy"},"RateLimit-Limit":{"$ref":"#/components/headers/RateLimitLimit"},"RateLimit-Remaining":{"$ref":"#/components/headers/RateLimitRemaining"},"RateLimit-Reset":{"$ref":"#/components/headers/RateLimitReset"}},"content":{"application/json":{"schema":{"type":"object","required":["success","data","contacts_added","duplicates_skipped"],"properties":{"success":{"type":"boolean","const":true},"data":{"$ref":"#/components/schemas/Audience"},"contacts_added":{"type":"integer","description":"Contacts stored."},"duplicates_skipped":{"type":"integer","description":"Contacts skipped because their number was already in the audience or earlier in the request."}}},"example":{"success":true,"data":{"id":"c2d9e7a4-51b8-4e0f-8a63-9f1d2b7c5e48","name":"October renewals","contact_count":3,"source":"api","is_dynamic":false,"created_at":"2026-10-01T08:30:12.000Z","updated_at":"2026-10-02T10:04:55.000Z"},"contacts_added":1,"duplicates_skipped":0}}}},"400":{"description":"`audience_id` is not a UUID, `contacts` is missing, empty or too long, or a contact failed validation (listed in `details`).","content":{"application/json":{"schema":{"allOf":[{"$ref":"#/components/schemas/Error"},{"type":"object","properties":{"error":{"type":"string","enum":["invalid_request"]}}}]},"examples":{"invalid_request":{"summary":"invalid_request","value":{"error":"invalid_request","message":"1 contact(s) failed validation","details":[{"index":0,"message":"phone_number is required"}]}}}}}},"401":{"$ref":"#/components/responses/Unauthorized"},"404":{"description":"No active audience with that ID, or its contact file no longer exists.","content":{"application/json":{"schema":{"allOf":[{"$ref":"#/components/schemas/Error"},{"type":"object","properties":{"error":{"type":"string","enum":["audience_not_found","contacts_file_missing"]}}}]},"examples":{"audience_not_found":{"summary":"audience_not_found","value":{"error":"audience_not_found","message":"No audience with that id in this organization"}},"contacts_file_missing":{"summary":"contacts_file_missing","value":{"error":"contacts_file_missing","message":"The contact file for this audience no longer exists"}}}}}},"409":{"description":"The audience cannot be edited right now.","content":{"application/json":{"schema":{"allOf":[{"$ref":"#/components/schemas/Error"},{"type":"object","properties":{"error":{"type":"string","enum":["audience_in_use","audience_not_editable","audience_limit_exceeded"]}}}]},"examples":{"audience_in_use":{"summary":"audience_in_use","value":{"error":"audience_in_use","message":"This audience is used by a running deployment. Stop the deployment before editing its contacts.","deployment_id":"0e8a4c71-9b2d-4f36-a5e1-7c3d9b2f6a10"}},"audience_not_editable":{"summary":"audience_not_editable","value":{"error":"audience_not_editable","message":"This audience has no contact file to edit"}},"audience_limit_exceeded":{"summary":"audience_limit_exceeded","value":{"error":"audience_limit_exceeded","message":"An audience can hold at most 50000 contacts"}}}}}},"423":{"$ref":"#/components/responses/AiAppsPaused"},"429":{"$ref":"#/components/responses/RateLimited"},"500":{"$ref":"#/components/responses/InternalError"}},"x-required-scope":"voice:write","x-legacy-scope":"voice"},"parameters":[{"$ref":"#/components/parameters/FinnOrg"},{"$ref":"#/components/parameters/FinnVersion"}]},"/audiences/{audience_id}/contacts/{phone}":{"delete":{"tags":["Audiences"],"operationId":"removeAudienceContact","summary":"Remove a contact","description":"Removes every contact whose number matches `phone`. Digits are compared, so `+91 98765-43210` matches `919876543210`. You cannot remove the last contact; archive the audience instead.\n\nRefused with `409 audience_in_use` while a running deployment uses the audience.\n\n**Rate limit:** 60 requests per minute per API key, shared by all audience writes.","parameters":[{"name":"audience_id","in":"path","required":true,"description":"The audience ID.","schema":{"type":"string","format":"uuid"},"example":"c2d9e7a4-51b8-4e0f-8a63-9f1d2b7c5e48"},{"name":"phone","in":"path","required":true,"description":"The full number including its country code. URL-encode a leading `+` as `%2B`, or leave it out.","schema":{"type":"string","pattern":"^\\+?[\\d\\s\\-().]+$"},"example":"919876543210"}],"responses":{"200":{"description":"The updated audience.","headers":{"RateLimit-Policy":{"$ref":"#/components/headers/RateLimitPolicy"},"RateLimit-Limit":{"$ref":"#/components/headers/RateLimitLimit"},"RateLimit-Remaining":{"$ref":"#/components/headers/RateLimitRemaining"},"RateLimit-Reset":{"$ref":"#/components/headers/RateLimitReset"}},"content":{"application/json":{"schema":{"type":"object","required":["success","data","contacts_removed"],"properties":{"success":{"type":"boolean","const":true},"data":{"$ref":"#/components/schemas/Audience"},"contacts_removed":{"type":"integer","description":"Contacts removed."}}},"example":{"success":true,"data":{"id":"c2d9e7a4-51b8-4e0f-8a63-9f1d2b7c5e48","name":"October renewals","contact_count":1,"source":"api","is_dynamic":false,"created_at":"2026-10-01T08:30:12.000Z","updated_at":"2026-10-02T10:06:31.000Z"},"contacts_removed":1}}}},"400":{"description":"The request failed validation. `message` names the problem.","content":{"application/json":{"schema":{"allOf":[{"$ref":"#/components/schemas/Error"},{"type":"object","properties":{"error":{"type":"string","enum":["invalid_request"]}}}]},"examples":{"invalid_request":{"summary":"invalid_request","value":{"error":"invalid_request","message":"phone must be a phone number including its country code"}}}}}},"401":{"$ref":"#/components/responses/Unauthorized"},"404":{"description":"No active audience with that ID, its contact file no longer exists, or no contact has that number.","content":{"application/json":{"schema":{"allOf":[{"$ref":"#/components/schemas/Error"},{"type":"object","properties":{"error":{"type":"string","enum":["audience_not_found","contacts_file_missing","contact_not_found"]}}}]},"examples":{"audience_not_found":{"summary":"audience_not_found","value":{"error":"audience_not_found","message":"No audience with that id in this organization"}},"contacts_file_missing":{"summary":"contacts_file_missing","value":{"error":"contacts_file_missing","message":"The contact file for this audience no longer exists"}},"contact_not_found":{"summary":"contact_not_found","value":{"error":"contact_not_found","message":"No contact with that phone number in this audience"}}}}}},"409":{"description":"The audience cannot be edited right now, or this is its last contact.","content":{"application/json":{"schema":{"allOf":[{"$ref":"#/components/schemas/Error"},{"type":"object","properties":{"error":{"type":"string","enum":["audience_in_use","audience_not_editable","audience_would_be_empty"]}}}]},"examples":{"audience_in_use":{"summary":"audience_in_use","value":{"error":"audience_in_use","message":"This audience is used by a running deployment. Stop the deployment before editing its contacts.","deployment_id":"0e8a4c71-9b2d-4f36-a5e1-7c3d9b2f6a10"}},"audience_not_editable":{"summary":"audience_not_editable","value":{"error":"audience_not_editable","message":"This audience has no contact file to edit"}},"audience_would_be_empty":{"summary":"audience_would_be_empty","value":{"error":"audience_would_be_empty","message":"Cannot remove the last contact; archive the audience instead"}}}}}},"423":{"$ref":"#/components/responses/AiAppsPaused"},"429":{"$ref":"#/components/responses/RateLimited"},"500":{"$ref":"#/components/responses/InternalError"}},"x-required-scope":"voice:write","x-legacy-scope":"voice"},"parameters":[{"$ref":"#/components/parameters/FinnOrg"},{"$ref":"#/components/parameters/FinnVersion"}]},"/deployments":{"get":{"tags":["Deployments"],"operationId":"listDeployments","x-mcp-tool":"list_campaigns","summary":"List deployments","description":"Returns your organization's deployments, newest first. Deployments hidden in the dashboard are not returned. There is no total count: page with `offset` until a page has fewer than `limit` items.\n\n**Rate limit:** 300 requests per minute per API key, shared with `GET /deployments`, `GET /deployments/{id}` and `GET /wallet/transactions`.","parameters":[{"name":"status","in":"query","description":"One lowercase status, for example `in_progress`.","schema":{"type":"string","pattern":"^[a-z_]{1,64}$"}},{"name":"call_type","in":"query","description":"Deployment direction.","schema":{"type":"string","enum":["inbound","outbound"]}},{"name":"limit","in":"query","description":"Page size, 1 to 200. Out-of-range values are clamped rather than rejected.","schema":{"type":"integer","minimum":1,"maximum":200,"default":50}},{"name":"offset","in":"query","description":"Number of items to skip.","schema":{"type":"integer","minimum":0,"default":0}}],"responses":{"200":{"description":"A page of deployments.","headers":{"RateLimit-Policy":{"$ref":"#/components/headers/RateLimitPolicy"},"RateLimit-Limit":{"$ref":"#/components/headers/RateLimitLimit"},"RateLimit-Remaining":{"$ref":"#/components/headers/RateLimitRemaining"},"RateLimit-Reset":{"$ref":"#/components/headers/RateLimitReset"}},"content":{"application/json":{"schema":{"type":"object","required":["success","data","limit","offset"],"properties":{"success":{"type":"boolean","const":true},"data":{"type":"array","items":{"$ref":"#/components/schemas/Deployment"}},"limit":{"type":"integer","description":"The page size that was applied."},"offset":{"type":"integer","description":"The offset that was applied."}}},"example":{"success":true,"data":[{"id":"0e8a4c71-9b2d-4f36-a5e1-7c3d9b2f6a10","finn_id":"8f14e45f-ceea-467a-9f6a-1c0e5b2a77d1","audience_id":"c2d9e7a4-51b8-4e0f-8a63-9f1d2b7c5e48","phone_number_id":"6b1f0c2e-3d4a-4f5b-9c8d-2e7a1b9f4c03","call_type":"outbound","status":"in_progress","from_number":"+14155550123","number_pool":null,"max_parallel_calls":1,"schedule":null,"total_calls":120,"completed_calls":37,"recording_enabled":false,"created_at":"2026-10-02T10:15:00.000Z","updated_at":"2026-10-02T10:41:12.000Z"}],"limit":50,"offset":0}}}},"400":{"description":"The request failed validation. `message` names the problem.","content":{"application/json":{"schema":{"allOf":[{"$ref":"#/components/schemas/Error"},{"type":"object","properties":{"error":{"type":"string","enum":["invalid_request"]}}}]},"examples":{"invalid_request":{"summary":"invalid_request","value":{"error":"invalid_request","message":"call_type must be inbound or outbound"}}}}}},"401":{"$ref":"#/components/responses/Unauthorized"},"429":{"$ref":"#/components/responses/RateLimited"},"500":{"$ref":"#/components/responses/InternalError"}},"x-required-scope":"voice:read","x-legacy-scope":"voice"},"post":{"tags":["Deployments"],"operationId":"createOutboundDeployment","x-mcp-tool":"start_campaign","summary":"Create an outbound deployment","description":"Dials every contact in an audience from one of your numbers. **This starts real, billed calls immediately.** Deploying sets the Finn's status to `live`.\n\nBefore creating the deployment, Finn checks that the wallet holds at least half the audience's size in one-minute calls (minimum one). The Finn must not already have a live deployment, the number must not be held by another deployment that has not ended, and the audience must have at least one contact and must not be dynamic.\n\nWith `max_parallel_calls` above 1, the returned `phone_number_id` and `from_number` are `null` and the number is in `number_pool`. In rare cases `data` contains only `id`; read the full object with `GET /deployments/{id}`.\n\nScheduling for later is not available: a request with a `schedule` field returns `422 schedule_not_supported`. Send an `Idempotency-Key` header so a retry after a timeout or a `5xx` returns the first response instead of starting the deployment again.\n\n**Rate limit:** 10 requests per minute per API key, shared by `POST /deployments` and `POST /deployments/inbound`.","requestBody":{"required":true,"content":{"application/json":{"schema":{"$ref":"#/components/schemas/CreateOutboundDeploymentRequest"},"example":{"finn_id":"8f14e45f-ceea-467a-9f6a-1c0e5b2a77d1","phone_number_id":"0d3f1a7c-9b52-4e86-a1c4-7f2d6b8e3a90","audience_id":"c2d9e7a4-51b8-4e0f-8a63-9f1d2b7c5e48","max_parallel_calls":1,"recording_enabled":true,"schedule":{"date":"2026-11-03","start_time":"10:00","timezone":"Asia/Kolkata"}}}}},"responses":{"201":{"description":"The created deployment.","headers":{"RateLimit-Policy":{"$ref":"#/components/headers/RateLimitPolicy"},"RateLimit-Limit":{"$ref":"#/components/headers/RateLimitLimit"},"RateLimit-Remaining":{"$ref":"#/components/headers/RateLimitRemaining"},"RateLimit-Reset":{"$ref":"#/components/headers/RateLimitReset"},"Idempotent-Replayed":{"$ref":"#/components/headers/IdempotentReplayed"}},"content":{"application/json":{"schema":{"type":"object","required":["success","data"],"properties":{"success":{"type":"boolean","const":true},"data":{"$ref":"#/components/schemas/Deployment"}}},"example":{"success":true,"data":{"id":"0e8a4c71-9b2d-4f36-a5e1-7c3d9b2f6a10","finn_id":"8f14e45f-ceea-467a-9f6a-1c0e5b2a77d1","audience_id":"c2d9e7a4-51b8-4e0f-8a63-9f1d2b7c5e48","phone_number_id":"6b1f0c2e-3d4a-4f5b-9c8d-2e7a1b9f4c03","call_type":"outbound","status":"pending","from_number":"+14155550123","number_pool":null,"max_parallel_calls":1,"schedule":null,"total_calls":0,"completed_calls":0,"recording_enabled":false,"created_at":"2026-10-02T10:15:00.000Z","updated_at":"2026-10-02T10:15:00.000Z"}}}}},"202":{"$ref":"#/components/responses/PendingApproval"},"400":{"description":"The request failed validation. `message` names the problem. Also returned when `Idempotency-Key` is not 1 to 255 visible ASCII characters.","content":{"application/json":{"schema":{"allOf":[{"$ref":"#/components/schemas/Error"},{"type":"object","properties":{"error":{"type":"string","enum":["invalid_request"]}}}]},"examples":{"invalid_request":{"summary":"invalid_request","value":{"error":"invalid_request","message":"max_parallel_calls must be an integer from 1 to 5"}}}}}},"401":{"$ref":"#/components/responses/Unauthorized"},"402":{"description":"The wallet does not cover half the audience in one-minute calls.","content":{"application/json":{"schema":{"allOf":[{"$ref":"#/components/schemas/Error"},{"type":"object","properties":{"error":{"type":"string","enum":["insufficient_credits"]}}}]},"examples":{"insufficient_credits":{"summary":"insufficient_credits","value":{"error":"insufficient_credits","message":"Insufficient wallet credits. Top up in the Finn dashboard (see action_url).","action_url":"https://www.hirefinn.ai/dashboard/settings?tab=billing"}}}}}},"404":{"description":"The Finn, number or audience is not in your organization, or the number is pending or cancelled.","content":{"application/json":{"schema":{"allOf":[{"$ref":"#/components/schemas/Error"},{"type":"object","properties":{"error":{"type":"string","enum":["finn_not_found","phone_number_not_found","audience_not_found"]}}}]},"examples":{"finn_not_found":{"summary":"finn_not_found","value":{"error":"finn_not_found","message":"No Finn with that id in this organization"}},"phone_number_not_found":{"summary":"phone_number_not_found","value":{"error":"phone_number_not_found","message":"No usable phone number with that id in this organization"}},"audience_not_found":{"summary":"audience_not_found","value":{"error":"audience_not_found","message":"No audience with that id in this organization"}}}}}},"409":{"description":"The request conflicts with current state. `deployment_already_active` includes the live `deployment_id`. `idempotency_in_progress`: a request with the same `Idempotency-Key` is still running; retry after `Retry-After` seconds to get its result.","content":{"application/json":{"schema":{"allOf":[{"$ref":"#/components/schemas/Error"},{"type":"object","properties":{"error":{"type":"string","enum":["deployment_already_active","phone_number_in_use","phone_number_isolated","api_key_owner_required","conflict"]}}}]},"examples":{"deployment_already_active":{"summary":"deployment_already_active","value":{"error":"deployment_already_active","message":"This Finn already has a live deployment. Stop it before starting another.","deployment_id":"0e8a4c71-9b2d-4f36-a5e1-7c3d9b2f6a10"}},"phone_number_in_use":{"summary":"phone_number_in_use","value":{"error":"phone_number_in_use","message":"Phone number +14155550123 is already allocated to a live deployment. Please choose a different number or end the other deployment first."}},"phone_number_isolated":{"summary":"phone_number_isolated","value":{"error":"phone_number_isolated","message":"This number is isolated after a spam-flag check. Release it in the dashboard first."}},"api_key_owner_required":{"summary":"api_key_owner_required","value":{"error":"api_key_owner_required","message":"The user who created this API key is no longer a member of this organization. Create a new key to deploy.","action_url":"https://www.hirefinn.ai/dashboard/settings?tab=integrations"}},"conflict":{"summary":"conflict","value":{"error":"conflict","message":"Conflict"}}}}}},"422":{"description":"The request is valid but cannot be carried out. Or `idempotency_key_reused`: this `Idempotency-Key` was already used with a different request.","content":{"application/json":{"schema":{"allOf":[{"$ref":"#/components/schemas/Error"},{"type":"object","properties":{"error":{"type":"string","enum":["schedule_not_supported","audience_not_ready","audience_not_supported","deployment_rejected"]}}}]},"examples":{"schedule_not_supported":{"summary":"schedule_not_supported","value":{"error":"schedule_not_supported","message":"Scheduled deployments are not available yet. Omit schedule to start the deployment now."}},"audience_not_ready":{"summary":"audience_not_ready","value":{"error":"audience_not_ready","message":"This audience has no contacts. Add contacts before deploying."}},"audience_not_supported":{"summary":"audience_not_supported","value":{"error":"audience_not_supported","message":"Dynamic audiences cannot be deployed through the API"}},"deployment_rejected":{"summary":"deployment_rejected","value":{"error":"deployment_rejected","message":"Invalid dynamic variables","errors":[]}}}}}},"423":{"$ref":"#/components/responses/AiAppsPaused"},"429":{"$ref":"#/components/responses/RateLimited"},"500":{"$ref":"#/components/responses/InternalError"},"502":{"description":"The deployment could not be created. Check `GET /deployments` before retrying.","content":{"application/json":{"schema":{"allOf":[{"$ref":"#/components/schemas/Error"},{"type":"object","properties":{"error":{"type":"string","enum":["deployment_failed"]}}}]},"examples":{"deployment_failed":{"summary":"deployment_failed","value":{"error":"deployment_failed","message":"Deployment could not be created"}}}}}}},"x-required-scope":"voice:write","x-legacy-scope":"voice","parameters":[{"$ref":"#/components/parameters/IdempotencyKey"}],"x-finn-hold":{"action":"deployment.create","class":"money"}},"parameters":[{"$ref":"#/components/parameters/FinnOrg"},{"$ref":"#/components/parameters/FinnVersion"}]},"/deployments/inbound":{"post":{"tags":["Deployments"],"operationId":"createInboundDeployment","summary":"Create an inbound deployment","description":"Connects a number to a Finn so the Finn answers calls to it. This is how you assign a number to a Finn. A Finn can have one live inbound deployment, and a number can serve one Finn. Inbound calls are billed as they happen; creating the deployment does not check the balance.\n\nSend an `Idempotency-Key` header so a retry after a timeout or a `5xx` returns the first response instead of starting the deployment again.\n\n**Rate limit:** 10 requests per minute per API key, shared by `POST /deployments` and `POST /deployments/inbound`.\n\n**Approval hold (off by default):** where the organization has `v1_hold_inbound_sales_runs` on, this request can be held for a person to approve, like a campaign launch: it answers 202 `PendingApproval` and nothing runs until it is approved. A member's token cannot call this endpoint; an org key is held where the organization's hold policy holds org keys for `deployment.inbound`.","requestBody":{"required":true,"content":{"application/json":{"schema":{"$ref":"#/components/schemas/CreateInboundDeploymentRequest"},"example":{"finn_id":"8f14e45f-ceea-467a-9f6a-1c0e5b2a77d1","phone_number_id":"0d3f1a7c-9b52-4e86-a1c4-7f2d6b8e3a90","recording_enabled":true}}}},"responses":{"201":{"description":"The created deployment.","headers":{"RateLimit-Policy":{"$ref":"#/components/headers/RateLimitPolicy"},"RateLimit-Limit":{"$ref":"#/components/headers/RateLimitLimit"},"RateLimit-Remaining":{"$ref":"#/components/headers/RateLimitRemaining"},"RateLimit-Reset":{"$ref":"#/components/headers/RateLimitReset"},"Idempotent-Replayed":{"$ref":"#/components/headers/IdempotentReplayed"}},"content":{"application/json":{"schema":{"type":"object","required":["success","data"],"properties":{"success":{"type":"boolean","const":true},"data":{"$ref":"#/components/schemas/Deployment"}}},"example":{"success":true,"data":{"id":"5d2c8f19-7a4e-4b03-b6d1-3e9f0a7c2b84","finn_id":"8f14e45f-ceea-467a-9f6a-1c0e5b2a77d1","audience_id":null,"phone_number_id":"6b1f0c2e-3d4a-4f5b-9c8d-2e7a1b9f4c03","call_type":"inbound","status":"active","from_number":"+14155550123","number_pool":null,"max_parallel_calls":1,"schedule":null,"total_calls":0,"completed_calls":0,"recording_enabled":false,"created_at":"2026-10-02T10:15:00.000Z","updated_at":"2026-10-02T10:15:00.000Z"}}}}},"202":{"$ref":"#/components/responses/PendingApproval"},"400":{"description":"The request failed validation. `message` names the problem. Also returned when `Idempotency-Key` is not 1 to 255 visible ASCII characters.","content":{"application/json":{"schema":{"allOf":[{"$ref":"#/components/schemas/Error"},{"type":"object","properties":{"error":{"type":"string","enum":["invalid_request"]}}}]},"examples":{"invalid_request":{"summary":"invalid_request","value":{"error":"invalid_request","message":"phone_number_id must be a valid UUID"}}}}}},"401":{"$ref":"#/components/responses/Unauthorized"},"404":{"description":"The Finn or number is not in your organization, or the number is pending or cancelled.","content":{"application/json":{"schema":{"allOf":[{"$ref":"#/components/schemas/Error"},{"type":"object","properties":{"error":{"type":"string","enum":["finn_not_found","phone_number_not_found"]}}}]},"examples":{"finn_not_found":{"summary":"finn_not_found","value":{"error":"finn_not_found","message":"No Finn with that id in this organization"}},"phone_number_not_found":{"summary":"phone_number_not_found","value":{"error":"phone_number_not_found","message":"No usable phone number with that id in this organization"}}}}}},"409":{"description":"The request conflicts with current state. `deployment_already_active` includes the live `deployment_id`, or `null` if it could not be determined. `idempotency_in_progress`: a request with the same `Idempotency-Key` is still running; retry after `Retry-After` seconds to get its result.","content":{"application/json":{"schema":{"allOf":[{"$ref":"#/components/schemas/Error"},{"type":"object","properties":{"error":{"type":"string","enum":["deployment_already_active","phone_number_in_use","phone_number_isolated","api_key_owner_required","conflict"]}}}]},"examples":{"deployment_already_active":{"summary":"deployment_already_active","value":{"error":"deployment_already_active","message":"This Finn already has a live deployment. Stop it before starting another.","deployment_id":"5d2c8f19-7a4e-4b03-b6d1-3e9f0a7c2b84"}},"phone_number_in_use":{"summary":"phone_number_in_use","value":{"error":"phone_number_in_use","message":"This number already has a live inbound deployment for another Finn. Stop it before deploying."}},"phone_number_isolated":{"summary":"phone_number_isolated","value":{"error":"phone_number_isolated","message":"This number is isolated after a spam-flag check. Release it in the dashboard first."}},"api_key_owner_required":{"summary":"api_key_owner_required","value":{"error":"api_key_owner_required","message":"The user who created this API key is no longer a member of this organization. Create a new key to deploy.","action_url":"https://www.hirefinn.ai/dashboard/settings?tab=integrations"}},"conflict":{"summary":"conflict","value":{"error":"conflict","message":"Conflict"}}}}}},"422":{"description":"The deployment was rejected. `errors` lists the reasons when available. Or `idempotency_key_reused`: this `Idempotency-Key` was already used with a different request.","content":{"application/json":{"schema":{"allOf":[{"$ref":"#/components/schemas/Error"},{"type":"object","properties":{"error":{"type":"string","enum":["deployment_rejected"]}}}]},"examples":{"deployment_rejected":{"summary":"deployment_rejected","value":{"error":"deployment_rejected","message":"Deployment was rejected"}}}}}},"423":{"$ref":"#/components/responses/AiAppsPaused"},"429":{"$ref":"#/components/responses/RateLimited"},"500":{"$ref":"#/components/responses/InternalError"},"502":{"description":"The deployment could not be created. Check `GET /deployments` before retrying.","content":{"application/json":{"schema":{"allOf":[{"$ref":"#/components/schemas/Error"},{"type":"object","properties":{"error":{"type":"string","enum":["deployment_failed"]}}}]},"examples":{"deployment_failed":{"summary":"deployment_failed","value":{"error":"deployment_failed","message":"Deployment could not be created"}}}}}}},"x-required-scope":"voice:write","x-legacy-scope":"voice","parameters":[{"$ref":"#/components/parameters/IdempotencyKey"}],"x-finn-hold":{"action":"deployment.inbound","class":"money","feature":"v1_hold_inbound_sales_runs"}},"parameters":[{"$ref":"#/components/parameters/FinnOrg"},{"$ref":"#/components/parameters/FinnVersion"}]},"/deployments/{id}":{"get":{"tags":["Deployments"],"operationId":"retrieveDeployment","x-mcp-tool":"get_campaign","summary":"Retrieve a deployment","description":"Returns one deployment. Poll it to follow `total_calls` and `completed_calls`.\n\n**Rate limit:** 300 requests per minute per API key, shared with `GET /deployments`, `GET /deployments/{id}` and `GET /wallet/transactions`.","parameters":[{"name":"id","in":"path","required":true,"description":"The deployment ID.","schema":{"type":"string","format":"uuid"},"example":"0e8a4c71-9b2d-4f36-a5e1-7c3d9b2f6a10"}],"responses":{"200":{"description":"The deployment.","headers":{"RateLimit-Policy":{"$ref":"#/components/headers/RateLimitPolicy"},"RateLimit-Limit":{"$ref":"#/components/headers/RateLimitLimit"},"RateLimit-Remaining":{"$ref":"#/components/headers/RateLimitRemaining"},"RateLimit-Reset":{"$ref":"#/components/headers/RateLimitReset"}},"content":{"application/json":{"schema":{"type":"object","required":["success","data"],"properties":{"success":{"type":"boolean","const":true},"data":{"$ref":"#/components/schemas/Deployment"}}},"example":{"success":true,"data":{"id":"0e8a4c71-9b2d-4f36-a5e1-7c3d9b2f6a10","finn_id":"8f14e45f-ceea-467a-9f6a-1c0e5b2a77d1","audience_id":"c2d9e7a4-51b8-4e0f-8a63-9f1d2b7c5e48","phone_number_id":"6b1f0c2e-3d4a-4f5b-9c8d-2e7a1b9f4c03","call_type":"outbound","status":"in_progress","from_number":"+14155550123","number_pool":null,"max_parallel_calls":1,"schedule":null,"total_calls":120,"completed_calls":37,"recording_enabled":false,"created_at":"2026-10-02T10:15:00.000Z","updated_at":"2026-10-02T10:41:12.000Z"}}}}},"400":{"description":"The request failed validation. `message` names the problem.","content":{"application/json":{"schema":{"allOf":[{"$ref":"#/components/schemas/Error"},{"type":"object","properties":{"error":{"type":"string","enum":["invalid_request"]}}}]},"examples":{"invalid_request":{"summary":"invalid_request","value":{"error":"invalid_request","message":"id must be a valid UUID"}}}}}},"401":{"$ref":"#/components/responses/Unauthorized"},"404":{"description":"No deployment with that ID in your organization.","content":{"application/json":{"schema":{"allOf":[{"$ref":"#/components/schemas/Error"},{"type":"object","properties":{"error":{"type":"string","enum":["deployment_not_found"]}}}]},"examples":{"deployment_not_found":{"summary":"deployment_not_found","value":{"error":"deployment_not_found","message":"No deployment with that id in this organization"}}}}}},"429":{"$ref":"#/components/responses/RateLimited"},"500":{"$ref":"#/components/responses/InternalError"}},"x-required-scope":"voice:read","x-legacy-scope":"voice"},"parameters":[{"$ref":"#/components/parameters/FinnOrg"},{"$ref":"#/components/parameters/FinnVersion"}]},"/deployments/{id}/stop":{"post":{"tags":["Deployments"],"operationId":"stopDeployment","summary":"Stop an outbound deployment","description":"Stops dialing, frees the number and sets the Finn back to `draft` (even if it still has a live inbound deployment started in the dashboard). Works on deployments that have not finished. A stopped deployment cannot be restarted; create a new one. Inbound deployments are stopped with `POST /deployments/inbound/{finn_id}/stop`.\n\n**Rate limit:** 30 requests per minute per API key, shared by both stop endpoints.","parameters":[{"name":"id","in":"path","required":true,"description":"The deployment ID.","schema":{"type":"string","format":"uuid"},"example":"0e8a4c71-9b2d-4f36-a5e1-7c3d9b2f6a10"}],"responses":{"200":{"description":"The deployment was stopped.","headers":{"RateLimit-Policy":{"$ref":"#/components/headers/RateLimitPolicy"},"RateLimit-Limit":{"$ref":"#/components/headers/RateLimitLimit"},"RateLimit-Remaining":{"$ref":"#/components/headers/RateLimitRemaining"},"RateLimit-Reset":{"$ref":"#/components/headers/RateLimitReset"}},"content":{"application/json":{"schema":{"type":"object","required":["success","data"],"properties":{"success":{"type":"boolean","const":true},"data":{"$ref":"#/components/schemas/StoppedDeployment"}}},"example":{"success":true,"data":{"id":"0e8a4c71-9b2d-4f36-a5e1-7c3d9b2f6a10","status":"stopped"}}}}},"400":{"description":"The request failed validation. `message` names the problem.","content":{"application/json":{"schema":{"allOf":[{"$ref":"#/components/schemas/Error"},{"type":"object","properties":{"error":{"type":"string","enum":["invalid_request"]}}}]},"examples":{"invalid_request":{"summary":"invalid_request","value":{"error":"invalid_request","message":"id must be a valid UUID"}}}}}},"401":{"$ref":"#/components/responses/Unauthorized"},"404":{"description":"No deployment with that ID in your organization.","content":{"application/json":{"schema":{"allOf":[{"$ref":"#/components/schemas/Error"},{"type":"object","properties":{"error":{"type":"string","enum":["deployment_not_found"]}}}]},"examples":{"deployment_not_found":{"summary":"deployment_not_found","value":{"error":"deployment_not_found","message":"No deployment with that id in this organization"}}}}}},"409":{"description":"The deployment is inbound, or has already finished.","content":{"application/json":{"schema":{"allOf":[{"$ref":"#/components/schemas/Error"},{"type":"object","properties":{"error":{"type":"string","enum":["use_inbound_stop","deployment_not_active"]}}}]},"examples":{"use_inbound_stop":{"summary":"use_inbound_stop","value":{"error":"use_inbound_stop","message":"Stop inbound deployments with POST /api/v1/deployments/inbound/{finn_id}/stop"}},"deployment_not_active":{"summary":"deployment_not_active","value":{"error":"deployment_not_active","message":"Deployment is already completed"}}}}}},"423":{"$ref":"#/components/responses/AiAppsPaused"},"429":{"$ref":"#/components/responses/RateLimited"},"500":{"$ref":"#/components/responses/InternalError"},"502":{"description":"The deployment could not be stopped. Retry.","content":{"application/json":{"schema":{"allOf":[{"$ref":"#/components/schemas/Error"},{"type":"object","properties":{"error":{"type":"string","enum":["stop_failed"]}}}]},"examples":{"stop_failed":{"summary":"stop_failed","value":{"error":"stop_failed","message":"Deployment could not be stopped"}}}}}}},"x-required-scope":"voice:write","x-legacy-scope":"voice"},"parameters":[{"$ref":"#/components/parameters/FinnOrg"},{"$ref":"#/components/parameters/FinnVersion"}]},"/deployments/inbound/{finn_id}/stop":{"post":{"tags":["Deployments"],"operationId":"stopInboundDeployment","summary":"Stop an inbound deployment","description":"Takes the **Finn's** ID, not the deployment's, and stops that Finn's live inbound deployment. The number stops routing calls to the Finn and is freed, but stays rented; release it with `DELETE /phone_numbers/{id}` if you no longer need it. The Finn returns to `draft` if it has no other live deployment.\n\n**Rate limit:** 30 requests per minute per API key, shared by both stop endpoints.","parameters":[{"name":"finn_id","in":"path","required":true,"description":"The Finn ID.","schema":{"type":"string","format":"uuid"},"example":"8f14e45f-ceea-467a-9f6a-1c0e5b2a77d1"}],"responses":{"200":{"description":"The inbound deployment was stopped.","headers":{"RateLimit-Policy":{"$ref":"#/components/headers/RateLimitPolicy"},"RateLimit-Limit":{"$ref":"#/components/headers/RateLimitLimit"},"RateLimit-Remaining":{"$ref":"#/components/headers/RateLimitRemaining"},"RateLimit-Reset":{"$ref":"#/components/headers/RateLimitReset"}},"content":{"application/json":{"schema":{"type":"object","required":["success","data"],"properties":{"success":{"type":"boolean","const":true},"data":{"$ref":"#/components/schemas/StoppedInboundDeployment"}}},"example":{"success":true,"data":{"id":"5d2c8f19-7a4e-4b03-b6d1-3e9f0a7c2b84","finn_id":"8f14e45f-ceea-467a-9f6a-1c0e5b2a77d1","status":"stopped"}}}}},"400":{"description":"The request failed validation. `message` names the problem.","content":{"application/json":{"schema":{"allOf":[{"$ref":"#/components/schemas/Error"},{"type":"object","properties":{"error":{"type":"string","enum":["invalid_request"]}}}]},"examples":{"invalid_request":{"summary":"invalid_request","value":{"error":"invalid_request","message":"finn_id must be a valid UUID"}}}}}},"401":{"$ref":"#/components/responses/Unauthorized"},"404":{"description":"No Finn with that ID in your organization, or the Finn has no live inbound deployment.","content":{"application/json":{"schema":{"allOf":[{"$ref":"#/components/schemas/Error"},{"type":"object","properties":{"error":{"type":"string","enum":["finn_not_found","deployment_not_found"]}}}]},"examples":{"finn_not_found":{"summary":"finn_not_found","value":{"error":"finn_not_found","message":"No Finn with that id in this organization"}},"deployment_not_found":{"summary":"deployment_not_found","value":{"error":"deployment_not_found","message":"This Finn has no live inbound deployment"}}}}}},"423":{"$ref":"#/components/responses/AiAppsPaused"},"429":{"$ref":"#/components/responses/RateLimited"},"500":{"$ref":"#/components/responses/InternalError"},"502":{"description":"The deployment could not be stopped. Retry.","content":{"application/json":{"schema":{"allOf":[{"$ref":"#/components/schemas/Error"},{"type":"object","properties":{"error":{"type":"string","enum":["stop_failed"]}}}]},"examples":{"stop_failed":{"summary":"stop_failed","value":{"error":"stop_failed","message":"Deployment could not be stopped"}}}}}}},"x-required-scope":"voice:write","x-legacy-scope":"voice"},"parameters":[{"$ref":"#/components/parameters/FinnOrg"},{"$ref":"#/components/parameters/FinnVersion"}]},"/wallet":{"get":{"tags":["Wallet"],"operationId":"retrieveWallet","summary":"Retrieve the wallet balance","description":"Returns your organization's credit balance. Calls, number rentals and other usage are debited from it; top-ups are made in the dashboard. If your organization has no wallet yet, `credits` is `0`, `currency` and `price_per_credit` are `null` and `low_balance` is `true`.\n\n**Rate limit:** 300 requests per minute per API key, shared with `GET /calls/{call_uuid}`, `GET /finns`, `GET /finns/{finn_id}` and `GET /wallet`.","responses":{"200":{"description":"The wallet balance.","headers":{"RateLimit-Policy":{"$ref":"#/components/headers/RateLimitPolicy"},"RateLimit-Limit":{"$ref":"#/components/headers/RateLimitLimit"},"RateLimit-Remaining":{"$ref":"#/components/headers/RateLimitRemaining"},"RateLimit-Reset":{"$ref":"#/components/headers/RateLimitReset"}},"content":{"application/json":{"schema":{"type":"object","required":["success","data"],"properties":{"success":{"type":"boolean","const":true},"data":{"$ref":"#/components/schemas/Wallet"}}},"example":{"success":true,"data":{"credits":4175,"currency":"USD","price_per_credit":0.1,"monetary_value":417.5,"low_balance":false}}}}},"401":{"$ref":"#/components/responses/Unauthorized"},"429":{"$ref":"#/components/responses/RateLimited"},"500":{"$ref":"#/components/responses/InternalError"}},"x-required-scope":"voice:read","x-legacy-scope":"voice"},"parameters":[{"$ref":"#/components/parameters/FinnOrg"},{"$ref":"#/components/parameters/FinnVersion"}]},"/wallet/transactions":{"get":{"tags":["Wallet"],"operationId":"listWalletTransactions","summary":"List wallet transactions","description":"Returns the wallet ledger, newest first. Every debit and credit is one entry. This is the record of what was charged. There is no total count: page with `offset` until a page has fewer than `limit` items.\n\n**Rate limit:** 300 requests per minute per API key, shared with `GET /deployments`, `GET /deployments/{id}` and `GET /wallet/transactions`.","parameters":[{"name":"types","in":"query","description":"Comma-separated transaction types to include: up to 20 values of lowercase letters and `_`.","schema":{"type":"string"}},{"name":"limit","in":"query","description":"Page size, 1 to 200. Out-of-range values are clamped rather than rejected.","schema":{"type":"integer","minimum":1,"maximum":200,"default":50}},{"name":"offset","in":"query","description":"Number of items to skip.","schema":{"type":"integer","minimum":0,"default":0}}],"responses":{"200":{"description":"A page of ledger entries.","headers":{"RateLimit-Policy":{"$ref":"#/components/headers/RateLimitPolicy"},"RateLimit-Limit":{"$ref":"#/components/headers/RateLimitLimit"},"RateLimit-Remaining":{"$ref":"#/components/headers/RateLimitRemaining"},"RateLimit-Reset":{"$ref":"#/components/headers/RateLimitReset"}},"content":{"application/json":{"schema":{"type":"object","required":["success","data","limit","offset"],"properties":{"success":{"type":"boolean","const":true},"data":{"type":"array","items":{"$ref":"#/components/schemas/WalletTransaction"}},"limit":{"type":"integer","description":"The page size that was applied."},"offset":{"type":"integer","description":"The offset that was applied."}}},"example":{"success":true,"data":[{"id":"48213","type":"debit_call_answered","credits_delta":-2,"new_balance":4175,"call_uuid":"b7e3c9a2-4d1f-4e8b-9a6c-2f5d8e1b3c70","created_at":"2026-10-02T10:44:12.000Z"}],"limit":50,"offset":0}}}},"400":{"description":"The request failed validation. `message` names the problem.","content":{"application/json":{"schema":{"allOf":[{"$ref":"#/components/schemas/Error"},{"type":"object","properties":{"error":{"type":"string","enum":["invalid_request"]}}}]},"examples":{"invalid_request":{"summary":"invalid_request","value":{"error":"invalid_request","message":"types must be up to 20 comma-separated values of a-z and _"}}}}}},"401":{"$ref":"#/components/responses/Unauthorized"},"429":{"$ref":"#/components/responses/RateLimited"},"500":{"$ref":"#/components/responses/InternalError"}},"x-required-scope":"voice:read","x-legacy-scope":"voice"},"parameters":[{"$ref":"#/components/parameters/FinnOrg"},{"$ref":"#/components/parameters/FinnVersion"}]},"/deployments/{id}/analytics":{"get":{"tags":["Deployments"],"operationId":"getDeploymentAnalytics","x-mcp-tool":"get_campaign","summary":"Get deployment analytics","description":"Aggregate results for one deployment: how many calls were placed, how many were\nanswered, how long they ran, and how they ended.\n\nComputed from the call records at request time, so it is correct for a deployment that\nis still running.\n\n**Rate limit:** 300 requests per minute per API key.","parameters":[{"name":"id","in":"path","required":true,"schema":{"type":"string","format":"uuid"}}],"responses":{"200":{"description":"The deployment analytics.","headers":{"RateLimit-Policy":{"$ref":"#/components/headers/RateLimitPolicy"},"RateLimit-Limit":{"$ref":"#/components/headers/RateLimitLimit"},"RateLimit-Remaining":{"$ref":"#/components/headers/RateLimitRemaining"},"RateLimit-Reset":{"$ref":"#/components/headers/RateLimitReset"}},"content":{"application/json":{"schema":{"type":"object","required":["success","data"],"properties":{"success":{"type":"boolean","const":true},"data":{"$ref":"#/components/schemas/DeploymentAnalytics"}}},"example":{"success":true,"data":{"deployment_id":"6b1f0c2e-3d4a-4f5b-9c8d-2e7a1b9f4c03","calls":{"total":120,"answered":86,"completed":80,"unanswered":28,"voicemail":6,"successful":54},"rates":{"answer_rate_percent":71.67,"success_rate_percent":45,"voicemail_rate_percent":5},"duration":{"total_seconds":7420,"average_seconds":86},"status_breakdown":[{"status":"completed","count":80},{"status":"no-answer","count":22},{"status":"voicemail","count":6},{"status":"busy","count":6},{"status":"failed","count":6}],"sentiment_breakdown":[{"sentiment":"Positive","count":41},{"sentiment":"Neutral","count":33},{"sentiment":"Negative","count":12}],"first_call_at":"2026-10-08T10:00:00.000Z","last_call_at":"2026-10-08T12:31:04.000Z"}}}}},"400":{"description":"`id` is not a valid UUID.","content":{"application/json":{"schema":{"$ref":"#/components/schemas/Error"}}}},"401":{"$ref":"#/components/responses/Unauthorized"},"404":{"description":"No deployment with that id in this organization.","content":{"application/json":{"schema":{"$ref":"#/components/schemas/Error"}}}},"429":{"$ref":"#/components/responses/RateLimited"}},"x-required-scope":"voice:read","x-legacy-scope":"voice"},"parameters":[{"$ref":"#/components/parameters/FinnOrg"},{"$ref":"#/components/parameters/FinnVersion"}]},"/finns/{finn_id}/post_call_analysis":{"get":{"tags":["Finns"],"operationId":"getFinnPostCallAnalysis","summary":"Get a Finn's post-call analysis","description":"The Finn's `post_call_analysis` questions with the distribution of answers across its\ncalls — what the questions are for. Per-call answers are on `GET /calls/{call_uuid}`.\n\nNarrow the window with `from` and `to`.\n\n**Rate limit:** 300 requests per minute per API key.","parameters":[{"name":"finn_id","in":"path","required":true,"schema":{"type":"string","format":"uuid"}},{"name":"from","in":"query","description":"Only answers extracted at or after this ISO-8601 time.","schema":{"type":"string","format":"date-time"}},{"name":"to","in":"query","description":"Only answers extracted at or before this ISO-8601 time.","schema":{"type":"string","format":"date-time"}}],"responses":{"200":{"description":"The aggregated answers.","headers":{"RateLimit-Policy":{"$ref":"#/components/headers/RateLimitPolicy"},"RateLimit-Limit":{"$ref":"#/components/headers/RateLimitLimit"},"RateLimit-Remaining":{"$ref":"#/components/headers/RateLimitRemaining"},"RateLimit-Reset":{"$ref":"#/components/headers/RateLimitReset"}},"content":{"application/json":{"schema":{"type":"object","required":["success","data"],"properties":{"success":{"type":"boolean","const":true},"data":{"$ref":"#/components/schemas/FinnPostCallAnalysis"}}},"example":{"success":true,"data":{"finn_id":"8f14e45f-ceea-467a-9f6a-1c0e5b2a77d1","calls_analyzed":80,"questions":[{"question_name":"Wants to renew","question_type":"Yes/No","total_answers":80,"needs_review":3,"answers":[{"answer":"Yes","count":54},{"answer":"No","count":26}]},{"question_name":"Reason for declining","question_type":"Selector","total_answers":26,"needs_review":1,"answers":[{"answer":"Too expensive","count":14},{"answer":"Switching provider","count":7},{"answer":"No longer needed","count":5}]}]}}}}},"400":{"description":"`finn_id` is not a valid UUID, or a timestamp could not be parsed.","content":{"application/json":{"schema":{"$ref":"#/components/schemas/Error"}}}},"401":{"$ref":"#/components/responses/Unauthorized"},"404":{"description":"No Finn with that id in this organization.","content":{"application/json":{"schema":{"$ref":"#/components/schemas/Error"}}}},"429":{"$ref":"#/components/responses/RateLimited"}},"x-required-scope":"voice:read","x-legacy-scope":"voice"},"parameters":[{"$ref":"#/components/parameters/FinnOrg"},{"$ref":"#/components/parameters/FinnVersion"}]},"/do_not_call":{"get":{"tags":["Do not call"],"operationId":"listDoNotCall","summary":"List do-not-call numbers","description":"Your organization's do-not-call list, one entry per number, newest first. Finn will not call a listed number from any deployment or single call.\n\n**Rate limit:** 300 requests per minute per API key, shared with the other do-not-call operations and the API's other reads (`GET /calls/{call_uuid}`, `GET /finns`, `GET /finns/{finn_id}`, `GET /wallet`).","parameters":[{"name":"limit","in":"query","schema":{"type":"integer","minimum":1,"maximum":200,"default":50}},{"name":"offset","in":"query","schema":{"type":"integer","minimum":0,"default":0}}],"responses":{"200":{"description":"A page of the list.","headers":{"RateLimit-Policy":{"$ref":"#/components/headers/RateLimitPolicy"},"RateLimit-Limit":{"$ref":"#/components/headers/RateLimitLimit"},"RateLimit-Remaining":{"$ref":"#/components/headers/RateLimitRemaining"},"RateLimit-Reset":{"$ref":"#/components/headers/RateLimitReset"}},"content":{"application/json":{"schema":{"type":"object","required":["success","data","total","limit","offset"],"properties":{"success":{"type":"boolean","const":true},"data":{"type":"array","items":{"$ref":"#/components/schemas/DoNotCallEntry"}},"total":{"type":"integer"},"limit":{"type":"integer"},"offset":{"type":"integer"}}}}}},"401":{"$ref":"#/components/responses/Unauthorized"},"429":{"$ref":"#/components/responses/RateLimited"},"500":{"$ref":"#/components/responses/InternalError"}},"x-required-scope":"voice:read","x-legacy-scope":"voice"},"post":{"tags":["Do not call"],"operationId":"addDoNotCall","summary":"Add a number to the do-not-call list","description":"Adds a number with source `api`. Adding a number that is already listed from the API returns `200` with `created: false`.\n\n**Rate limit:** 300 requests per minute per API key, shared with the other do-not-call operations and the API's other reads (`GET /calls/{call_uuid}`, `GET /finns`, `GET /finns/{finn_id}`, `GET /wallet`).","requestBody":{"required":true,"content":{"application/json":{"schema":{"type":"object","required":["phone_number"],"properties":{"phone_number":{"type":"string","description":"6 to 15 digits, optionally starting with `+`.","example":"+14155550123"},"reason":{"type":"string","maxLength":500}}}}}},"responses":{"200":{"description":"Already listed from the API.","headers":{"RateLimit-Policy":{"$ref":"#/components/headers/RateLimitPolicy"},"RateLimit-Limit":{"$ref":"#/components/headers/RateLimitLimit"},"RateLimit-Remaining":{"$ref":"#/components/headers/RateLimitRemaining"},"RateLimit-Reset":{"$ref":"#/components/headers/RateLimitReset"}},"content":{"application/json":{"schema":{"type":"object","required":["success","data","created"],"properties":{"success":{"type":"boolean","const":true},"data":{"$ref":"#/components/schemas/DoNotCallEntry"},"created":{"type":"boolean","const":false}}}}}},"201":{"description":"Added.","headers":{"RateLimit-Policy":{"$ref":"#/components/headers/RateLimitPolicy"},"RateLimit-Limit":{"$ref":"#/components/headers/RateLimitLimit"},"RateLimit-Remaining":{"$ref":"#/components/headers/RateLimitRemaining"},"RateLimit-Reset":{"$ref":"#/components/headers/RateLimitReset"}},"content":{"application/json":{"schema":{"type":"object","required":["success","data","created"],"properties":{"success":{"type":"boolean","const":true},"data":{"$ref":"#/components/schemas/DoNotCallEntry"},"created":{"type":"boolean","const":true}}}}}},"400":{"description":"The number is not 6 to 15 digits.","content":{"application/json":{"schema":{"$ref":"#/components/schemas/Error"},"example":{"error":"invalid_phone_number","message":"phone_number must be 6 to 15 digits, optionally starting with +"}}}},"401":{"$ref":"#/components/responses/Unauthorized"},"423":{"$ref":"#/components/responses/AiAppsPaused"},"429":{"$ref":"#/components/responses/RateLimited"},"500":{"$ref":"#/components/responses/InternalError"}},"x-required-scope":"voice:write","x-legacy-scope":"voice"},"parameters":[{"$ref":"#/components/parameters/FinnOrg"},{"$ref":"#/components/parameters/FinnVersion"}]},"/do_not_call/{phone}":{"delete":{"tags":["Do not call"],"operationId":"removeDoNotCall","summary":"Remove a number from the do-not-call list","description":"**Scope:** `voice:dnc:write` (older keys: `dnc:write`; includes `voice:write` and `voice:read`). A key with only `voice:write` or `voice`, or one created before scopes, gets `403 insufficient_scope`.\n\nRemoves every source that lists the number. If the person asked not to be contacted (a call or chat opt-out, or one recorded by the rep dialer, Finn Operator or Sales AI), nothing is removed and the response is `409 opted_out`: only an organization admin can remove those, in the dashboard.\n\n**Rate limit:** 300 requests per minute per API key, shared with the other do-not-call operations and the API's other reads (`GET /calls/{call_uuid}`, `GET /finns`, `GET /finns/{finn_id}`, `GET /wallet`).","parameters":[{"name":"phone","in":"path","required":true,"schema":{"type":"string"},"description":"6 to 15 digits, optionally starting with `+`."}],"responses":{"200":{"description":"Removed.","headers":{"RateLimit-Policy":{"$ref":"#/components/headers/RateLimitPolicy"},"RateLimit-Limit":{"$ref":"#/components/headers/RateLimitLimit"},"RateLimit-Remaining":{"$ref":"#/components/headers/RateLimitRemaining"},"RateLimit-Reset":{"$ref":"#/components/headers/RateLimitReset"}},"content":{"application/json":{"schema":{"type":"object","required":["success","removed_sources"],"properties":{"success":{"type":"boolean","const":true},"removed_sources":{"type":"array","items":{"type":"string"}}}}}}},"400":{"description":"The number is not 6 to 15 digits.","content":{"application/json":{"schema":{"$ref":"#/components/schemas/Error"},"example":{"error":"invalid_phone_number","message":"phone must be 6 to 15 digits"}}}},"401":{"$ref":"#/components/responses/Unauthorized"},"403":{"description":"`insufficient_scope`: the key lacks `dnc:write`.","content":{"application/json":{"schema":{"$ref":"#/components/schemas/Error"}}}},"404":{"description":"The number is not listed.","content":{"application/json":{"schema":{"$ref":"#/components/schemas/Error"},"example":{"error":"not_found","message":"That number is not on the do-not-call list"}}}},"409":{"description":"The person asked not to be contacted.","content":{"application/json":{"schema":{"type":"object","required":["error","message","sources"],"properties":{"error":{"type":"string","const":"opted_out"},"message":{"type":"string"},"sources":{"type":"array","items":{"type":"string"}}}}}}},"423":{"$ref":"#/components/responses/AiAppsPaused"},"429":{"$ref":"#/components/responses/RateLimited"},"500":{"$ref":"#/components/responses/InternalError"}},"x-required-scope":"voice:dnc:write","x-legacy-scope":"dnc:write"},"parameters":[{"$ref":"#/components/parameters/FinnOrg"},{"$ref":"#/components/parameters/FinnVersion"}]},"/record_kinds":{"get":{"tags":["Records"],"operationId":"listRecordKinds","summary":"List record kinds","description":"**Scope:** `crm:read`.\n\nThe record kinds of your workspace (for example clients, companies, properties). `enabled` is `false` for a workspace without kinds; `kinds` then holds only the implicit `client` kind.\n\n**Rate limit:** 300 requests per minute per API key, shared by every read in Records, Cases and Approvals.","responses":{"200":{"description":"Kinds.","content":{"application/json":{"schema":{"type":"object","required":["enabled","kinds"],"properties":{"enabled":{"type":"boolean"},"kinds":{"type":"array","items":{"$ref":"#/components/schemas/RecordKind"}}}},"example":{"enabled":false,"kinds":[{"key":"client","label":"Client","label_plural":"Clients","lead_centric":true}]}}},"headers":{"RateLimit-Policy":{"$ref":"#/components/headers/RateLimitPolicy"},"RateLimit-Limit":{"$ref":"#/components/headers/RateLimitLimit"},"RateLimit-Remaining":{"$ref":"#/components/headers/RateLimitRemaining"},"RateLimit-Reset":{"$ref":"#/components/headers/RateLimitReset"}}},"401":{"$ref":"#/components/responses/Unauthorized"},"403":{"$ref":"#/components/responses/InsufficientScope"},"429":{"$ref":"#/components/responses/RateLimited"},"500":{"$ref":"#/components/responses/InternalError"},"503":{"description":"This environment is missing a contacts migration.","content":{"application/json":{"schema":{"allOf":[{"$ref":"#/components/schemas/Error"},{"type":"object","properties":{"error":{"type":"string","enum":["schema_missing"]}}}]}}}}},"x-required-scope":"crm:read"},"parameters":[{"$ref":"#/components/parameters/FinnOrg"},{"$ref":"#/components/parameters/FinnVersion"}]},"/fields":{"get":{"tags":["Records"],"operationId":"listFields","summary":"List fields","description":"**Scope:** `crm:read`.\n\nYour workspace's custom field definitions. Use the keys in `custom_fields` when you create or update a record.\n\n**Rate limit:** 300 requests per minute per API key, shared by every read in Records, Cases and Approvals.","responses":{"200":{"description":"Field definitions.","content":{"application/json":{"schema":{"type":"object","required":["fields"],"properties":{"fields":{"type":"array","items":{"$ref":"#/components/schemas/FieldDefinition"}}}},"example":{"fields":[{"key":"budget","label":"Budget","type":"number","options":[],"position":0,"archived":false}]}}},"headers":{"RateLimit-Policy":{"$ref":"#/components/headers/RateLimitPolicy"},"RateLimit-Limit":{"$ref":"#/components/headers/RateLimitLimit"},"RateLimit-Remaining":{"$ref":"#/components/headers/RateLimitRemaining"},"RateLimit-Reset":{"$ref":"#/components/headers/RateLimitReset"}}},"401":{"$ref":"#/components/responses/Unauthorized"},"403":{"$ref":"#/components/responses/InsufficientScope"},"429":{"$ref":"#/components/responses/RateLimited"},"500":{"$ref":"#/components/responses/InternalError"},"503":{"description":"This environment is missing a contacts migration.","content":{"application/json":{"schema":{"allOf":[{"$ref":"#/components/schemas/Error"},{"type":"object","properties":{"error":{"type":"string","enum":["schema_missing"]}}}]}}}}},"x-required-scope":"crm:read"},"parameters":[{"$ref":"#/components/parameters/FinnOrg"},{"$ref":"#/components/parameters/FinnVersion"}]},"/contacts":{"get":{"tags":["Records"],"operationId":"listRecords","summary":"List records","description":"**Scope:** `crm:read`.\n\nEvery record in your organization, of every kind unless you pass `kind`. Paged with `page` and `page_size`; the response has a total count.\n\n**Rate limit:** 300 requests per minute per API key, shared by every read in Records, Cases and Approvals.","parameters":[{"name":"q","in":"query","description":"Search text (name, number, email, Case ID). At most 200 characters are used.","schema":{"type":"string"}},{"name":"stage","in":"query","description":"Only records at this stage key. Must match `^[a-z][a-z0-9_]{0,39}$`, else `400 Invalid stage`.","schema":{"type":"string"}},{"name":"tag","in":"query","description":"Only records with this tag.","schema":{"type":"string"}},{"name":"kind","in":"query","description":"A record kind key, or `all` (the default).","schema":{"type":"string"}},{"name":"page","in":"query","description":"Page number, from 1.","schema":{"type":"integer","minimum":1,"default":1}},{"name":"page_size","in":"query","description":"Records per page, 1 to 100.","schema":{"type":"integer","minimum":1,"maximum":100,"default":50}}],"responses":{"200":{"description":"A page of records.","content":{"application/json":{"schema":{"type":"object","required":["contacts","totalCount","page","pageSize","totalPages"],"properties":{"contacts":{"type":"array","items":{"$ref":"#/components/schemas/Record"}},"totalCount":{"type":"integer"},"page":{"type":"integer"},"pageSize":{"type":"integer"},"totalPages":{"type":"integer"}}},"example":{"contacts":[{"id":"6f1c2a0e-8d1b-4b7a-9f3e-2c4d5e6f7a8b","phone":"+919800000091","phoneKey":"919800000091","name":"Ravi Rao","fields":{"company":"Acme"},"segments":[],"sources":["api"],"stage":"new","stageManual":false,"assigneeId":null,"assigneeName":null,"tags":["expo"],"notes":[],"customFields":{"budget":12000,"pan":{"redacted":true,"set":true,"hint":"••••4821"}},"leadStatus":"new","stats":{"count":0,"connected":0},"createdAt":"2026-11-03T09:02:44.000Z","updatedAt":"2026-11-03T09:02:44.000Z"}],"totalCount":1,"page":1,"pageSize":50,"totalPages":1}}},"headers":{"RateLimit-Policy":{"$ref":"#/components/headers/RateLimitPolicy"},"RateLimit-Limit":{"$ref":"#/components/headers/RateLimitLimit"},"RateLimit-Remaining":{"$ref":"#/components/headers/RateLimitRemaining"},"RateLimit-Reset":{"$ref":"#/components/headers/RateLimitReset"}}},"400":{"description":"`stage` or `kind` is not valid.","content":{"application/json":{"schema":{"$ref":"#/components/schemas/Error"}}}},"401":{"$ref":"#/components/responses/Unauthorized"},"403":{"$ref":"#/components/responses/InsufficientScope"},"429":{"$ref":"#/components/responses/RateLimited"},"500":{"$ref":"#/components/responses/InternalError"},"503":{"description":"This environment is missing a contacts migration.","content":{"application/json":{"schema":{"allOf":[{"$ref":"#/components/schemas/Error"},{"type":"object","properties":{"error":{"type":"string","enum":["schema_missing"]}}}]}}}}},"x-required-scope":"crm:read"},"post":{"tags":["Records"],"operationId":"createRecord","summary":"Create a record","description":"**Scope:** `crm:write`.\n\nAdds a record with source `api`. The record's audit log and timeline name the key (`API key: <name>`). A number your organization already has returns `409 contact_exists` with its `contact_id`: change that record with `PATCH /contacts/{id}` instead. Sends `contact.created` to webhook endpoints that listen for it.\n\n**Rate limit:** 120 writes per minute per API key, shared by `POST /contacts`, `PATCH /contacts/{id}`, `POST /contacts/{id}/notes` and the three approval decisions.","requestBody":{"required":true,"content":{"application/json":{"schema":{"$ref":"#/components/schemas/CreateRecordRequest"},"example":{"phone":"+919800000091","name":"Ravi Rao","custom_fields":{"budget":12000}}}}},"responses":{"201":{"description":"The new record.","content":{"application/json":{"schema":{"type":"object","required":["contact"],"properties":{"contact":{"$ref":"#/components/schemas/Record"}}},"example":{"contact":{"id":"6f1c2a0e-8d1b-4b7a-9f3e-2c4d5e6f7a8b","phone":"+919800000091","phoneKey":"919800000091","name":"Ravi Rao","fields":{"company":"Acme"},"segments":[],"sources":["api"],"stage":"new","stageManual":false,"assigneeId":null,"assigneeName":null,"tags":["expo"],"notes":[],"customFields":{"budget":12000,"pan":{"redacted":true,"set":true,"hint":"••••4821"}},"leadStatus":"new","stats":{"count":0,"connected":0},"createdAt":"2026-11-03T09:02:44.000Z","updatedAt":"2026-11-03T09:02:44.000Z"}}}},"headers":{"RateLimit-Policy":{"$ref":"#/components/headers/RateLimitPolicy"},"RateLimit-Limit":{"$ref":"#/components/headers/RateLimitLimit"},"RateLimit-Remaining":{"$ref":"#/components/headers/RateLimitRemaining"},"RateLimit-Reset":{"$ref":"#/components/headers/RateLimitReset"}}},"400":{"description":"`phone_required` (fewer than 6 digits), `name must be a string`, or a kind or custom field that doesn't fit your workspace.","content":{"application/json":{"schema":{"$ref":"#/components/schemas/Error"}}}},"401":{"$ref":"#/components/responses/Unauthorized"},"403":{"$ref":"#/components/responses/InsufficientScope"},"409":{"description":"A record with this number already exists.","content":{"application/json":{"schema":{"allOf":[{"$ref":"#/components/schemas/Error"},{"type":"object","properties":{"error":{"type":"string","enum":["contact_exists"]}}}]}}}},"423":{"$ref":"#/components/responses/AiAppsPaused"},"429":{"$ref":"#/components/responses/RateLimited"},"500":{"$ref":"#/components/responses/InternalError"},"503":{"description":"This environment is missing a contacts migration.","content":{"application/json":{"schema":{"allOf":[{"$ref":"#/components/schemas/Error"},{"type":"object","properties":{"error":{"type":"string","enum":["schema_missing"]}}}]}}}}},"x-required-scope":"crm:write"},"parameters":[{"$ref":"#/components/parameters/FinnOrg"},{"$ref":"#/components/parameters/FinnVersion"}]},"/contacts/{id}":{"parameters":[{"name":"id","in":"path","required":true,"description":"Record ID, or the record's phone number.","schema":{"type":"string"},"example":"6f1c2a0e-8d1b-4b7a-9f3e-2c4d5e6f7a8b"},{"$ref":"#/components/parameters/FinnOrg"},{"$ref":"#/components/parameters/FinnVersion"}],"get":{"tags":["Records"],"operationId":"retrieveRecord","summary":"Retrieve a record","description":"**Scope:** `crm:read`.\n\nOne record, by ID or phone number.\n\n**Rate limit:** 300 requests per minute per API key, shared by every read in Records, Cases and Approvals.","responses":{"200":{"description":"The record.","content":{"application/json":{"schema":{"type":"object","required":["contact"],"properties":{"contact":{"$ref":"#/components/schemas/Record"}}},"example":{"contact":{"id":"6f1c2a0e-8d1b-4b7a-9f3e-2c4d5e6f7a8b","phone":"+919800000091","phoneKey":"919800000091","name":"Ravi Rao","fields":{"company":"Acme"},"segments":[],"sources":["api"],"stage":"new","stageManual":false,"assigneeId":null,"assigneeName":null,"tags":["expo"],"notes":[],"customFields":{"budget":12000,"pan":{"redacted":true,"set":true,"hint":"••••4821"}},"leadStatus":"new","stats":{"count":0,"connected":0},"createdAt":"2026-11-03T09:02:44.000Z","updatedAt":"2026-11-03T09:02:44.000Z"}}}},"headers":{"RateLimit-Policy":{"$ref":"#/components/headers/RateLimitPolicy"},"RateLimit-Limit":{"$ref":"#/components/headers/RateLimitLimit"},"RateLimit-Remaining":{"$ref":"#/components/headers/RateLimitRemaining"},"RateLimit-Reset":{"$ref":"#/components/headers/RateLimitReset"}}},"401":{"$ref":"#/components/responses/Unauthorized"},"403":{"$ref":"#/components/responses/InsufficientScope"},"404":{"description":"No live record with that ID or number in your organization.","content":{"application/json":{"schema":{"allOf":[{"$ref":"#/components/schemas/Error"},{"type":"object","properties":{"error":{"type":"string","enum":["not_found"]}}}]}}}},"429":{"$ref":"#/components/responses/RateLimited"},"500":{"$ref":"#/components/responses/InternalError"},"503":{"description":"This environment is missing a contacts migration.","content":{"application/json":{"schema":{"allOf":[{"$ref":"#/components/schemas/Error"},{"type":"object","properties":{"error":{"type":"string","enum":["schema_missing"]}}}]}}}}},"x-required-scope":"crm:read"},"patch":{"tags":["Records"],"operationId":"updateRecord","summary":"Update a record","description":"**Scope:** `crm:write`.\n\nChanges the name, tags, custom fields, stage, substatus or lead status. Other keys return `400 unsupported_field` with the keys in `fields`. A stage check (a field a person must fill, or an approval) stops an API key: it returns `409`, and only a person in Finn can move past it. Required fields are not enforced on API writes. Lead status, stage and substatus can't be set on a record of a kind that isn't lead-centric (`400`). A change sends `contact.updated` (and `contact.stage_changed`, `contact.assigned`, `lead.status_changed` where they apply) to webhook endpoints that listen for them.\n\n**Rate limit:** 120 writes per minute per API key, shared by `POST /contacts`, `PATCH /contacts/{id}`, `POST /contacts/{id}/notes` and the three approval decisions.","requestBody":{"required":true,"content":{"application/json":{"schema":{"$ref":"#/components/schemas/UpdateRecordRequest"},"example":{"stage":"qualified","custom_fields":{"budget":15000,"notes_old":null},"tags":["expo","hot"]}}}},"responses":{"200":{"description":"The record after the change.","content":{"application/json":{"schema":{"type":"object","required":["contact"],"properties":{"contact":{"$ref":"#/components/schemas/Record"}}},"example":{"contact":{"id":"6f1c2a0e-8d1b-4b7a-9f3e-2c4d5e6f7a8b","phone":"+919800000091","phoneKey":"919800000091","name":"Ravi Rao","fields":{"company":"Acme"},"segments":[],"sources":["api"],"stage":"new","stageManual":false,"assigneeId":null,"assigneeName":null,"tags":["expo"],"notes":[],"customFields":{"budget":12000,"pan":{"redacted":true,"set":true,"hint":"••••4821"}},"leadStatus":"new","stats":{"count":0,"connected":0},"createdAt":"2026-11-03T09:02:44.000Z","updatedAt":"2026-11-03T09:02:44.000Z"}}}},"headers":{"RateLimit-Policy":{"$ref":"#/components/headers/RateLimitPolicy"},"RateLimit-Limit":{"$ref":"#/components/headers/RateLimitLimit"},"RateLimit-Remaining":{"$ref":"#/components/headers/RateLimitRemaining"},"RateLimit-Reset":{"$ref":"#/components/headers/RateLimitReset"}}},"400":{"description":"`unsupported_field`, `invalid_substatus`, an unknown stage or lead status, a bad custom field, or a stage move on a record that isn't a lead.","content":{"application/json":{"schema":{"$ref":"#/components/schemas/Error"}}}},"401":{"$ref":"#/components/responses/Unauthorized"},"403":{"$ref":"#/components/responses/InsufficientScope"},"404":{"description":"No live record with that ID or number in your organization.","content":{"application/json":{"schema":{"allOf":[{"$ref":"#/components/schemas/Error"},{"type":"object","properties":{"error":{"type":"string","enum":["not_found"]}}}]}}}},"409":{"description":"A stage check blocks the move. The body names the check; a person must clear it in Finn.","content":{"application/json":{"schema":{"type":"object","additionalProperties":true}}}},"423":{"$ref":"#/components/responses/AiAppsPaused"},"429":{"$ref":"#/components/responses/RateLimited"},"500":{"$ref":"#/components/responses/InternalError"},"503":{"description":"This environment is missing a contacts migration.","content":{"application/json":{"schema":{"allOf":[{"$ref":"#/components/schemas/Error"},{"type":"object","properties":{"error":{"type":"string","enum":["schema_missing"]}}}]}}}}},"x-required-scope":"crm:write"}},"/contacts/{id}/timeline":{"parameters":[{"name":"id","in":"path","required":true,"description":"Record ID, or the record's phone number.","schema":{"type":"string"},"example":"6f1c2a0e-8d1b-4b7a-9f3e-2c4d5e6f7a8b"},{"$ref":"#/components/parameters/FinnOrg"},{"$ref":"#/components/parameters/FinnVersion"}],"get":{"tags":["Records"],"operationId":"listRecordTimeline","summary":"List a record's timeline","description":"**Scope:** `crm:read`.\n\nCalls, chats, messages, Operator steps, notes, forms and changes on a record, newest first. Pass `next` back as `before` for the next page; `next` is `null` on the last page.\n\n**Rate limit:** 300 requests per minute per API key, shared by every read in Records, Cases and Approvals.","parameters":[{"name":"before","in":"query","description":"The `next` value of the previous page.","schema":{"type":"string"}},{"name":"limit","in":"query","description":"Items per page, 1 to 100.","schema":{"type":"integer","minimum":1,"maximum":100,"default":50}},{"name":"kind","in":"query","description":"Comma list of `call`, `chat`, `message`, `operator`, `note`, `form`, `change`, `file`, `view`.","schema":{"type":"string"}},{"name":"channel","in":"query","description":"`email` for email only, `-email` for everything but email.","schema":{"type":"string"}}],"responses":{"200":{"description":"A page of the timeline.","content":{"application/json":{"schema":{"type":"object","required":["items","next"],"properties":{"items":{"type":"array","items":{"$ref":"#/components/schemas/TimelineItem"}},"next":{"type":["string","null"]},"counts":{"type":"object","additionalProperties":{"type":"integer"},"description":"Items by kind, and `all`."}}}}},"headers":{"RateLimit-Policy":{"$ref":"#/components/headers/RateLimitPolicy"},"RateLimit-Limit":{"$ref":"#/components/headers/RateLimitLimit"},"RateLimit-Remaining":{"$ref":"#/components/headers/RateLimitRemaining"},"RateLimit-Reset":{"$ref":"#/components/headers/RateLimitReset"}}},"400":{"description":"`kind` or `channel` is not valid.","content":{"application/json":{"schema":{"$ref":"#/components/schemas/Error"}}}},"401":{"$ref":"#/components/responses/Unauthorized"},"403":{"$ref":"#/components/responses/InsufficientScope"},"404":{"description":"No live record with that ID or number in your organization.","content":{"application/json":{"schema":{"allOf":[{"$ref":"#/components/schemas/Error"},{"type":"object","properties":{"error":{"type":"string","enum":["not_found"]}}}]}}}},"429":{"$ref":"#/components/responses/RateLimited"},"500":{"$ref":"#/components/responses/InternalError"},"503":{"description":"This environment is missing a contacts migration.","content":{"application/json":{"schema":{"allOf":[{"$ref":"#/components/schemas/Error"},{"type":"object","properties":{"error":{"type":"string","enum":["schema_missing"]}}}]}}}}},"x-required-scope":"crm:read"}},"/contacts/{id}/notes":{"parameters":[{"name":"id","in":"path","required":true,"description":"Record ID, or the record's phone number.","schema":{"type":"string"},"example":"6f1c2a0e-8d1b-4b7a-9f3e-2c4d5e6f7a8b"},{"$ref":"#/components/parameters/FinnOrg"},{"$ref":"#/components/parameters/FinnVersion"}],"post":{"tags":["Records"],"operationId":"addRecordNote","summary":"Add a note","description":"**Scope:** `crm:write`.\n\nAdds a note to the record's timeline, written by `API key: <key name>`. Text over 2,000 characters is cut. Send a `client_id` to make retries safe: the same `client_id` on the same record returns the first note instead of adding another.\n\n**Rate limit:** 120 writes per minute per API key, shared by `POST /contacts`, `PATCH /contacts/{id}`, `POST /contacts/{id}/notes` and the three approval decisions.","requestBody":{"required":true,"content":{"application/json":{"schema":{"type":"object","required":["text"],"properties":{"text":{"type":"string","maxLength":2000},"client_id":{"type":"string","maxLength":100,"description":"Your own ID for this note, for safe retries."}}},"example":{"text":"Asked for a callback after 6 pm.","client_id":"agent-run-4821-note-1"}}}},"responses":{"201":{"description":"The note.","content":{"application/json":{"schema":{"type":"object","required":["note"],"properties":{"note":{"$ref":"#/components/schemas/Note"}}}}},"headers":{"RateLimit-Policy":{"$ref":"#/components/headers/RateLimitPolicy"},"RateLimit-Limit":{"$ref":"#/components/headers/RateLimitLimit"},"RateLimit-Remaining":{"$ref":"#/components/headers/RateLimitRemaining"},"RateLimit-Reset":{"$ref":"#/components/headers/RateLimitReset"}}},"400":{"description":"`text is required` or `client_id must be a string`.","content":{"application/json":{"schema":{"$ref":"#/components/schemas/Error"}}}},"401":{"$ref":"#/components/responses/Unauthorized"},"403":{"$ref":"#/components/responses/InsufficientScope"},"404":{"description":"No live record with that ID or number in your organization.","content":{"application/json":{"schema":{"allOf":[{"$ref":"#/components/schemas/Error"},{"type":"object","properties":{"error":{"type":"string","enum":["not_found"]}}}]}}}},"423":{"$ref":"#/components/responses/AiAppsPaused"},"429":{"$ref":"#/components/responses/RateLimited"},"500":{"$ref":"#/components/responses/InternalError"},"503":{"description":"This environment is missing a contacts migration.","content":{"application/json":{"schema":{"allOf":[{"$ref":"#/components/schemas/Error"},{"type":"object","properties":{"error":{"type":"string","enum":["schema_missing"]}}}]}}}}},"x-required-scope":"crm:write"}},"/cases":{"get":{"tags":["Cases"],"operationId":"listCases","summary":"List cases","description":"**Scope:** `operator:read` (older keys: `cases:read`).\n\nYour organization's Finn Operator cases, newest first. Pass `next_before` back as `before` for the next page; it is `null` when the page is empty. Test cases are not listed.\n\n**Rate limit:** 300 requests per minute per API key, shared by every read in Records, Cases and Approvals.","parameters":[{"name":"status","in":"query","description":"`open` (open, running or waiting), or one exact status: `running`, `waiting`, `resolved`, `ended`, `failed`, `cancelled`.","schema":{"type":"string"}},{"name":"agent_id","in":"query","description":"Only this agent's cases.","schema":{"type":"string","format":"uuid"}},{"name":"contact_id","in":"query","description":"Only cases about this record.","schema":{"type":"string"}},{"name":"owner_id","in":"query","description":"Only cases this person owns; `none` for unowned cases.","schema":{"type":"string"}},{"name":"outcome_key","in":"query","description":"Only cases with this outcome.","schema":{"type":"string"}},{"name":"q","in":"query","description":"Search title and subject.","schema":{"type":"string"}},{"name":"before","in":"query","description":"`next_before` of the previous page (RFC 3339 time).","schema":{"type":"string"}},{"name":"limit","in":"query","description":"Cases per page, 1 to 200.","schema":{"type":"integer","minimum":1,"maximum":200,"default":50}}],"responses":{"200":{"description":"A page of cases.","content":{"application/json":{"schema":{"type":"object","required":["cases","next_before"],"properties":{"cases":{"type":"array","items":{"$ref":"#/components/schemas/CaseSummary"}},"next_before":{"type":["string","null"],"format":"date-time"}}}}},"headers":{"RateLimit-Policy":{"$ref":"#/components/headers/RateLimitPolicy"},"RateLimit-Limit":{"$ref":"#/components/headers/RateLimitLimit"},"RateLimit-Remaining":{"$ref":"#/components/headers/RateLimitRemaining"},"RateLimit-Reset":{"$ref":"#/components/headers/RateLimitReset"}}},"400":{"description":"A query value is not valid.","content":{"application/json":{"schema":{"oneOf":[{"$ref":"#/components/schemas/OperatorError"},{"$ref":"#/components/schemas/Error"}]}}}},"401":{"$ref":"#/components/responses/Unauthorized"},"403":{"$ref":"#/components/responses/InsufficientScope"},"429":{"$ref":"#/components/responses/RateLimited"},"502":{"description":"Finn Operator is not configured or did not answer (`operator_unavailable`). Safe to retry reads; check state before retrying a write.","content":{"application/json":{"schema":{"allOf":[{"$ref":"#/components/schemas/Error"},{"type":"object","properties":{"error":{"type":"string","enum":["operator_unavailable"]}}}]}}}},"503":{"description":"Finn Operator is not configured or did not answer (`operator_unavailable`). Safe to retry reads; check state before retrying a write.","content":{"application/json":{"schema":{"allOf":[{"$ref":"#/components/schemas/Error"},{"type":"object","properties":{"error":{"type":"string","enum":["operator_unavailable"]}}}]}}}}},"x-required-scope":"operator:read","x-legacy-scope":"cases:read","x-mcp-tool":"list_operator_cases"},"parameters":[{"$ref":"#/components/parameters/FinnOrg"},{"$ref":"#/components/parameters/FinnVersion"}]},"/cases/{id}":{"parameters":[{"name":"id","in":"path","required":true,"description":"Case ID.","schema":{"type":"string","format":"uuid"},"example":"3c9b1f7e-2a4d-4e6b-8c1d-5f7a9b0c2d3e"},{"$ref":"#/components/parameters/FinnOrg"},{"$ref":"#/components/parameters/FinnVersion"}],"get":{"tags":["Cases"],"operationId":"retrieveCase","x-mcp-tool":"get_case_status","summary":"Retrieve a case","description":"**Scope:** `operator:read` (older keys: `cases:read`).\n\nOne case with its participants, threads, steps, open waits and resolutions.\n\n**Rate limit:** 300 requests per minute per API key, shared by every read in Records, Cases and Approvals.","responses":{"200":{"description":"The case.","content":{"application/json":{"schema":{"$ref":"#/components/schemas/Case"}}},"headers":{"RateLimit-Policy":{"$ref":"#/components/headers/RateLimitPolicy"},"RateLimit-Limit":{"$ref":"#/components/headers/RateLimitLimit"},"RateLimit-Remaining":{"$ref":"#/components/headers/RateLimitRemaining"},"RateLimit-Reset":{"$ref":"#/components/headers/RateLimitReset"}}},"400":{"description":"A path ID is not a UUID.","content":{"application/json":{"schema":{"allOf":[{"$ref":"#/components/schemas/Error"},{"type":"object","properties":{"error":{"type":"string","enum":["invalid_id"]}}}]}}}},"401":{"$ref":"#/components/responses/Unauthorized"},"403":{"$ref":"#/components/responses/InsufficientScope"},"404":{"description":"No such case in your organization (`code: not_found`).","content":{"application/json":{"schema":{"oneOf":[{"$ref":"#/components/schemas/OperatorError"},{"$ref":"#/components/schemas/Error"}]}}}},"429":{"$ref":"#/components/responses/RateLimited"},"502":{"description":"Finn Operator is not configured or did not answer (`operator_unavailable`). Safe to retry reads; check state before retrying a write.","content":{"application/json":{"schema":{"allOf":[{"$ref":"#/components/schemas/Error"},{"type":"object","properties":{"error":{"type":"string","enum":["operator_unavailable"]}}}]}}}},"503":{"description":"Finn Operator is not configured or did not answer (`operator_unavailable`). Safe to retry reads; check state before retrying a write.","content":{"application/json":{"schema":{"allOf":[{"$ref":"#/components/schemas/Error"},{"type":"object","properties":{"error":{"type":"string","enum":["operator_unavailable"]}}}]}}}}},"x-required-scope":"operator:read","x-legacy-scope":"cases:read"}},"/cases/{id}/events":{"parameters":[{"name":"id","in":"path","required":true,"description":"Case ID.","schema":{"type":"string","format":"uuid"},"example":"3c9b1f7e-2a4d-4e6b-8c1d-5f7a9b0c2d3e"},{"$ref":"#/components/parameters/FinnOrg"},{"$ref":"#/components/parameters/FinnVersion"}],"get":{"tags":["Cases"],"operationId":"listCaseEvents","x-mcp-tool":"get_case_status","summary":"List a case's events","description":"**Scope:** `operator:read` (older keys: `cases:read`).\n\nThe case's event log, oldest first: up to the first 200 events. Query parameters are not passed on, so later events can't be paged through this endpoint yet; `next_after` is the last `seq` returned.\n\n**Rate limit:** 300 requests per minute per API key, shared by every read in Records, Cases and Approvals.","responses":{"200":{"description":"Events.","content":{"application/json":{"schema":{"type":"object","required":["events","next_after"],"properties":{"events":{"type":"array","items":{"$ref":"#/components/schemas/CaseEvent"}},"next_after":{"type":"integer"}}}}},"headers":{"RateLimit-Policy":{"$ref":"#/components/headers/RateLimitPolicy"},"RateLimit-Limit":{"$ref":"#/components/headers/RateLimitLimit"},"RateLimit-Remaining":{"$ref":"#/components/headers/RateLimitRemaining"},"RateLimit-Reset":{"$ref":"#/components/headers/RateLimitReset"}}},"400":{"description":"A path ID is not a UUID.","content":{"application/json":{"schema":{"allOf":[{"$ref":"#/components/schemas/Error"},{"type":"object","properties":{"error":{"type":"string","enum":["invalid_id"]}}}]}}}},"401":{"$ref":"#/components/responses/Unauthorized"},"403":{"$ref":"#/components/responses/InsufficientScope"},"404":{"description":"No such case in your organization (`code: not_found`).","content":{"application/json":{"schema":{"oneOf":[{"$ref":"#/components/schemas/OperatorError"},{"$ref":"#/components/schemas/Error"}]}}}},"429":{"$ref":"#/components/responses/RateLimited"},"502":{"description":"Finn Operator is not configured or did not answer (`operator_unavailable`). Safe to retry reads; check state before retrying a write.","content":{"application/json":{"schema":{"allOf":[{"$ref":"#/components/schemas/Error"},{"type":"object","properties":{"error":{"type":"string","enum":["operator_unavailable"]}}}]}}}},"503":{"description":"Finn Operator is not configured or did not answer (`operator_unavailable`). Safe to retry reads; check state before retrying a write.","content":{"application/json":{"schema":{"allOf":[{"$ref":"#/components/schemas/Error"},{"type":"object","properties":{"error":{"type":"string","enum":["operator_unavailable"]}}}]}}}}},"x-required-scope":"operator:read","x-legacy-scope":"cases:read"}},"/agents/{id}/cases":{"parameters":[{"name":"id","in":"path","required":true,"description":"The Finn Operator agent's ID.","schema":{"type":"string","format":"uuid"}},{"$ref":"#/components/parameters/FinnOrg"},{"$ref":"#/components/parameters/FinnVersion"}],"post":{"tags":["Cases"],"operationId":"startCase","x-mcp-tool":"start_operator_case","summary":"Start a case","description":"**Scope:** `operator:write` (older keys: `cases:write`).\n\nStarts a case on a published, live Finn Operator agent at one of its API Starts. Finn forwards the request to Finn Operator, which runs the case.\n\nSend an `Idempotency-Key` header to make retries safe: a repeat with the same key returns the first answer with `replayed: true`. Without one, every request is a new start.\n\n`201` when a new case was opened; `200` when the request matched or reopened a case for the same subject, or was a replay. The body has the same shape either way.\n\n**Rate limit:** 60 cases started per minute per API key.","parameters":[{"name":"Idempotency-Key","in":"header","required":false,"description":"Your unique key for this start, up to 200 characters.","schema":{"type":"string","maxLength":200}}],"requestBody":{"required":false,"content":{"application/json":{"schema":{"$ref":"#/components/schemas/StartCaseRequest"},"example":{"subject":{"type":"contact","ref":"6f1c2a0e-8d1b-4b7a-9f3e-2c4d5e6f7a8b"},"title":"Renewal follow-up","participants":[{"role":"customer","contact_id":"6f1c2a0e-8d1b-4b7a-9f3e-2c4d5e6f7a8b"}],"data":{"policy_no":"P-20419"}}}}},"responses":{"200":{"description":"Matched, reopened or replayed.","content":{"application/json":{"schema":{"$ref":"#/components/schemas/CaseStarted"}}},"headers":{"RateLimit-Policy":{"$ref":"#/components/headers/RateLimitPolicy"},"RateLimit-Limit":{"$ref":"#/components/headers/RateLimitLimit"},"RateLimit-Remaining":{"$ref":"#/components/headers/RateLimitRemaining"},"RateLimit-Reset":{"$ref":"#/components/headers/RateLimitReset"}}},"201":{"description":"A new case was opened.","content":{"application/json":{"schema":{"$ref":"#/components/schemas/CaseStarted"},"example":{"case_id":"3c9b1f7e-2a4d-4e6b-8c1d-5f7a9b0c2d3e","trigger_event_id":"9a1e5c3b-7d2f-4b8a-a6e4-1c3f5d7b9e20","result":"opened","replayed":false}}},"headers":{"RateLimit-Policy":{"$ref":"#/components/headers/RateLimitPolicy"},"RateLimit-Limit":{"$ref":"#/components/headers/RateLimitLimit"},"RateLimit-Remaining":{"$ref":"#/components/headers/RateLimitRemaining"},"RateLimit-Reset":{"$ref":"#/components/headers/RateLimitReset"}}},"202":{"$ref":"#/components/responses/PendingApproval"},"400":{"description":"`invalid_id` (agent ID not a UUID), or Operator's `invalid` for a malformed body.","content":{"application/json":{"schema":{"oneOf":[{"$ref":"#/components/schemas/Error"},{"$ref":"#/components/schemas/OperatorError"}]}}}},"401":{"$ref":"#/components/responses/Unauthorized"},"403":{"$ref":"#/components/responses/InsufficientScope"},"404":{"description":"No such agent in your organization.","content":{"application/json":{"schema":{"oneOf":[{"$ref":"#/components/schemas/V1Error"},{"$ref":"#/components/schemas/OperatorError"},{"$ref":"#/components/schemas/Error"}]}}}},"409":{"description":"`conflict`: the agent has no published version, is not live, or has no API Start.","content":{"application/json":{"schema":{"oneOf":[{"$ref":"#/components/schemas/V1Error"},{"$ref":"#/components/schemas/OperatorError"},{"$ref":"#/components/schemas/Error"}]}}}},"422":{"description":"`invalid`: a field breaks a rule, such as `start` missing when there are several Starts, a participant without `role`, a phone that isn't E.164, or `memory` that doesn't fit the agent's schema.","content":{"application/json":{"schema":{"oneOf":[{"$ref":"#/components/schemas/V1Error"},{"$ref":"#/components/schemas/OperatorError"},{"$ref":"#/components/schemas/Error"}]}}}},"423":{"$ref":"#/components/responses/AiAppsPaused"},"429":{"description":"`rate_limited` (more than 60 starts a minute on this key; `Retry-After` header), or Operator's `cap_reached` (a daily case cap; `details.resets_at` says when it resets; no `Retry-After`).","content":{"application/json":{"schema":{"oneOf":[{"$ref":"#/components/schemas/Error"},{"$ref":"#/components/schemas/OperatorError"}]}}}},"502":{"description":"Finn Operator is not configured or did not answer (`operator_unavailable`). Safe to retry reads; check state before retrying a write.","content":{"application/json":{"schema":{"allOf":[{"$ref":"#/components/schemas/Error"},{"type":"object","properties":{"error":{"type":"string","enum":["operator_unavailable"]}}}]}}}},"503":{"description":"Finn Operator is not configured or did not answer (`operator_unavailable`). Safe to retry reads; check state before retrying a write.","content":{"application/json":{"schema":{"allOf":[{"$ref":"#/components/schemas/Error"},{"type":"object","properties":{"error":{"type":"string","enum":["operator_unavailable"]}}}]}}}}},"x-required-scope":"operator:write","x-legacy-scope":"cases:write","x-finn-hold":{"action":"case.start","class":"outward"}}},"/approvals":{"get":{"tags":["Approvals"],"operationId":"listApprovals","x-mcp-tool":"whats_waiting_for_approval","summary":"List approvals","description":"**Scope:** `approvals:read` (older keys: `cases:read`).\n\nApprovals Finn Operator cases are waiting on. By default, open approvals this key may answer (`scope=mine`). `scope=team` lists every approval in the organization and needs `approvals:write`. Pass `next_cursor` back as `cursor`; it is `null` on the last page. Approvals of test cases are not listed.\n\nWith the organization's `approvals_all_holds` switch on, the caller's own API holds (items with `source: \"api_hold\"`, see `ApiHold`) are listed too, newest first in the same pages and cursor. Holds are not listed when `contact_id` is given. If Finn Operator is not on for the organization, the list holds only API holds.\n\n**Rate limit:** 300 requests per minute per API key, shared by every read in Records, Cases and Approvals.","parameters":[{"name":"status","in":"query","description":"`all` to include decided, expired and cancelled approvals. Anything else: open only.","schema":{"type":"string"}},{"name":"scope","in":"query","description":"`mine` (default) or `team`.","schema":{"type":"string"}},{"name":"contact_id","in":"query","description":"Only approvals about this record.","schema":{"type":"string"}},{"name":"cursor","in":"query","description":"`next_cursor` of the previous page.","schema":{"type":"string"}},{"name":"limit","in":"query","description":"Approvals per page, 1 to 200.","schema":{"type":"integer","minimum":1,"maximum":200,"default":50}}],"responses":{"200":{"description":"A page of approvals.","content":{"application/json":{"schema":{"type":"object","required":["approvals","next_cursor"],"properties":{"approvals":{"type":"array","items":{"oneOf":[{"$ref":"#/components/schemas/Approval"},{"$ref":"#/components/schemas/ApiHold"}]}},"next_cursor":{"type":["string","null"]}}},"example":{"approvals":[{"id":"5d2e8f1a-3b4c-4d6e-9f0a-1b2c3d4e5f6a","case_id":"3c9b1f7e-2a4d-4e6b-8c1d-5f7a9b0c2d3e","case_title":"Renewal follow-up","agent_id":"1b2c3d4e-5f6a-4b7c-8d9e-0f1a2b3c4d5e","agent_name":"Renewals","contact_id":"6f1c2a0e-8d1b-4b7a-9f3e-2c4d5e6f7a8b","question":"Send the renewal quote of 12,000 to Ravi Rao?","allowed_answers":["approve","reject"],"status":"open","assignee_role":"approver","assignee_user_id":null,"assignee_user_ids":[],"payment":null,"proposal":{"summary":"Email the renewal quote"},"answer":null,"comment":null,"decided_by":null,"decided_via":null,"decided_at":null,"deadline_at":"2026-11-04T09:00:00.000Z","reminders":0,"created_at":"2026-11-03T09:00:00.000Z"}],"next_cursor":null}}},"headers":{"RateLimit-Policy":{"$ref":"#/components/headers/RateLimitPolicy"},"RateLimit-Limit":{"$ref":"#/components/headers/RateLimitLimit"},"RateLimit-Remaining":{"$ref":"#/components/headers/RateLimitRemaining"},"RateLimit-Reset":{"$ref":"#/components/headers/RateLimitReset"}}},"400":{"description":"`limit` is not a number in range.","content":{"application/json":{"schema":{"oneOf":[{"$ref":"#/components/schemas/OperatorError"},{"$ref":"#/components/schemas/Error"}]}}}},"401":{"$ref":"#/components/responses/Unauthorized"},"403":{"description":"`insufficient_scope`, or Operator's `forbidden` for `scope=team` without `approvals:write`.","content":{"application/json":{"schema":{"oneOf":[{"$ref":"#/components/schemas/Error"},{"$ref":"#/components/schemas/OperatorError"}]}}}},"422":{"description":"`cursor` is not valid.","content":{"application/json":{"schema":{"oneOf":[{"$ref":"#/components/schemas/OperatorError"},{"$ref":"#/components/schemas/Error"}]}}}},"429":{"$ref":"#/components/responses/RateLimited"},"502":{"description":"Finn Operator is not configured or did not answer (`operator_unavailable`). Safe to retry reads; check state before retrying a write.","content":{"application/json":{"schema":{"allOf":[{"$ref":"#/components/schemas/Error"},{"type":"object","properties":{"error":{"type":"string","enum":["operator_unavailable"]}}}]}}}},"503":{"description":"Finn Operator is not configured or did not answer (`operator_unavailable`). Safe to retry reads; check state before retrying a write.","content":{"application/json":{"schema":{"allOf":[{"$ref":"#/components/schemas/Error"},{"type":"object","properties":{"error":{"type":"string","enum":["operator_unavailable"]}}}]}}}}},"x-required-scope":"approvals:read","x-legacy-scope":"cases:read"},"parameters":[{"$ref":"#/components/parameters/FinnOrg"},{"$ref":"#/components/parameters/FinnVersion"}]},"/approvals/{id}":{"parameters":[{"name":"id","in":"path","required":true,"description":"Approval ID.","schema":{"type":"string","format":"uuid"},"example":"5d2e8f1a-3b4c-4d6e-9f0a-1b2c3d4e5f6a"},{"$ref":"#/components/parameters/FinnOrg"},{"$ref":"#/components/parameters/FinnVersion"}],"get":{"tags":["Approvals"],"operationId":"retrieveApproval","x-mcp-tool":"whats_waiting_for_approval","summary":"Retrieve an approval","description":"**Scope:** `approvals:read` (older keys: `cases:read`).\n\nOne approval, with its `allowed_answers`, `proposal` and decision.\n\n**Rate limit:** 300 requests per minute per API key, shared by every read in Records, Cases and Approvals.\n\n**Held writes:** the `approval_id` from a `202 pending_approval` answer is read here too, as an `ApiHold` (`source: \"api_hold\"`), by the credential that made it, with no extra scope. Any other credential gets 404.","responses":{"200":{"description":"The approval.","content":{"application/json":{"schema":{"oneOf":[{"$ref":"#/components/schemas/Approval"},{"$ref":"#/components/schemas/ApiHold"}]},"example":{"id":"5d2e8f1a-3b4c-4d6e-9f0a-1b2c3d4e5f6a","case_id":"3c9b1f7e-2a4d-4e6b-8c1d-5f7a9b0c2d3e","case_title":"Renewal follow-up","agent_id":"1b2c3d4e-5f6a-4b7c-8d9e-0f1a2b3c4d5e","agent_name":"Renewals","contact_id":"6f1c2a0e-8d1b-4b7a-9f3e-2c4d5e6f7a8b","question":"Send the renewal quote of 12,000 to Ravi Rao?","allowed_answers":["approve","reject"],"status":"open","assignee_role":"approver","assignee_user_id":null,"assignee_user_ids":[],"payment":null,"proposal":{"summary":"Email the renewal quote"},"answer":null,"comment":null,"decided_by":null,"decided_via":null,"decided_at":null,"deadline_at":"2026-11-04T09:00:00.000Z","reminders":0,"created_at":"2026-11-03T09:00:00.000Z"}}},"headers":{"RateLimit-Policy":{"$ref":"#/components/headers/RateLimitPolicy"},"RateLimit-Limit":{"$ref":"#/components/headers/RateLimitLimit"},"RateLimit-Remaining":{"$ref":"#/components/headers/RateLimitRemaining"},"RateLimit-Reset":{"$ref":"#/components/headers/RateLimitReset"}}},"400":{"description":"A path ID is not a UUID.","content":{"application/json":{"schema":{"allOf":[{"$ref":"#/components/schemas/Error"},{"type":"object","properties":{"error":{"type":"string","enum":["invalid_id"]}}}]}}}},"401":{"$ref":"#/components/responses/Unauthorized"},"403":{"$ref":"#/components/responses/InsufficientScope"},"404":{"description":"No such approval in your organization.","content":{"application/json":{"schema":{"oneOf":[{"$ref":"#/components/schemas/OperatorError"},{"$ref":"#/components/schemas/Error"}]}}}},"429":{"$ref":"#/components/responses/RateLimited"},"502":{"description":"Finn Operator is not configured or did not answer (`operator_unavailable`). Safe to retry reads; check state before retrying a write.","content":{"application/json":{"schema":{"allOf":[{"$ref":"#/components/schemas/Error"},{"type":"object","properties":{"error":{"type":"string","enum":["operator_unavailable"]}}}]}}}},"503":{"description":"Finn Operator is not configured or did not answer (`operator_unavailable`). Safe to retry reads; check state before retrying a write.","content":{"application/json":{"schema":{"allOf":[{"$ref":"#/components/schemas/Error"},{"type":"object","properties":{"error":{"type":"string","enum":["operator_unavailable"]}}}]}}}}},"x-required-scope":"approvals:read","x-legacy-scope":"cases:read"}},"/approvals/{id}/decide":{"parameters":[{"name":"id","in":"path","required":true,"description":"Approval ID.","schema":{"type":"string","format":"uuid"},"example":"5d2e8f1a-3b4c-4d6e-9f0a-1b2c3d4e5f6a"},{"$ref":"#/components/parameters/FinnOrg"},{"$ref":"#/components/parameters/FinnVersion"}],"post":{"tags":["Approvals"],"operationId":"decideApproval","summary":"Decide an approval","description":"**Scope:** `approvals:write`, and your organization must have turned on approval decisions by API key (off by default; ask Finn support), else `403 approvals_by_api_off`.\n\nAnswers the approval with one of its `allowed_answers`. Use it for approvals that offer a choice between options; for plain yes/no use approve or reject.\n\nAn API key with `approvals:write` decides one approval at a time, as asked: only approvals asked of the `approver` role or of a role that isn't a Finn Operator role (such as `finance`), never one assigned to a named person. It can't decide a payment (`403 forbidden`), can't send an edited proposal (`422 invalid`: edit it in Finn), and can't use bulk decide. The decision is recorded with `decided_by: \"api_key:<key_id>\"` and `decided_via: \"api\"`. A key created before scopes existed can't decide approvals: create a key with `approvals:write`.\n\n**Rate limit:** 120 writes per minute per API key, shared by `POST /contacts`, `PATCH /contacts/{id}`, `POST /contacts/{id}/notes` and the three approval decisions.","requestBody":{"required":true,"content":{"application/json":{"schema":{"type":"object","required":["answer"],"properties":{"answer":{"type":"string","maxLength":100},"comment":{"type":"string","maxLength":2000,"description":"Optional note kept with the decision."}}},"example":{"answer":"approve","comment":"Checked against the policy."}}}},"responses":{"200":{"description":"The approval, decided.","content":{"application/json":{"schema":{"$ref":"#/components/schemas/Approval"}}},"headers":{"RateLimit-Policy":{"$ref":"#/components/headers/RateLimitPolicy"},"RateLimit-Limit":{"$ref":"#/components/headers/RateLimitLimit"},"RateLimit-Remaining":{"$ref":"#/components/headers/RateLimitRemaining"},"RateLimit-Reset":{"$ref":"#/components/headers/RateLimitReset"}}},"400":{"description":"`invalid_id` (not a UUID) or `answer_required`.","content":{"application/json":{"schema":{"oneOf":[{"$ref":"#/components/schemas/Error"},{"$ref":"#/components/schemas/OperatorError"}]}}}},"401":{"$ref":"#/components/responses/Unauthorized"},"403":{"description":"`insufficient_scope` (the key lacks `approvals:write`), `approvals_by_api_off` (your organization hasn't turned on approval decisions by API key), or Operator's `forbidden`: the approval isn't the key's to answer, it is a payment, or the key was created before scopes.","content":{"application/json":{"schema":{"oneOf":[{"$ref":"#/components/schemas/Error"},{"$ref":"#/components/schemas/OperatorError"}]}}}},"404":{"description":"No such approval in your organization.","content":{"application/json":{"schema":{"oneOf":[{"$ref":"#/components/schemas/OperatorError"},{"$ref":"#/components/schemas/Error"}]}}}},"409":{"description":"`conflict`: the approval is no longer open.","content":{"application/json":{"schema":{"oneOf":[{"$ref":"#/components/schemas/OperatorError"},{"$ref":"#/components/schemas/Error"}]}}}},"422":{"description":"`invalid`: the answer isn't one of `allowed_answers`, or the body carries an edited proposal.","content":{"application/json":{"schema":{"oneOf":[{"$ref":"#/components/schemas/OperatorError"},{"$ref":"#/components/schemas/Error"}]}}}},"423":{"$ref":"#/components/responses/AiAppsPaused"},"429":{"$ref":"#/components/responses/RateLimited"},"502":{"description":"Finn Operator is not configured or did not answer (`operator_unavailable`). Safe to retry reads; check state before retrying a write.","content":{"application/json":{"schema":{"allOf":[{"$ref":"#/components/schemas/Error"},{"type":"object","properties":{"error":{"type":"string","enum":["operator_unavailable"]}}}]}}}},"503":{"description":"Finn Operator is not configured or did not answer (`operator_unavailable`). Safe to retry reads; check state before retrying a write.","content":{"application/json":{"schema":{"allOf":[{"$ref":"#/components/schemas/Error"},{"type":"object","properties":{"error":{"type":"string","enum":["operator_unavailable"]}}}]}}}}},"x-required-scope":"approvals:write"}},"/approvals/{id}/approve":{"parameters":[{"name":"id","in":"path","required":true,"description":"Approval ID.","schema":{"type":"string","format":"uuid"},"example":"5d2e8f1a-3b4c-4d6e-9f0a-1b2c3d4e5f6a"},{"$ref":"#/components/parameters/FinnOrg"},{"$ref":"#/components/parameters/FinnVersion"}],"post":{"tags":["Approvals"],"operationId":"approveApproval","summary":"Approve an approval","description":"**Scope:** `approvals:write`, and your organization must have turned on approval decisions by API key (off by default; ask Finn support), else `403 approvals_by_api_off`.\n\nAnswers with the approval's own approve answer, found in `allowed_answers` (`approve`, `approved`, `yes`, `accept` or `confirm`, any case). An approval without one returns `422 no_such_answer` with its `allowed_answers`: use `POST /approvals/{id}/decide`.\n\nAn API key with `approvals:write` decides one approval at a time, as asked: only approvals asked of the `approver` role or of a role that isn't a Finn Operator role (such as `finance`), never one assigned to a named person. It can't decide a payment (`403 forbidden`), can't send an edited proposal (`422 invalid`: edit it in Finn), and can't use bulk decide. The decision is recorded with `decided_by: \"api_key:<key_id>\"` and `decided_via: \"api\"`. A key created before scopes existed can't decide approvals: create a key with `approvals:write`.\n\n**Rate limit:** 120 writes per minute per API key, shared by `POST /contacts`, `PATCH /contacts/{id}`, `POST /contacts/{id}/notes` and the three approval decisions.","requestBody":{"required":false,"content":{"application/json":{"schema":{"type":"object","properties":{"comment":{"type":"string","maxLength":2000,"description":"Optional note kept with the decision."}}},"example":{"comment":"Looks right."}}}},"responses":{"200":{"description":"The approval, decided.","content":{"application/json":{"schema":{"$ref":"#/components/schemas/Approval"}}},"headers":{"RateLimit-Policy":{"$ref":"#/components/headers/RateLimitPolicy"},"RateLimit-Limit":{"$ref":"#/components/headers/RateLimitLimit"},"RateLimit-Remaining":{"$ref":"#/components/headers/RateLimitRemaining"},"RateLimit-Reset":{"$ref":"#/components/headers/RateLimitReset"}}},"400":{"description":"`invalid_id` (not a UUID) or `answer_required`.","content":{"application/json":{"schema":{"oneOf":[{"$ref":"#/components/schemas/Error"},{"$ref":"#/components/schemas/OperatorError"}]}}}},"401":{"$ref":"#/components/responses/Unauthorized"},"403":{"description":"`insufficient_scope` (the key lacks `approvals:write`), `approvals_by_api_off` (your organization hasn't turned on approval decisions by API key), or Operator's `forbidden`: the approval isn't the key's to answer, it is a payment, or the key was created before scopes.","content":{"application/json":{"schema":{"oneOf":[{"$ref":"#/components/schemas/Error"},{"$ref":"#/components/schemas/OperatorError"}]}}}},"404":{"description":"No such approval in your organization.","content":{"application/json":{"schema":{"oneOf":[{"$ref":"#/components/schemas/OperatorError"},{"$ref":"#/components/schemas/Error"}]}}}},"409":{"description":"`conflict`: the approval is no longer open.","content":{"application/json":{"schema":{"oneOf":[{"$ref":"#/components/schemas/OperatorError"},{"$ref":"#/components/schemas/Error"}]}}}},"422":{"description":"`no_such_answer` (no approve answer among `allowed_answers`), or Operator's `invalid`.","content":{"application/json":{"schema":{"oneOf":[{"$ref":"#/components/schemas/Error"},{"$ref":"#/components/schemas/OperatorError"}]}}}},"423":{"$ref":"#/components/responses/AiAppsPaused"},"429":{"$ref":"#/components/responses/RateLimited"},"502":{"description":"Finn Operator is not configured or did not answer (`operator_unavailable`). Safe to retry reads; check state before retrying a write.","content":{"application/json":{"schema":{"allOf":[{"$ref":"#/components/schemas/Error"},{"type":"object","properties":{"error":{"type":"string","enum":["operator_unavailable"]}}}]}}}},"503":{"description":"Finn Operator is not configured or did not answer (`operator_unavailable`). Safe to retry reads; check state before retrying a write.","content":{"application/json":{"schema":{"allOf":[{"$ref":"#/components/schemas/Error"},{"type":"object","properties":{"error":{"type":"string","enum":["operator_unavailable"]}}}]}}}}},"x-required-scope":"approvals:write"}},"/approvals/{id}/reject":{"parameters":[{"name":"id","in":"path","required":true,"description":"Approval ID.","schema":{"type":"string","format":"uuid"},"example":"5d2e8f1a-3b4c-4d6e-9f0a-1b2c3d4e5f6a"},{"$ref":"#/components/parameters/FinnOrg"},{"$ref":"#/components/parameters/FinnVersion"}],"post":{"tags":["Approvals"],"operationId":"rejectApproval","summary":"Reject an approval","description":"**Scope:** `approvals:write`, and your organization must have turned on approval decisions by API key (off by default; ask Finn support), else `403 approvals_by_api_off`.\n\nAnswers with the approval's own reject answer, found in `allowed_answers` (`reject`, `rejected`, `decline`, `declined`, `no` or `deny`, any case). An approval without one returns `422 no_such_answer` with its `allowed_answers`: use `POST /approvals/{id}/decide`.\n\nAn API key with `approvals:write` decides one approval at a time, as asked: only approvals asked of the `approver` role or of a role that isn't a Finn Operator role (such as `finance`), never one assigned to a named person. It can't decide a payment (`403 forbidden`), can't send an edited proposal (`422 invalid`: edit it in Finn), and can't use bulk decide. The decision is recorded with `decided_by: \"api_key:<key_id>\"` and `decided_via: \"api\"`. A key created before scopes existed can't decide approvals: create a key with `approvals:write`.\n\n**Rate limit:** 120 writes per minute per API key, shared by `POST /contacts`, `PATCH /contacts/{id}`, `POST /contacts/{id}/notes` and the three approval decisions.","requestBody":{"required":false,"content":{"application/json":{"schema":{"type":"object","properties":{"comment":{"type":"string","maxLength":2000,"description":"Optional note kept with the decision."}}},"example":{"comment":"Looks right."}}}},"responses":{"200":{"description":"The approval, decided.","content":{"application/json":{"schema":{"$ref":"#/components/schemas/Approval"}}},"headers":{"RateLimit-Policy":{"$ref":"#/components/headers/RateLimitPolicy"},"RateLimit-Limit":{"$ref":"#/components/headers/RateLimitLimit"},"RateLimit-Remaining":{"$ref":"#/components/headers/RateLimitRemaining"},"RateLimit-Reset":{"$ref":"#/components/headers/RateLimitReset"}}},"400":{"description":"`invalid_id` (not a UUID) or `answer_required`.","content":{"application/json":{"schema":{"oneOf":[{"$ref":"#/components/schemas/Error"},{"$ref":"#/components/schemas/OperatorError"}]}}}},"401":{"$ref":"#/components/responses/Unauthorized"},"403":{"description":"`insufficient_scope` (the key lacks `approvals:write`), `approvals_by_api_off` (your organization hasn't turned on approval decisions by API key), or Operator's `forbidden`: the approval isn't the key's to answer, it is a payment, or the key was created before scopes.","content":{"application/json":{"schema":{"oneOf":[{"$ref":"#/components/schemas/Error"},{"$ref":"#/components/schemas/OperatorError"}]}}}},"404":{"description":"No such approval in your organization.","content":{"application/json":{"schema":{"oneOf":[{"$ref":"#/components/schemas/OperatorError"},{"$ref":"#/components/schemas/Error"}]}}}},"409":{"description":"`conflict`: the approval is no longer open.","content":{"application/json":{"schema":{"oneOf":[{"$ref":"#/components/schemas/OperatorError"},{"$ref":"#/components/schemas/Error"}]}}}},"422":{"description":"`no_such_answer` (no reject answer among `allowed_answers`), or Operator's `invalid`.","content":{"application/json":{"schema":{"oneOf":[{"$ref":"#/components/schemas/Error"},{"$ref":"#/components/schemas/OperatorError"}]}}}},"423":{"$ref":"#/components/responses/AiAppsPaused"},"429":{"$ref":"#/components/responses/RateLimited"},"502":{"description":"Finn Operator is not configured or did not answer (`operator_unavailable`). Safe to retry reads; check state before retrying a write.","content":{"application/json":{"schema":{"allOf":[{"$ref":"#/components/schemas/Error"},{"type":"object","properties":{"error":{"type":"string","enum":["operator_unavailable"]}}}]}}}},"503":{"description":"Finn Operator is not configured or did not answer (`operator_unavailable`). Safe to retry reads; check state before retrying a write.","content":{"application/json":{"schema":{"allOf":[{"$ref":"#/components/schemas/Error"},{"type":"object","properties":{"error":{"type":"string","enum":["operator_unavailable"]}}}]}}}}},"x-required-scope":"approvals:write"}},"/agents":{"get":{"tags":["Agents"],"operationId":"listAgents","x-mcp-tool":"list_operator_agents","summary":"List agents","description":"**Scope:** `operator:read` (or `operator:build`). **Needs** building agents by API turned on for your organization.\n\nYour organization's Finn Operator agents, most recently updated first.\n\n**Rate limit:** 300 requests per minute per API key.","parameters":[{"name":"status","in":"query","description":"Only agents with this status.","schema":{"type":"string","enum":["draft","live","paused","archived"]}},{"name":"limit","in":"query","description":"Agents per page, 1 to 100 (default 50).","schema":{"type":"integer","minimum":1,"maximum":100}},{"name":"cursor","in":"query","description":"`next_cursor` of the previous page.","schema":{"type":"string"}}],"responses":{"200":{"description":"A page of agents.","content":{"application/json":{"schema":{"$ref":"#/components/schemas/AgentList"}}},"headers":{"RateLimit-Policy":{"$ref":"#/components/headers/RateLimitPolicy"},"RateLimit-Limit":{"$ref":"#/components/headers/RateLimitLimit"},"RateLimit-Remaining":{"$ref":"#/components/headers/RateLimitRemaining"},"RateLimit-Reset":{"$ref":"#/components/headers/RateLimitReset"}}},"400":{"description":"Invalid input (`invalid_request` with `fields`) or a bad cursor (`invalid_cursor`).","content":{"application/json":{"schema":{"$ref":"#/components/schemas/V1Error"}}}},"401":{"description":"Missing or invalid API key.","content":{"application/json":{"schema":{"$ref":"#/components/schemas/V1Error"}}}},"403":{"description":"`insufficient_scope`, or `agent_build_off` when building agents by API is not turned on for your organization (ask Finn support).","content":{"application/json":{"schema":{"$ref":"#/components/schemas/V1Error"}}}},"404":{"description":"No such agent or case in your organization (`not_found`).","content":{"application/json":{"schema":{"$ref":"#/components/schemas/V1Error"}}}},"429":{"description":"Too many requests (`rate_limited`). Retry after the `Retry-After` header.","content":{"application/json":{"schema":{"$ref":"#/components/schemas/V1Error"}}}},"502":{"description":"Finn Operator did not answer (`operator_unavailable`). Safe to retry reads; check state before retrying a write.","content":{"application/json":{"schema":{"$ref":"#/components/schemas/V1Error"}}}},"503":{"description":"Finn Operator is not configured, or settings could not be read (`operator_unavailable`, `unavailable`).","content":{"application/json":{"schema":{"$ref":"#/components/schemas/V1Error"}}}}},"x-required-scope":"operator:read"},"post":{"tags":["Agents"],"operationId":"createAgent","x-mcp-tool":"draft_operator_agent","summary":"Create an agent","description":"**Scope:** `operator:build`. **Needs** building agents by API turned on for your organization.\n\nCreates a draft agent. It never goes live by API: a person reviews and publishes it in Finn.\n\nSend an `Idempotency-Key` header to make retries safe: a repeat with the same key and body returns the first answer with `Idempotent-Replayed: true`; the same key with a different body answers `422 idempotency_key_reused`.\n\n**Rate limit:** 120 writes per minute per API key.","parameters":[{"name":"Idempotency-Key","in":"header","required":false,"description":"Your unique key for this create, up to 200 characters.","schema":{"type":"string","maxLength":200}}],"requestBody":{"required":true,"content":{"application/json":{"schema":{"type":"object","required":["name"],"properties":{"name":{"type":"string"},"draft":{"type":"object","description":"The draft graph, e.g. `graph` from POST /agents/draft."},"settings":{"type":"object"},"graph":{"type":"object","description":"Same as `draft`, so the answer of POST /agents/draft can be sent back as it is. Send one of `draft` and `graph`, not both (400)."}}}}}},"responses":{"200":{"description":"A replay of an earlier create with the same Idempotency-Key.","content":{"application/json":{"schema":{"$ref":"#/components/schemas/Agent"}}},"headers":{"RateLimit-Policy":{"$ref":"#/components/headers/RateLimitPolicy"},"RateLimit-Limit":{"$ref":"#/components/headers/RateLimitLimit"},"RateLimit-Remaining":{"$ref":"#/components/headers/RateLimitRemaining"},"RateLimit-Reset":{"$ref":"#/components/headers/RateLimitReset"},"ETag":{"description":"The draft revision (`draft_rev`).","schema":{"type":"string"}}}},"201":{"description":"The new draft agent.","content":{"application/json":{"schema":{"$ref":"#/components/schemas/Agent"}}},"headers":{"RateLimit-Policy":{"$ref":"#/components/headers/RateLimitPolicy"},"RateLimit-Limit":{"$ref":"#/components/headers/RateLimitLimit"},"RateLimit-Remaining":{"$ref":"#/components/headers/RateLimitRemaining"},"RateLimit-Reset":{"$ref":"#/components/headers/RateLimitReset"},"ETag":{"description":"The draft revision (`draft_rev`).","schema":{"type":"string"}}}},"400":{"description":"Invalid input (`invalid_request` with `fields`) or a bad cursor (`invalid_cursor`).","content":{"application/json":{"schema":{"$ref":"#/components/schemas/V1Error"}}}},"401":{"description":"Missing or invalid API key.","content":{"application/json":{"schema":{"$ref":"#/components/schemas/V1Error"}}}},"403":{"description":"`insufficient_scope`, or `agent_build_off` when building agents by API is not turned on for your organization (ask Finn support).","content":{"application/json":{"schema":{"$ref":"#/components/schemas/V1Error"}}}},"404":{"description":"No such agent or case in your organization (`not_found`).","content":{"application/json":{"schema":{"$ref":"#/components/schemas/V1Error"}}}},"422":{"description":"`idempotency_key_reused`, or the runtime refused the draft (`invalid`).","content":{"application/json":{"schema":{"$ref":"#/components/schemas/V1Error"}}}},"423":{"$ref":"#/components/responses/AiAppsPaused"},"429":{"description":"Too many requests (`rate_limited`). Retry after the `Retry-After` header.","content":{"application/json":{"schema":{"$ref":"#/components/schemas/V1Error"}}}},"502":{"description":"Finn Operator did not answer (`operator_unavailable`). Safe to retry reads; check state before retrying a write.","content":{"application/json":{"schema":{"$ref":"#/components/schemas/V1Error"}}}},"503":{"description":"Finn Operator is not configured, or settings could not be read (`operator_unavailable`, `unavailable`).","content":{"application/json":{"schema":{"$ref":"#/components/schemas/V1Error"}}}}},"x-required-scope":"operator:build"},"parameters":[{"$ref":"#/components/parameters/FinnOrg"},{"$ref":"#/components/parameters/FinnVersion"}]},"/agents/draft":{"post":{"tags":["Agents"],"operationId":"draftAgent","x-mcp-tool":"draft_operator_agent","summary":"Draft an agent with AI","description":"**Scope:** `operator:build`. **Needs** building agents by API turned on for your organization.\n\nTurns a plain description into a draft graph. Nothing is saved: pass `graph` as `draft` to POST /agents to keep it.\n\n**Rate limit:** 10 AI drafts per minute per API key.","requestBody":{"required":true,"content":{"application/json":{"schema":{"type":"object","required":["description"],"properties":{"description":{"type":"string","maxLength":4000},"language":{"type":"string","description":"Language the agent speaks, e.g. `en`."}}}}}},"responses":{"200":{"description":"The draft.","content":{"application/json":{"schema":{"$ref":"#/components/schemas/AgentDraftResult"}}},"headers":{"RateLimit-Policy":{"$ref":"#/components/headers/RateLimitPolicy"},"RateLimit-Limit":{"$ref":"#/components/headers/RateLimitLimit"},"RateLimit-Remaining":{"$ref":"#/components/headers/RateLimitRemaining"},"RateLimit-Reset":{"$ref":"#/components/headers/RateLimitReset"}}},"400":{"description":"Invalid input (`invalid_request` with `fields`) or a bad cursor (`invalid_cursor`).","content":{"application/json":{"schema":{"$ref":"#/components/schemas/V1Error"}}}},"401":{"description":"Missing or invalid API key.","content":{"application/json":{"schema":{"$ref":"#/components/schemas/V1Error"}}}},"403":{"description":"`insufficient_scope`, or `agent_build_off` when building agents by API is not turned on for your organization (ask Finn support).","content":{"application/json":{"schema":{"$ref":"#/components/schemas/V1Error"}}}},"404":{"description":"No such agent or case in your organization (`not_found`).","content":{"application/json":{"schema":{"$ref":"#/components/schemas/V1Error"}}}},"422":{"description":"The model did not return a usable draft. Rephrase and retry.","content":{"application/json":{"schema":{"$ref":"#/components/schemas/V1Error"}}}},"423":{"$ref":"#/components/responses/AiAppsPaused"},"429":{"description":"Too many requests (`rate_limited`). Retry after the `Retry-After` header.","content":{"application/json":{"schema":{"$ref":"#/components/schemas/V1Error"}}}},"502":{"description":"Finn Operator did not answer (`operator_unavailable`). Safe to retry reads; check state before retrying a write.","content":{"application/json":{"schema":{"$ref":"#/components/schemas/V1Error"}}}},"503":{"description":"Finn Operator is not configured, or settings could not be read (`operator_unavailable`, `unavailable`).","content":{"application/json":{"schema":{"$ref":"#/components/schemas/V1Error"}}}}},"x-required-scope":"operator:build"},"parameters":[{"$ref":"#/components/parameters/FinnOrg"},{"$ref":"#/components/parameters/FinnVersion"}]},"/agents/{id}":{"parameters":[{"name":"id","in":"path","required":true,"description":"The Finn Operator agent's ID.","schema":{"type":"string","format":"uuid"}},{"$ref":"#/components/parameters/FinnOrg"},{"$ref":"#/components/parameters/FinnVersion"}],"get":{"tags":["Agents"],"operationId":"getAgent","summary":"Get an agent","description":"**Scope:** `operator:read` (or `operator:build`). **Needs** building agents by API turned on for your organization.\n\nThe agent with its draft and `draft_rev`.\n\n**Rate limit:** 300 requests per minute per API key.","responses":{"200":{"description":"The agent.","content":{"application/json":{"schema":{"$ref":"#/components/schemas/Agent"}}},"headers":{"RateLimit-Policy":{"$ref":"#/components/headers/RateLimitPolicy"},"RateLimit-Limit":{"$ref":"#/components/headers/RateLimitLimit"},"RateLimit-Remaining":{"$ref":"#/components/headers/RateLimitRemaining"},"RateLimit-Reset":{"$ref":"#/components/headers/RateLimitReset"},"ETag":{"description":"The draft revision (`draft_rev`).","schema":{"type":"string"}}}},"400":{"description":"Invalid input (`invalid_request` with `fields`) or a bad cursor (`invalid_cursor`).","content":{"application/json":{"schema":{"$ref":"#/components/schemas/V1Error"}}}},"401":{"description":"Missing or invalid API key.","content":{"application/json":{"schema":{"$ref":"#/components/schemas/V1Error"}}}},"403":{"description":"`insufficient_scope`, or `agent_build_off` when building agents by API is not turned on for your organization (ask Finn support).","content":{"application/json":{"schema":{"$ref":"#/components/schemas/V1Error"}}}},"404":{"description":"No such agent or case in your organization (`not_found`).","content":{"application/json":{"schema":{"$ref":"#/components/schemas/V1Error"}}}},"429":{"description":"Too many requests (`rate_limited`). Retry after the `Retry-After` header.","content":{"application/json":{"schema":{"$ref":"#/components/schemas/V1Error"}}}},"502":{"description":"Finn Operator did not answer (`operator_unavailable`). Safe to retry reads; check state before retrying a write.","content":{"application/json":{"schema":{"$ref":"#/components/schemas/V1Error"}}}},"503":{"description":"Finn Operator is not configured, or settings could not be read (`operator_unavailable`, `unavailable`).","content":{"application/json":{"schema":{"$ref":"#/components/schemas/V1Error"}}}}},"x-required-scope":"operator:read","x-mcp-tool":"get_operator_agent"}},"/agents/{id}/draft":{"parameters":[{"name":"id","in":"path","required":true,"description":"The Finn Operator agent's ID.","schema":{"type":"string","format":"uuid"}},{"$ref":"#/components/parameters/FinnOrg"},{"$ref":"#/components/parameters/FinnVersion"}],"put":{"tags":["Agents"],"operationId":"saveAgentDraft","summary":"Edit an agent's draft","description":"**Scope:** `operator:build`. **Needs** building agents by API turned on for your organization.\n\nReplaces the draft. The live version, if any, keeps running until a person publishes.\n\nSend `If-Match: \"<draft_rev>\"` to refuse the save when someone else changed the draft since you read it (`409`).\n\n**Rate limit:** 120 writes per minute per API key.","parameters":[{"name":"If-Match","in":"header","required":false,"description":"The `ETag` (draft_rev) you last read.","schema":{"type":"string"}}],"requestBody":{"required":true,"content":{"application/json":{"schema":{"type":"object","required":["draft"],"properties":{"draft":{"type":"object"},"settings":{"type":"object"}}}}}},"responses":{"200":{"description":"The agent with its new draft.","content":{"application/json":{"schema":{"$ref":"#/components/schemas/Agent"}}},"headers":{"RateLimit-Policy":{"$ref":"#/components/headers/RateLimitPolicy"},"RateLimit-Limit":{"$ref":"#/components/headers/RateLimitLimit"},"RateLimit-Remaining":{"$ref":"#/components/headers/RateLimitRemaining"},"RateLimit-Reset":{"$ref":"#/components/headers/RateLimitReset"},"ETag":{"description":"The draft revision (`draft_rev`).","schema":{"type":"string"}}}},"400":{"description":"Invalid input (`invalid_request` with `fields`) or a bad cursor (`invalid_cursor`).","content":{"application/json":{"schema":{"$ref":"#/components/schemas/V1Error"}}}},"401":{"description":"Missing or invalid API key.","content":{"application/json":{"schema":{"$ref":"#/components/schemas/V1Error"}}}},"403":{"description":"`insufficient_scope`, or `agent_build_off` when building agents by API is not turned on for your organization (ask Finn support).","content":{"application/json":{"schema":{"$ref":"#/components/schemas/V1Error"}}}},"404":{"description":"No such agent or case in your organization (`not_found`).","content":{"application/json":{"schema":{"$ref":"#/components/schemas/V1Error"}}}},"409":{"description":"The draft changed since your `If-Match` (`conflict`).","content":{"application/json":{"schema":{"$ref":"#/components/schemas/V1Error"}}}},"423":{"$ref":"#/components/responses/AiAppsPaused"},"429":{"description":"Too many requests (`rate_limited`). Retry after the `Retry-After` header.","content":{"application/json":{"schema":{"$ref":"#/components/schemas/V1Error"}}}},"502":{"description":"Finn Operator did not answer (`operator_unavailable`). Safe to retry reads; check state before retrying a write.","content":{"application/json":{"schema":{"$ref":"#/components/schemas/V1Error"}}}},"503":{"description":"Finn Operator is not configured, or settings could not be read (`operator_unavailable`, `unavailable`).","content":{"application/json":{"schema":{"$ref":"#/components/schemas/V1Error"}}}}},"x-required-scope":"operator:build"}},"/agents/{id}/validate":{"parameters":[{"name":"id","in":"path","required":true,"description":"The Finn Operator agent's ID.","schema":{"type":"string","format":"uuid"}},{"$ref":"#/components/parameters/FinnOrg"},{"$ref":"#/components/parameters/FinnVersion"}],"post":{"tags":["Agents"],"operationId":"validateAgent","summary":"Validate an agent's draft","description":"**Scope:** `operator:build`. **Needs** building agents by API turned on for your organization.\n\nChecks the draft. An empty `problems` list means a person could publish it.\n\n**Rate limit:** 120 writes per minute per API key.","responses":{"200":{"description":"The draft's problems.","content":{"application/json":{"schema":{"type":"object","additionalProperties":true}}},"headers":{"RateLimit-Policy":{"$ref":"#/components/headers/RateLimitPolicy"},"RateLimit-Limit":{"$ref":"#/components/headers/RateLimitLimit"},"RateLimit-Remaining":{"$ref":"#/components/headers/RateLimitRemaining"},"RateLimit-Reset":{"$ref":"#/components/headers/RateLimitReset"}}},"400":{"description":"Invalid input (`invalid_request` with `fields`) or a bad cursor (`invalid_cursor`).","content":{"application/json":{"schema":{"$ref":"#/components/schemas/V1Error"}}}},"401":{"description":"Missing or invalid API key.","content":{"application/json":{"schema":{"$ref":"#/components/schemas/V1Error"}}}},"403":{"description":"`insufficient_scope`, or `agent_build_off` when building agents by API is not turned on for your organization (ask Finn support).","content":{"application/json":{"schema":{"$ref":"#/components/schemas/V1Error"}}}},"404":{"description":"No such agent or case in your organization (`not_found`).","content":{"application/json":{"schema":{"$ref":"#/components/schemas/V1Error"}}}},"423":{"$ref":"#/components/responses/AiAppsPaused"},"429":{"description":"Too many requests (`rate_limited`). Retry after the `Retry-After` header.","content":{"application/json":{"schema":{"$ref":"#/components/schemas/V1Error"}}}},"502":{"description":"Finn Operator did not answer (`operator_unavailable`). Safe to retry reads; check state before retrying a write.","content":{"application/json":{"schema":{"$ref":"#/components/schemas/V1Error"}}}},"503":{"description":"Finn Operator is not configured, or settings could not be read (`operator_unavailable`, `unavailable`).","content":{"application/json":{"schema":{"$ref":"#/components/schemas/V1Error"}}}}},"x-required-scope":"operator:build"}},"/agents/{id}/test":{"parameters":[{"name":"id","in":"path","required":true,"description":"The Finn Operator agent's ID.","schema":{"type":"string","format":"uuid"}},{"$ref":"#/components/parameters/FinnOrg"},{"$ref":"#/components/parameters/FinnVersion"}],"post":{"tags":["Agents"],"operationId":"testAgent","x-mcp-tool":"test_operator_agent","summary":"Start a test run","description":"**Scope:** `operator:build`. **Needs** building agents by API turned on for your organization.\n\nOpens a sandbox test case on the agent's draft. Every side effect is faked: no call, message or email leaves Finn, and nothing is written to records. The case moves only when you drive it with POST /cases/{id}/test/{action}, and is deleted after 24 hours.\n\nWith no `participants`, the case gets a fake person for each participant role the draft uses (\"Test customer\", a reserved +1 555 phone number and an example.com email), so its first Contact runs instead of failing with `no_participant`.\n\n**Rate limit:** 120 writes per minute per API key.","requestBody":{"required":false,"content":{"application/json":{"schema":{"type":"object","properties":{"start":{"type":"string"},"title":{"type":"string"},"subject":{"type":"object"},"participants":{"type":"array","items":{"type":"object"},"description":"People in the case, each with a `role`. Leave out to use fake people for the roles the draft uses."},"memory":{"type":"object"},"trigger":{"type":"object","description":"Run as if the Start's trigger fired with this data."}}}}}},"responses":{"201":{"description":"The test case.","content":{"application/json":{"schema":{"$ref":"#/components/schemas/Case"}}},"headers":{"RateLimit-Policy":{"$ref":"#/components/headers/RateLimitPolicy"},"RateLimit-Limit":{"$ref":"#/components/headers/RateLimitLimit"},"RateLimit-Remaining":{"$ref":"#/components/headers/RateLimitRemaining"},"RateLimit-Reset":{"$ref":"#/components/headers/RateLimitReset"}}},"400":{"description":"Invalid input (`invalid_request` with `fields`) or a bad cursor (`invalid_cursor`).","content":{"application/json":{"schema":{"$ref":"#/components/schemas/V1Error"}}}},"401":{"description":"Missing or invalid API key.","content":{"application/json":{"schema":{"$ref":"#/components/schemas/V1Error"}}}},"403":{"description":"`insufficient_scope`, or `agent_build_off` when building agents by API is not turned on for your organization (ask Finn support).","content":{"application/json":{"schema":{"$ref":"#/components/schemas/V1Error"}}}},"404":{"description":"No such agent or case in your organization (`not_found`).","content":{"application/json":{"schema":{"$ref":"#/components/schemas/V1Error"}}}},"423":{"$ref":"#/components/responses/AiAppsPaused"},"429":{"description":"Too many requests (`rate_limited`). Retry after the `Retry-After` header.","content":{"application/json":{"schema":{"$ref":"#/components/schemas/V1Error"}}}},"502":{"description":"Finn Operator did not answer (`operator_unavailable`). Safe to retry reads; check state before retrying a write.","content":{"application/json":{"schema":{"$ref":"#/components/schemas/V1Error"}}}},"503":{"description":"Finn Operator is not configured, or settings could not be read (`operator_unavailable`, `unavailable`).","content":{"application/json":{"schema":{"$ref":"#/components/schemas/V1Error"}}}}},"x-required-scope":"operator:build"}},"/agents/{id}/archive":{"parameters":[{"name":"id","in":"path","required":true,"description":"The Finn Operator agent's ID.","schema":{"type":"string","format":"uuid"}},{"$ref":"#/components/parameters/FinnOrg"},{"$ref":"#/components/parameters/FinnVersion"}],"post":{"tags":["Agents"],"operationId":"archiveAgent","summary":"Archive an agent","description":"**Scope:** `operator:build`. **Needs** building agents by API turned on for your organization.\n\nArchives the agent: a soft archive, nothing is deleted. An archived agent opens no new case (live, test or by trigger) and can no longer be published or paused; the agent, its versions and its cases stay readable, and cases already open finish on the version they started on (pause it first to hold their messages). There is no delete.\n\nA draft-only agent needs only the scope. An agent with a published version also needs the key's role to allow pausing agents (publisher or admin), else `403 forbidden`.\n\nIdempotent: archiving an archived agent changes nothing and answers `200` with the same agent.\n\n**Rate limit:** 120 writes per minute per API key.","responses":{"200":{"description":"The agent, `status: archived`.","content":{"application/json":{"schema":{"type":"object","additionalProperties":true}}},"headers":{"RateLimit-Policy":{"$ref":"#/components/headers/RateLimitPolicy"},"RateLimit-Limit":{"$ref":"#/components/headers/RateLimitLimit"},"RateLimit-Remaining":{"$ref":"#/components/headers/RateLimitRemaining"},"RateLimit-Reset":{"$ref":"#/components/headers/RateLimitReset"}}},"400":{"description":"Invalid input (`invalid_request` with `fields`) or a bad cursor (`invalid_cursor`).","content":{"application/json":{"schema":{"$ref":"#/components/schemas/V1Error"}}}},"401":{"description":"Missing or invalid API key.","content":{"application/json":{"schema":{"$ref":"#/components/schemas/V1Error"}}}},"403":{"description":"`insufficient_scope`, `agent_build_off` when building agents by API is not turned on for your organization (ask Finn support), or `forbidden` when the agent has a published version and the key's role may not pause agents.","content":{"application/json":{"schema":{"$ref":"#/components/schemas/V1Error"}}}},"404":{"description":"No such agent in your organization (`not_found`).","content":{"application/json":{"schema":{"$ref":"#/components/schemas/V1Error"}}}},"423":{"$ref":"#/components/responses/AiAppsPaused"},"429":{"description":"Too many requests (`rate_limited`). Retry after the `Retry-After` header.","content":{"application/json":{"schema":{"$ref":"#/components/schemas/V1Error"}}}},"502":{"description":"Finn Operator did not answer (`operator_unavailable`). Safe to retry reads; check state before retrying a write.","content":{"application/json":{"schema":{"$ref":"#/components/schemas/V1Error"}}}},"503":{"description":"Finn Operator is not configured, or settings could not be read (`operator_unavailable`, `unavailable`).","content":{"application/json":{"schema":{"$ref":"#/components/schemas/V1Error"}}}}},"x-required-scope":"operator:build","x-mcp-tool":"archive_operator_agent"}},"/agents/{id}/publish":{"parameters":[{"name":"id","in":"path","required":true,"description":"The Finn Operator agent's ID.","schema":{"type":"string","format":"uuid"}},{"$ref":"#/components/parameters/FinnOrg"},{"$ref":"#/components/parameters/FinnVersion"}],"post":{"tags":["Agents"],"operationId":"publishAgent","summary":"Publish an agent (refused)","description":"Always refused for API keys and tokens: only a signed-in person publishes an agent, in Finn.","responses":{"401":{"description":"Missing or invalid API key.","content":{"application/json":{"schema":{"$ref":"#/components/schemas/V1Error"}}}},"403":{"description":"`publish_requires_human` (or `agent_build_off`).","content":{"application/json":{"schema":{"$ref":"#/components/schemas/V1Error"}}}},"423":{"$ref":"#/components/responses/AiAppsPaused"}},"x-required-scope":"none"}},"/cases/{id}/test/{action}":{"parameters":[{"name":"id","in":"path","required":true,"description":"The test case's ID.","schema":{"type":"string","format":"uuid"}},{"name":"action","in":"path","required":true,"description":"`run` until it waits, `step` one block, `reply` as a participant, `skip-wait` past a timer, `restart` from the top (a new case).","schema":{"type":"string","enum":["run","step","reply","skip-wait","restart"]}},{"$ref":"#/components/parameters/FinnOrg"},{"$ref":"#/components/parameters/FinnVersion"}],"post":{"tags":["Agents"],"operationId":"driveAgentTest","x-mcp-tool":"test_operator_agent","summary":"Drive a test run","description":"**Scope:** `operator:build`. **Needs** building agents by API turned on for your organization.\n\nMoves a sandbox test case. Live cases are refused.\n\n**Rate limit:** 120 writes per minute per API key.","requestBody":{"required":false,"content":{"application/json":{"schema":{"type":"object","description":"For `reply` only: the participant's reply.","additionalProperties":true}}}},"responses":{"200":{"description":"The test case.","content":{"application/json":{"schema":{"$ref":"#/components/schemas/Case"}}},"headers":{"RateLimit-Policy":{"$ref":"#/components/headers/RateLimitPolicy"},"RateLimit-Limit":{"$ref":"#/components/headers/RateLimitLimit"},"RateLimit-Remaining":{"$ref":"#/components/headers/RateLimitRemaining"},"RateLimit-Reset":{"$ref":"#/components/headers/RateLimitReset"}}},"400":{"description":"Invalid input (`invalid_request` with `fields`) or a bad cursor (`invalid_cursor`).","content":{"application/json":{"schema":{"$ref":"#/components/schemas/V1Error"}}}},"401":{"description":"Missing or invalid API key.","content":{"application/json":{"schema":{"$ref":"#/components/schemas/V1Error"}}}},"403":{"description":"`insufficient_scope`, or `agent_build_off` when building agents by API is not turned on for your organization (ask Finn support).","content":{"application/json":{"schema":{"$ref":"#/components/schemas/V1Error"}}}},"404":{"description":"No such agent or case in your organization (`not_found`).","content":{"application/json":{"schema":{"$ref":"#/components/schemas/V1Error"}}}},"423":{"$ref":"#/components/responses/AiAppsPaused"},"429":{"description":"Too many requests (`rate_limited`). Retry after the `Retry-After` header.","content":{"application/json":{"schema":{"$ref":"#/components/schemas/V1Error"}}}},"502":{"description":"Finn Operator did not answer (`operator_unavailable`). Safe to retry reads; check state before retrying a write.","content":{"application/json":{"schema":{"$ref":"#/components/schemas/V1Error"}}}},"503":{"description":"Finn Operator is not configured, or settings could not be read (`operator_unavailable`, `unavailable`).","content":{"application/json":{"schema":{"$ref":"#/components/schemas/V1Error"}}}}},"x-required-scope":"operator:build"}},"/chat/channels":{"get":{"tags":["Chat"],"operationId":"listChatChannels","x-mcp-tool":"send_message_to_contact","summary":"List chat channels","description":"**Scope:** `chat:read`.\n\nThe channels connected to this organization's inbox (WhatsApp, website chat, email...). One page; `next_cursor` is always `null`. Keys are snake_case, like the rest of /v1 (a contact's custom `fields` keep the names they were given).\n\n**Rate limit:** 300 requests per minute per API key, shared by every Chat read.","responses":{"200":{"description":"A page of channels.","content":{"application/json":{"schema":{"$ref":"#/components/schemas/ChatList"}}}},"400":{"description":"The request is not valid (`invalid_request`, with `fields`), or `cursor` came from another list or other filters (`invalid_cursor`).","content":{"application/json":{"schema":{"allOf":[{"$ref":"#/components/schemas/V1Error"},{"type":"object","properties":{"error":{"type":"object","properties":{"code":{"type":"string","enum":["invalid_request","invalid_cursor"]}}}}}]}}}},"401":{"description":"Missing, unknown or revoked credentials.","content":{"application/json":{"schema":{"allOf":[{"$ref":"#/components/schemas/V1Error"},{"type":"object","properties":{"error":{"type":"object","properties":{"code":{"type":"string","enum":["api_key_required","invalid_api_key","key_expired"]}}}}}]}}}},"403":{"description":"The key lacks the scope (`insufficient_scope`), or the Chat API is not switched on for this organization (`chat_api_not_enabled`).","content":{"application/json":{"schema":{"allOf":[{"$ref":"#/components/schemas/V1Error"},{"type":"object","properties":{"error":{"type":"object","properties":{"code":{"type":"string","enum":["insufficient_scope","chat_api_not_enabled","credential_not_supported"]}}}}}]}}}},"429":{"$ref":"#/components/responses/RateLimited"},"503":{"description":"Chat is briefly unavailable (`unavailable`). Safe to retry; a send retried with the same `Idempotency-Key` is not sent twice.","content":{"application/json":{"schema":{"allOf":[{"$ref":"#/components/schemas/V1Error"},{"type":"object","properties":{"error":{"type":"object","properties":{"code":{"type":"string","enum":["unavailable"]}}}}}]}}}}},"x-required-scope":"chat:read"},"parameters":[{"$ref":"#/components/parameters/FinnOrg"},{"$ref":"#/components/parameters/FinnVersion"}]},"/chat/conversations":{"get":{"tags":["Chat"],"operationId":"listChatConversations","x-mcp-tool":"list_conversations","summary":"List conversations","description":"**Scope:** `chat:read`.\n\nConversations in the inbox, newest activity first. Finn picks the page size. Pass `next_cursor` back as `cursor` with the same filters; it is `null` on the last page. Keys are snake_case, like the rest of /v1 (a contact's custom `fields` keep the names they were given).\n\n**Rate limit:** 300 requests per minute per API key, shared by every Chat read.","parameters":[{"name":"status","in":"query","description":"Only conversations in this state: `ai` (an agent is answering), `needs_human` (handed off, waiting for a teammate), `human` (a teammate has it), `snoozed`, `closed`, or `all` (the default). The same values a conversation's `status` holds.","schema":{"type":"string","enum":["ai","needs_human","human","snoozed","closed","all"],"default":"all"}},{"name":"channel","in":"query","description":"Only this channel: the same values a conversation's `channel` holds (`widget` is web chat).","schema":{"type":"string","enum":["whatsapp","widget","sms","email","instagram","messenger","tiktok"]}},{"name":"agent_id","in":"query","description":"Only conversations this chat agent handles.","schema":{"type":"string"}},{"name":"label","in":"query","description":"Only conversations with this label.","schema":{"type":"string"}},{"name":"q","in":"query","description":"Search text.","schema":{"type":"string"}},{"name":"from","in":"query","description":"Last activity on or after this ISO 8601 time.","schema":{"type":"string"}},{"name":"to","in":"query","description":"Last activity on or before this ISO 8601 time.","schema":{"type":"string"}},{"name":"limit","in":"query","required":false,"description":"Items per page, 1 to 100. Without it a page is finn-chat's own page size. The next_cursor resumes where this page stopped.","schema":{"type":"integer","minimum":1,"maximum":100}},{"name":"cursor","in":"query","description":"`next_cursor` of the previous page. Send the same filters with it.","schema":{"type":"string"}}],"responses":{"200":{"description":"A page of conversations.","content":{"application/json":{"schema":{"$ref":"#/components/schemas/ChatList"}}}},"400":{"description":"The request is not valid (`invalid_request`, with `fields`), or `cursor` came from another list or other filters (`invalid_cursor`).","content":{"application/json":{"schema":{"allOf":[{"$ref":"#/components/schemas/V1Error"},{"type":"object","properties":{"error":{"type":"object","properties":{"code":{"type":"string","enum":["invalid_request","invalid_cursor"]}}}}}]}}}},"401":{"description":"Missing, unknown or revoked credentials.","content":{"application/json":{"schema":{"allOf":[{"$ref":"#/components/schemas/V1Error"},{"type":"object","properties":{"error":{"type":"object","properties":{"code":{"type":"string","enum":["api_key_required","invalid_api_key","key_expired"]}}}}}]}}}},"403":{"description":"The key lacks the scope (`insufficient_scope`), or the Chat API is not switched on for this organization (`chat_api_not_enabled`).","content":{"application/json":{"schema":{"allOf":[{"$ref":"#/components/schemas/V1Error"},{"type":"object","properties":{"error":{"type":"object","properties":{"code":{"type":"string","enum":["insufficient_scope","chat_api_not_enabled","credential_not_supported"]}}}}}]}}}},"429":{"$ref":"#/components/responses/RateLimited"},"503":{"description":"Chat is briefly unavailable (`unavailable`). Safe to retry; a send retried with the same `Idempotency-Key` is not sent twice.","content":{"application/json":{"schema":{"allOf":[{"$ref":"#/components/schemas/V1Error"},{"type":"object","properties":{"error":{"type":"object","properties":{"code":{"type":"string","enum":["unavailable"]}}}}}]}}}}},"x-required-scope":"chat:read"},"post":{"tags":["Chat"],"operationId":"startChatConversation","x-mcp-tool":"send_message_to_contact","summary":"Start a conversation","description":"**Scope:** `chat:write`. **Idempotency-Key required.**\n\nStarts a conversation with a contact on a channel and sends the first message. Body: `channel`, `from` (a channel id from `GET /chat/channels`), `to` (`{ contact_id }` or `{ phone | email, name? }`), `text` or `template`, optional `subject` and `keep_with_ai` (default true: the channel's AI agent answers replies; false holds the conversation for the API, like a teammate). A channel that is not connected returns `422 not_connected`.\n\n**Rate limit:** 60 writes per minute per API key, shared by every Chat write.","parameters":[{"name":"Idempotency-Key","in":"header","required":true,"description":"Required. Your unique key for this send, 1 to 255 visible ASCII characters (a UUID works well). A retry with the same key and body within 24 hours returns the first response (`Idempotent-Replayed: true`) and sends nothing; the same key with another body returns `422 idempotency_key_reused`.","schema":{"type":"string","minLength":1,"maxLength":255}}],"requestBody":{"required":true,"content":{"application/json":{"schema":{"type":"object","additionalProperties":true}}}},"responses":{"201":{"description":"The conversation and its first message.","content":{"application/json":{"schema":{"type":"object","additionalProperties":true}}}},"202":{"$ref":"#/components/responses/PendingApproval"},"400":{"description":"The request is not valid (`invalid_request`, with `fields`), or `cursor` came from another list or other filters (`invalid_cursor`).","content":{"application/json":{"schema":{"allOf":[{"$ref":"#/components/schemas/V1Error"},{"type":"object","properties":{"error":{"type":"object","properties":{"code":{"type":"string","enum":["invalid_request","invalid_cursor"]}}}}}]}}}},"401":{"description":"Missing, unknown or revoked credentials.","content":{"application/json":{"schema":{"allOf":[{"$ref":"#/components/schemas/V1Error"},{"type":"object","properties":{"error":{"type":"object","properties":{"code":{"type":"string","enum":["api_key_required","invalid_api_key","key_expired"]}}}}}]}}}},"403":{"description":"The key lacks the scope (`insufficient_scope`), or the Chat API is not switched on for this organization (`chat_api_not_enabled`). An OAuth token or MCP key cannot write chat yet (`credential_not_supported`).","content":{"application/json":{"schema":{"allOf":[{"$ref":"#/components/schemas/V1Error"},{"type":"object","properties":{"error":{"type":"object","properties":{"code":{"type":"string","enum":["insufficient_scope","chat_api_not_enabled","credential_not_supported"]}}}}}]}}}},"422":{"description":"The channel is not connected (`not_connected`), or the key was used with another body (`idempotency_key_reused`).","content":{"application/json":{"schema":{"$ref":"#/components/schemas/V1Error"}}}},"423":{"$ref":"#/components/responses/AiAppsPaused"},"429":{"$ref":"#/components/responses/RateLimited"},"503":{"description":"Chat is briefly unavailable (`unavailable`). Safe to retry; a send retried with the same `Idempotency-Key` is not sent twice.","content":{"application/json":{"schema":{"allOf":[{"$ref":"#/components/schemas/V1Error"},{"type":"object","properties":{"error":{"type":"object","properties":{"code":{"type":"string","enum":["unavailable"]}}}}}]}}}}},"x-required-scope":"chat:write","x-finn-hold":{"action":"chat.start","class":"outward"}},"parameters":[{"$ref":"#/components/parameters/FinnOrg"},{"$ref":"#/components/parameters/FinnVersion"}]},"/chat/conversations/{id}":{"get":{"tags":["Chat"],"operationId":"getChatConversation","x-mcp-tool":"read_conversation","summary":"Get a conversation","description":"**Scope:** `chat:read`.\n\nOne conversation with its latest messages. Pass `messages_next_cursor` as `cursor` to `GET /chat/conversations/{id}/messages` for older ones. Keys are snake_case, like the rest of /v1 (a contact's custom `fields` keep the names they were given).\n\n**Rate limit:** 300 requests per minute per API key, shared by every Chat read.","parameters":[{"name":"id","in":"path","required":true,"description":"The conversation id (digits).","schema":{"type":"string","pattern":"^\\d{1,12}$"}}],"responses":{"200":{"description":"The conversation.","content":{"application/json":{"schema":{"type":"object","required":["conversation","messages","messages_next_cursor"],"properties":{"conversation":{"type":"object","additionalProperties":true},"messages":{"type":"array","items":{"type":"object","additionalProperties":true}},"messages_next_cursor":{"type":["string","null"]}}}}}},"400":{"description":"The request is not valid (`invalid_request`, with `fields`), or `cursor` came from another list or other filters (`invalid_cursor`).","content":{"application/json":{"schema":{"allOf":[{"$ref":"#/components/schemas/V1Error"},{"type":"object","properties":{"error":{"type":"object","properties":{"code":{"type":"string","enum":["invalid_request","invalid_cursor"]}}}}}]}}}},"401":{"description":"Missing, unknown or revoked credentials.","content":{"application/json":{"schema":{"allOf":[{"$ref":"#/components/schemas/V1Error"},{"type":"object","properties":{"error":{"type":"object","properties":{"code":{"type":"string","enum":["api_key_required","invalid_api_key","key_expired"]}}}}}]}}}},"403":{"description":"The key lacks the scope (`insufficient_scope`), or the Chat API is not switched on for this organization (`chat_api_not_enabled`).","content":{"application/json":{"schema":{"allOf":[{"$ref":"#/components/schemas/V1Error"},{"type":"object","properties":{"error":{"type":"object","properties":{"code":{"type":"string","enum":["insufficient_scope","chat_api_not_enabled","credential_not_supported"]}}}}}]}}}},"404":{"description":"No such conversation in your organization.","content":{"application/json":{"schema":{"allOf":[{"$ref":"#/components/schemas/V1Error"},{"type":"object","properties":{"error":{"type":"object","properties":{"code":{"type":"string","enum":["conversation_not_found"]}}}}}]}}}},"429":{"$ref":"#/components/responses/RateLimited"},"503":{"description":"Chat is briefly unavailable (`unavailable`). Safe to retry; a send retried with the same `Idempotency-Key` is not sent twice.","content":{"application/json":{"schema":{"allOf":[{"$ref":"#/components/schemas/V1Error"},{"type":"object","properties":{"error":{"type":"object","properties":{"code":{"type":"string","enum":["unavailable"]}}}}}]}}}}},"x-required-scope":"chat:read"},"parameters":[{"$ref":"#/components/parameters/FinnOrg"},{"$ref":"#/components/parameters/FinnVersion"}]},"/chat/conversations/{id}/messages":{"get":{"tags":["Chat"],"operationId":"listChatMessages","x-mcp-tool":"read_conversation","summary":"List messages","description":"**Scope:** `chat:read`.\n\nA conversation's messages, newest first. Keys are snake_case, like the rest of /v1 (a contact's custom `fields` keep the names they were given).\n\n**Rate limit:** 300 requests per minute per API key, shared by every Chat read.","parameters":[{"name":"id","in":"path","required":true,"description":"The conversation id (digits).","schema":{"type":"string","pattern":"^\\d{1,12}$"}},{"name":"limit","in":"query","required":false,"description":"Items per page, 1 to 100. Without it a page is finn-chat's own page size. The next_cursor resumes where this page stopped.","schema":{"type":"integer","minimum":1,"maximum":100}},{"name":"cursor","in":"query","description":"`next_cursor` of the previous page. Send the same filters with it.","schema":{"type":"string"}}],"responses":{"200":{"description":"A page of messages.","content":{"application/json":{"schema":{"$ref":"#/components/schemas/ChatList"}}}},"400":{"description":"The request is not valid (`invalid_request`, with `fields`), or `cursor` came from another list or other filters (`invalid_cursor`).","content":{"application/json":{"schema":{"allOf":[{"$ref":"#/components/schemas/V1Error"},{"type":"object","properties":{"error":{"type":"object","properties":{"code":{"type":"string","enum":["invalid_request","invalid_cursor"]}}}}}]}}}},"401":{"description":"Missing, unknown or revoked credentials.","content":{"application/json":{"schema":{"allOf":[{"$ref":"#/components/schemas/V1Error"},{"type":"object","properties":{"error":{"type":"object","properties":{"code":{"type":"string","enum":["api_key_required","invalid_api_key","key_expired"]}}}}}]}}}},"403":{"description":"The key lacks the scope (`insufficient_scope`), or the Chat API is not switched on for this organization (`chat_api_not_enabled`).","content":{"application/json":{"schema":{"allOf":[{"$ref":"#/components/schemas/V1Error"},{"type":"object","properties":{"error":{"type":"object","properties":{"code":{"type":"string","enum":["insufficient_scope","chat_api_not_enabled","credential_not_supported"]}}}}}]}}}},"404":{"description":"No such conversation in your organization.","content":{"application/json":{"schema":{"allOf":[{"$ref":"#/components/schemas/V1Error"},{"type":"object","properties":{"error":{"type":"object","properties":{"code":{"type":"string","enum":["conversation_not_found"]}}}}}]}}}},"429":{"$ref":"#/components/responses/RateLimited"},"503":{"description":"Chat is briefly unavailable (`unavailable`). Safe to retry; a send retried with the same `Idempotency-Key` is not sent twice.","content":{"application/json":{"schema":{"allOf":[{"$ref":"#/components/schemas/V1Error"},{"type":"object","properties":{"error":{"type":"object","properties":{"code":{"type":"string","enum":["unavailable"]}}}}}]}}}}},"x-required-scope":"chat:read"},"post":{"tags":["Chat"],"operationId":"sendChatMessage","x-mcp-tool":"send_message_to_contact","summary":"Send a message","description":"**Scope:** `chat:write`. **Idempotency-Key required.**\n\nSends a message in a conversation, shown in the inbox as sent by the API key. Body: `{ text }` or `{ template: { name, language, body?, header?, buttons? } }`. A retry with the same key never sends twice.\n\n**Sending takes the conversation.** A person (you, through the API) is now answering, so the conversation's `status` becomes `human` and the AI agent stops replying to it. The answer says so (`ai_paused: true` and a `notice`). To give it back to the AI, a teammate clicks **Hand back** on the conversation in Finn's chat inbox; there is no API for that yet.\n\n**Rate limit:** 60 writes per minute per API key, shared by every Chat write.","parameters":[{"name":"id","in":"path","required":true,"description":"The conversation id (digits).","schema":{"type":"string","pattern":"^\\d{1,12}$"}},{"name":"Idempotency-Key","in":"header","required":true,"description":"Required. Your unique key for this send, 1 to 255 visible ASCII characters (a UUID works well). A retry with the same key and body within 24 hours returns the first response (`Idempotent-Replayed: true`) and sends nothing; the same key with another body returns `422 idempotency_key_reused`.","schema":{"type":"string","minLength":1,"maxLength":255}}],"requestBody":{"required":true,"content":{"application/json":{"schema":{"type":"object","additionalProperties":true}}}},"responses":{"201":{"description":"The message was sent, and the conversation is now handled by a person.","content":{"application/json":{"schema":{"type":"object","additionalProperties":true,"properties":{"message":{"type":"object","additionalProperties":true,"description":"The sent message."},"ai_paused":{"type":"boolean","description":"Always `true`: the conversation's status is now `human` and the AI agent stops replying until a teammate hands it back in Finn."},"notice":{"type":"string","description":"The same, in words, with how to hand the conversation back to the AI."}}}}}},"202":{"$ref":"#/components/responses/PendingApproval"},"400":{"description":"The request is not valid (`invalid_request`, with `fields`), or `cursor` came from another list or other filters (`invalid_cursor`).","content":{"application/json":{"schema":{"allOf":[{"$ref":"#/components/schemas/V1Error"},{"type":"object","properties":{"error":{"type":"object","properties":{"code":{"type":"string","enum":["invalid_request","invalid_cursor"]}}}}}]}}}},"401":{"description":"Missing, unknown or revoked credentials.","content":{"application/json":{"schema":{"allOf":[{"$ref":"#/components/schemas/V1Error"},{"type":"object","properties":{"error":{"type":"object","properties":{"code":{"type":"string","enum":["api_key_required","invalid_api_key","key_expired"]}}}}}]}}}},"403":{"description":"The key lacks the scope (`insufficient_scope`), or the Chat API is not switched on for this organization (`chat_api_not_enabled`). An OAuth token or MCP key cannot write chat yet (`credential_not_supported`).","content":{"application/json":{"schema":{"allOf":[{"$ref":"#/components/schemas/V1Error"},{"type":"object","properties":{"error":{"type":"object","properties":{"code":{"type":"string","enum":["insufficient_scope","chat_api_not_enabled","credential_not_supported"]}}}}}]}}}},"404":{"description":"No such conversation in your organization.","content":{"application/json":{"schema":{"allOf":[{"$ref":"#/components/schemas/V1Error"},{"type":"object","properties":{"error":{"type":"object","properties":{"code":{"type":"string","enum":["conversation_not_found"]}}}}}]}}}},"422":{"description":"The channel is not connected (`not_connected`), or the key was used with another body (`idempotency_key_reused`).","content":{"application/json":{"schema":{"$ref":"#/components/schemas/V1Error"}}}},"423":{"$ref":"#/components/responses/AiAppsPaused"},"429":{"$ref":"#/components/responses/RateLimited"},"503":{"description":"Chat is briefly unavailable (`unavailable`). Safe to retry; a send retried with the same `Idempotency-Key` is not sent twice.","content":{"application/json":{"schema":{"allOf":[{"$ref":"#/components/schemas/V1Error"},{"type":"object","properties":{"error":{"type":"object","properties":{"code":{"type":"string","enum":["unavailable"]}}}}}]}}}}},"x-required-scope":"chat:write","x-finn-hold":{"action":"chat.send","class":"outward"}},"parameters":[{"$ref":"#/components/parameters/FinnOrg"},{"$ref":"#/components/parameters/FinnVersion"}]},"/chat/contacts":{"get":{"tags":["Chat"],"operationId":"listChatContacts","summary":"List chat contacts","description":"**Scope:** `chat:read`.\n\nPeople the inbox has talked to. Adds `total`, the number of matching contacts. These are the inbox's contacts, not CRM records. Keys are snake_case, like the rest of /v1 (a contact's custom `fields` keep the names they were given).\n\n**Rate limit:** 300 requests per minute per API key, shared by every Chat read.","parameters":[{"name":"q","in":"query","description":"Search name, phone or email.","schema":{"type":"string"}},{"name":"label","in":"query","description":"Only contacts with this label.","schema":{"type":"string"}},{"name":"type","in":"query","description":"Only this contact type.","schema":{"type":"string"}},{"name":"channel","in":"query","description":"Only contacts on this channel id.","schema":{"type":"string"}},{"name":"limit","in":"query","required":false,"description":"Items per page, 1 to 100. Without it a page is finn-chat's own page size. The next_cursor resumes where this page stopped.","schema":{"type":"integer","minimum":1,"maximum":100}},{"name":"cursor","in":"query","description":"`next_cursor` of the previous page. Send the same filters with it.","schema":{"type":"string"}}],"responses":{"200":{"description":"A page of contacts.","content":{"application/json":{"schema":{"allOf":[{"$ref":"#/components/schemas/ChatList"},{"type":"object","properties":{"total":{"type":["integer","null"]}}}]}}}},"400":{"description":"The request is not valid (`invalid_request`, with `fields`), or `cursor` came from another list or other filters (`invalid_cursor`).","content":{"application/json":{"schema":{"allOf":[{"$ref":"#/components/schemas/V1Error"},{"type":"object","properties":{"error":{"type":"object","properties":{"code":{"type":"string","enum":["invalid_request","invalid_cursor"]}}}}}]}}}},"401":{"description":"Missing, unknown or revoked credentials.","content":{"application/json":{"schema":{"allOf":[{"$ref":"#/components/schemas/V1Error"},{"type":"object","properties":{"error":{"type":"object","properties":{"code":{"type":"string","enum":["api_key_required","invalid_api_key","key_expired"]}}}}}]}}}},"403":{"description":"The key lacks the scope (`insufficient_scope`), or the Chat API is not switched on for this organization (`chat_api_not_enabled`).","content":{"application/json":{"schema":{"allOf":[{"$ref":"#/components/schemas/V1Error"},{"type":"object","properties":{"error":{"type":"object","properties":{"code":{"type":"string","enum":["insufficient_scope","chat_api_not_enabled","credential_not_supported"]}}}}}]}}}},"429":{"$ref":"#/components/responses/RateLimited"},"503":{"description":"Chat is briefly unavailable (`unavailable`). Safe to retry; a send retried with the same `Idempotency-Key` is not sent twice.","content":{"application/json":{"schema":{"allOf":[{"$ref":"#/components/schemas/V1Error"},{"type":"object","properties":{"error":{"type":"object","properties":{"code":{"type":"string","enum":["unavailable"]}}}}}]}}}}},"x-required-scope":"chat:read","x-mcp-tool":"list_chat_contacts"},"parameters":[{"$ref":"#/components/parameters/FinnOrg"},{"$ref":"#/components/parameters/FinnVersion"}]},"/chat/contacts/{id}":{"get":{"tags":["Chat"],"operationId":"getChatContact","summary":"Get a chat contact","description":"**Scope:** `chat:read`.\n\nKeys are snake_case, like the rest of /v1 (a contact's custom `fields` keep the names they were given).\n\n**Rate limit:** 300 requests per minute per API key, shared by every Chat read.","parameters":[{"name":"id","in":"path","required":true,"description":"The contact id (digits).","schema":{"type":"string","pattern":"^\\d{1,12}$"}}],"responses":{"200":{"description":"The contact.","content":{"application/json":{"schema":{"type":"object","additionalProperties":true}}}},"400":{"description":"The request is not valid (`invalid_request`, with `fields`), or `cursor` came from another list or other filters (`invalid_cursor`).","content":{"application/json":{"schema":{"allOf":[{"$ref":"#/components/schemas/V1Error"},{"type":"object","properties":{"error":{"type":"object","properties":{"code":{"type":"string","enum":["invalid_request","invalid_cursor"]}}}}}]}}}},"401":{"description":"Missing, unknown or revoked credentials.","content":{"application/json":{"schema":{"allOf":[{"$ref":"#/components/schemas/V1Error"},{"type":"object","properties":{"error":{"type":"object","properties":{"code":{"type":"string","enum":["api_key_required","invalid_api_key","key_expired"]}}}}}]}}}},"403":{"description":"The key lacks the scope (`insufficient_scope`), or the Chat API is not switched on for this organization (`chat_api_not_enabled`).","content":{"application/json":{"schema":{"allOf":[{"$ref":"#/components/schemas/V1Error"},{"type":"object","properties":{"error":{"type":"object","properties":{"code":{"type":"string","enum":["insufficient_scope","chat_api_not_enabled","credential_not_supported"]}}}}}]}}}},"404":{"description":"No such contact in your organization.","content":{"application/json":{"schema":{"allOf":[{"$ref":"#/components/schemas/V1Error"},{"type":"object","properties":{"error":{"type":"object","properties":{"code":{"type":"string","enum":["contact_not_found"]}}}}}]}}}},"429":{"$ref":"#/components/responses/RateLimited"},"503":{"description":"Chat is briefly unavailable (`unavailable`). Safe to retry; a send retried with the same `Idempotency-Key` is not sent twice.","content":{"application/json":{"schema":{"allOf":[{"$ref":"#/components/schemas/V1Error"},{"type":"object","properties":{"error":{"type":"object","properties":{"code":{"type":"string","enum":["unavailable"]}}}}}]}}}}},"x-required-scope":"chat:read","x-mcp-tool":"get_chat_contact"},"patch":{"tags":["Chat"],"operationId":"updateChatContact","summary":"Update a chat contact","description":"**Scope:** `chat:write`.\n\nChanges the fields sent: any of `name`, `email`, `phone`, `company`, `type`, `fields`.\n\n**Approval:** where your organization has turned on approval holds for chat contact changes (`v1_hold_chat_contact_updates`, off by default; ask Finn support), a member's OAuth token or MCP key may call this too, and the change waits for that member to approve it in Finn: the answer is `202` with a `PendingApproval` and nothing has changed yet. An org key changes the contact at once unless your organization holds org keys for `chat.contact_update` too. Send an `Idempotency-Key` so a retry returns the same approval instead of asking again.\n\n**Rate limit:** 60 writes per minute per API key, shared by every Chat write.","parameters":[{"name":"id","in":"path","required":true,"description":"The contact id (digits).","schema":{"type":"string","pattern":"^\\d{1,12}$"}}],"requestBody":{"required":true,"content":{"application/json":{"schema":{"type":"object","additionalProperties":true}}}},"responses":{"200":{"description":"The contact.","content":{"application/json":{"schema":{"type":"object","additionalProperties":true}}}},"202":{"$ref":"#/components/responses/PendingApproval"},"400":{"description":"The request is not valid (`invalid_request`, with `fields`), or `cursor` came from another list or other filters (`invalid_cursor`).","content":{"application/json":{"schema":{"allOf":[{"$ref":"#/components/schemas/V1Error"},{"type":"object","properties":{"error":{"type":"object","properties":{"code":{"type":"string","enum":["invalid_request","invalid_cursor"]}}}}}]}}}},"401":{"description":"Missing, unknown or revoked credentials.","content":{"application/json":{"schema":{"allOf":[{"$ref":"#/components/schemas/V1Error"},{"type":"object","properties":{"error":{"type":"object","properties":{"code":{"type":"string","enum":["api_key_required","invalid_api_key","key_expired"]}}}}}]}}}},"403":{"description":"The key lacks the scope (`insufficient_scope`), or the Chat API is not switched on for this organization (`chat_api_not_enabled`). An OAuth token or MCP key cannot write chat yet (`credential_not_supported`).","content":{"application/json":{"schema":{"allOf":[{"$ref":"#/components/schemas/V1Error"},{"type":"object","properties":{"error":{"type":"object","properties":{"code":{"type":"string","enum":["insufficient_scope","chat_api_not_enabled","credential_not_supported"]}}}}}]}}}},"404":{"description":"No such contact in your organization.","content":{"application/json":{"schema":{"allOf":[{"$ref":"#/components/schemas/V1Error"},{"type":"object","properties":{"error":{"type":"object","properties":{"code":{"type":"string","enum":["contact_not_found"]}}}}}]}}}},"423":{"$ref":"#/components/responses/AiAppsPaused"},"429":{"$ref":"#/components/responses/RateLimited"},"503":{"description":"Chat is briefly unavailable (`unavailable`). Safe to retry; a send retried with the same `Idempotency-Key` is not sent twice.","content":{"application/json":{"schema":{"allOf":[{"$ref":"#/components/schemas/V1Error"},{"type":"object","properties":{"error":{"type":"object","properties":{"code":{"type":"string","enum":["unavailable"]}}}}}]}}}}},"x-required-scope":"chat:write","x-mcp-tool":"update_chat_contact","x-finn-hold":{"action":"chat.contact_update","class":"outward","feature":"v1_hold_chat_contact_updates"}},"parameters":[{"$ref":"#/components/parameters/FinnOrg"},{"$ref":"#/components/parameters/FinnVersion"}]},"/webhook_endpoints":{"get":{"tags":["Webhooks"],"operationId":"listWebhookEndpoints","summary":"List webhook endpoints","description":"**Scope:** `admin:webhooks`. **Needs** webhook endpoints by API turned on for your organization (ask Finn support).\n\nThe organization's webhook endpoints, newest first, without their secrets (also those made in the dashboard).\n\n**Rate limit:** 300 requests per minute per API key.","x-required-scope":"admin:webhooks","parameters":[{"name":"limit","in":"query","description":"Items per page, 1 to 200. Default 50.","schema":{"type":"integer","minimum":1,"maximum":200,"default":50}},{"name":"cursor","in":"query","description":"`next_cursor` of the previous page. Send the same filters with it.","schema":{"type":"string"}}],"responses":{"200":{"description":"A page of endpoints.","headers":{"RateLimit-Policy":{"$ref":"#/components/headers/RateLimitPolicy"},"RateLimit-Limit":{"$ref":"#/components/headers/RateLimitLimit"},"RateLimit-Remaining":{"$ref":"#/components/headers/RateLimitRemaining"},"RateLimit-Reset":{"$ref":"#/components/headers/RateLimitReset"}},"content":{"application/json":{"schema":{"$ref":"#/components/schemas/WebhookEndpointList"}}}},"400":{"description":"Invalid input (`invalid_request` with `fields`) or a bad cursor (`invalid_cursor`).","content":{"application/json":{"schema":{"$ref":"#/components/schemas/V1Error"}}}},"401":{"description":"Missing or invalid API key (`api_key_required`, `invalid_api_key`).","content":{"application/json":{"schema":{"$ref":"#/components/schemas/V1Error"}}}},"403":{"description":"`insufficient_scope` (the key lacks `admin:webhooks`), or `webhooks_api_not_enabled` when webhook endpoints by API are not turned on for your organization.","content":{"application/json":{"schema":{"$ref":"#/components/schemas/V1Error"}}}},"429":{"description":"Too many requests (`rate_limited`). Retry after the `Retry-After` header.","content":{"application/json":{"schema":{"$ref":"#/components/schemas/V1Error"}}}},"503":{"description":"Settings could not be read (`unavailable`). Retry shortly.","content":{"application/json":{"schema":{"$ref":"#/components/schemas/V1Error"}}}}}},"post":{"tags":["Webhooks"],"operationId":"createWebhookEndpoint","summary":"Create a webhook endpoint","description":"**Scope:** `admin:webhooks`. **Needs** webhook endpoints by API turned on for your organization (ask Finn support).\n\nAdds an endpoint and returns its signing secret (`whsec_...`) **once**: store it, Finn never shows it again. At most 5 endpoints per organization. The URL must be `https` and resolve to a public address. An endpoint gets only events that happen after it is created. `Idempotency-Key`: when your organization has it switched on (ask Finn support), create needs the header (`400` without it) and a retry with the same key and body within 24 hours returns the first endpoint with its secret (`Idempotent-Replayed: true`; `secret` is `null` if the endpoint was deleted since) instead of creating another. Otherwise the header is not used: if a create times out, list the endpoints before retrying.\n\n**Rate limit:** 60 writes per minute per API key.","x-required-scope":"admin:webhooks","parameters":[{"name":"Idempotency-Key","in":"header","required":false,"description":"Required when your organization has it switched on; otherwise not used. Your unique key for this create, 1 to 255 visible ASCII characters (a UUID works well). A retry with the same key and body within 24 hours returns the first endpoint and its secret; the same key with another body returns `422 idempotency_key_reused`.","schema":{"type":"string","minLength":1,"maxLength":255}}],"requestBody":{"required":true,"content":{"application/json":{"schema":{"$ref":"#/components/schemas/CreateWebhookEndpointRequest"},"example":{"url":"https://example.com/finn/webhooks","events":["call.completed","contact.stage_changed"]}}}},"responses":{"201":{"description":"The endpoint, with its secret.","headers":{"RateLimit-Policy":{"$ref":"#/components/headers/RateLimitPolicy"},"RateLimit-Limit":{"$ref":"#/components/headers/RateLimitLimit"},"RateLimit-Remaining":{"$ref":"#/components/headers/RateLimitRemaining"},"RateLimit-Reset":{"$ref":"#/components/headers/RateLimitReset"}},"content":{"application/json":{"schema":{"$ref":"#/components/schemas/WebhookEndpointWithSecret"}}}},"400":{"description":"Invalid input (`invalid_request` with `fields`): a missing or non-public `url` (`invalid_url`), an unknown event (`unknown_event`), an unknown field. Also a missing `Idempotency-Key` when your organization requires it, or one that is not 1 to 255 visible ASCII characters.","content":{"application/json":{"schema":{"$ref":"#/components/schemas/V1Error"}}}},"401":{"description":"Missing or invalid API key (`api_key_required`, `invalid_api_key`).","content":{"application/json":{"schema":{"$ref":"#/components/schemas/V1Error"}}}},"403":{"description":"`insufficient_scope` (the key lacks `admin:webhooks`), or `webhooks_api_not_enabled` when webhook endpoints by API are not turned on for your organization.","content":{"application/json":{"schema":{"$ref":"#/components/schemas/V1Error"}}}},"409":{"description":"The organization already has 5 endpoints (`endpoint_limit_reached`), or a create with the same `Idempotency-Key` is still running (`idempotency_in_progress`; retry after `Retry-After` seconds).","content":{"application/json":{"schema":{"$ref":"#/components/schemas/V1Error"}}}},"422":{"description":"This `Idempotency-Key` was already used with a different request (`idempotency_key_reused`).","content":{"application/json":{"schema":{"$ref":"#/components/schemas/V1Error"}}}},"429":{"description":"Too many requests (`rate_limited`). Retry after the `Retry-After` header.","content":{"application/json":{"schema":{"$ref":"#/components/schemas/V1Error"}}}},"503":{"description":"Settings could not be read (`unavailable`). Retry shortly.","content":{"application/json":{"schema":{"$ref":"#/components/schemas/V1Error"}}}}}},"parameters":[{"$ref":"#/components/parameters/FinnOrg"},{"$ref":"#/components/parameters/FinnVersion"}]},"/webhook_endpoints/{id}":{"get":{"tags":["Webhooks"],"operationId":"getWebhookEndpoint","summary":"Get a webhook endpoint","description":"**Scope:** `admin:webhooks`. **Needs** webhook endpoints by API turned on for your organization (ask Finn support).\n\nOne endpoint, without its secret.\n\n**Rate limit:** 300 requests per minute per API key.","x-required-scope":"admin:webhooks","parameters":[{"name":"id","in":"path","required":true,"description":"The webhook endpoint's id.","schema":{"type":"string","format":"uuid"}}],"responses":{"200":{"description":"The endpoint.","headers":{"RateLimit-Policy":{"$ref":"#/components/headers/RateLimitPolicy"},"RateLimit-Limit":{"$ref":"#/components/headers/RateLimitLimit"},"RateLimit-Remaining":{"$ref":"#/components/headers/RateLimitRemaining"},"RateLimit-Reset":{"$ref":"#/components/headers/RateLimitReset"}},"content":{"application/json":{"schema":{"$ref":"#/components/schemas/WebhookEndpoint"}}}},"401":{"description":"Missing or invalid API key (`api_key_required`, `invalid_api_key`).","content":{"application/json":{"schema":{"$ref":"#/components/schemas/V1Error"}}}},"403":{"description":"`insufficient_scope` (the key lacks `admin:webhooks`), or `webhooks_api_not_enabled` when webhook endpoints by API are not turned on for your organization.","content":{"application/json":{"schema":{"$ref":"#/components/schemas/V1Error"}}}},"404":{"description":"No such webhook endpoint in your organization (`webhook_endpoint_not_found`).","content":{"application/json":{"schema":{"$ref":"#/components/schemas/V1Error"}}}},"429":{"description":"Too many requests (`rate_limited`). Retry after the `Retry-After` header.","content":{"application/json":{"schema":{"$ref":"#/components/schemas/V1Error"}}}},"503":{"description":"Settings could not be read (`unavailable`). Retry shortly.","content":{"application/json":{"schema":{"$ref":"#/components/schemas/V1Error"}}}}}},"patch":{"tags":["Webhooks"],"operationId":"updateWebhookEndpoint","summary":"Update, enable or disable a webhook endpoint","description":"**Scope:** `admin:webhooks`. **Needs** webhook endpoints by API turned on for your organization (ask Finn support).\n\nChange the URL or the events, or switch the endpoint off (`enabled: false`) and on again (`enabled: true`, which also clears `failure_count`). The secret does not change.\n\n**Rate limit:** 60 writes per minute per API key.","x-required-scope":"admin:webhooks","parameters":[{"name":"id","in":"path","required":true,"description":"The webhook endpoint's id.","schema":{"type":"string","format":"uuid"}}],"requestBody":{"required":true,"content":{"application/json":{"schema":{"$ref":"#/components/schemas/UpdateWebhookEndpointRequest"},"example":{"enabled":false}}}},"responses":{"200":{"description":"The endpoint after the change.","headers":{"RateLimit-Policy":{"$ref":"#/components/headers/RateLimitPolicy"},"RateLimit-Limit":{"$ref":"#/components/headers/RateLimitLimit"},"RateLimit-Remaining":{"$ref":"#/components/headers/RateLimitRemaining"},"RateLimit-Reset":{"$ref":"#/components/headers/RateLimitReset"}},"content":{"application/json":{"schema":{"$ref":"#/components/schemas/WebhookEndpoint"}}}},"400":{"description":"Invalid input (`invalid_request` with `fields`).","content":{"application/json":{"schema":{"$ref":"#/components/schemas/V1Error"}}}},"401":{"description":"Missing or invalid API key (`api_key_required`, `invalid_api_key`).","content":{"application/json":{"schema":{"$ref":"#/components/schemas/V1Error"}}}},"403":{"description":"`insufficient_scope` (the key lacks `admin:webhooks`), or `webhooks_api_not_enabled` when webhook endpoints by API are not turned on for your organization.","content":{"application/json":{"schema":{"$ref":"#/components/schemas/V1Error"}}}},"404":{"description":"No such webhook endpoint in your organization (`webhook_endpoint_not_found`).","content":{"application/json":{"schema":{"$ref":"#/components/schemas/V1Error"}}}},"429":{"description":"Too many requests (`rate_limited`). Retry after the `Retry-After` header.","content":{"application/json":{"schema":{"$ref":"#/components/schemas/V1Error"}}}},"503":{"description":"Settings could not be read (`unavailable`). Retry shortly.","content":{"application/json":{"schema":{"$ref":"#/components/schemas/V1Error"}}}}}},"delete":{"tags":["Webhooks"],"operationId":"deleteWebhookEndpoint","summary":"Delete a webhook endpoint","description":"**Scope:** `admin:webhooks`. **Needs** webhook endpoints by API turned on for your organization (ask Finn support).\n\nDeletes the endpoint and its delivery history. Events waiting for it are not sent.\n\n**Rate limit:** 60 writes per minute per API key.","x-required-scope":"admin:webhooks","parameters":[{"name":"id","in":"path","required":true,"description":"The webhook endpoint's id.","schema":{"type":"string","format":"uuid"}}],"responses":{"200":{"description":"Deleted.","headers":{"RateLimit-Policy":{"$ref":"#/components/headers/RateLimitPolicy"},"RateLimit-Limit":{"$ref":"#/components/headers/RateLimitLimit"},"RateLimit-Remaining":{"$ref":"#/components/headers/RateLimitRemaining"},"RateLimit-Reset":{"$ref":"#/components/headers/RateLimitReset"}},"content":{"application/json":{"schema":{"$ref":"#/components/schemas/DeletedWebhookEndpoint"}}}},"401":{"description":"Missing or invalid API key (`api_key_required`, `invalid_api_key`).","content":{"application/json":{"schema":{"$ref":"#/components/schemas/V1Error"}}}},"403":{"description":"`insufficient_scope` (the key lacks `admin:webhooks`), or `webhooks_api_not_enabled` when webhook endpoints by API are not turned on for your organization.","content":{"application/json":{"schema":{"$ref":"#/components/schemas/V1Error"}}}},"404":{"description":"No such webhook endpoint in your organization (`webhook_endpoint_not_found`).","content":{"application/json":{"schema":{"$ref":"#/components/schemas/V1Error"}}}},"429":{"description":"Too many requests (`rate_limited`). Retry after the `Retry-After` header.","content":{"application/json":{"schema":{"$ref":"#/components/schemas/V1Error"}}}},"503":{"description":"Settings could not be read (`unavailable`). Retry shortly.","content":{"application/json":{"schema":{"$ref":"#/components/schemas/V1Error"}}}}}},"parameters":[{"$ref":"#/components/parameters/FinnOrg"},{"$ref":"#/components/parameters/FinnVersion"}]},"/webhook_endpoints/{id}/deliveries":{"get":{"tags":["Webhooks"],"operationId":"listWebhookDeliveries","summary":"List an endpoint's deliveries","description":"**Scope:** `admin:webhooks`. **Needs** webhook endpoints by API turned on for your organization (ask Finn support).\n\nThe events sent, waiting or failed for this endpoint, newest first. Bodies are not kept.\n\n**Rate limit:** 300 requests per minute per API key.","x-required-scope":"admin:webhooks","parameters":[{"name":"id","in":"path","required":true,"description":"The webhook endpoint's id.","schema":{"type":"string","format":"uuid"}},{"name":"status","in":"query","description":"Only deliveries in this status.","schema":{"type":"string","enum":["pending","delivered","failed"]}},{"name":"limit","in":"query","description":"Items per page, 1 to 200. Default 50.","schema":{"type":"integer","minimum":1,"maximum":200,"default":50}},{"name":"cursor","in":"query","description":"`next_cursor` of the previous page. Send the same filters with it.","schema":{"type":"string"}}],"responses":{"200":{"description":"A page of deliveries.","headers":{"RateLimit-Policy":{"$ref":"#/components/headers/RateLimitPolicy"},"RateLimit-Limit":{"$ref":"#/components/headers/RateLimitLimit"},"RateLimit-Remaining":{"$ref":"#/components/headers/RateLimitRemaining"},"RateLimit-Reset":{"$ref":"#/components/headers/RateLimitReset"}},"content":{"application/json":{"schema":{"$ref":"#/components/schemas/WebhookDeliveryList"}}}},"400":{"description":"Invalid input (`invalid_request` with `fields`) or a bad cursor (`invalid_cursor`).","content":{"application/json":{"schema":{"$ref":"#/components/schemas/V1Error"}}}},"401":{"description":"Missing or invalid API key (`api_key_required`, `invalid_api_key`).","content":{"application/json":{"schema":{"$ref":"#/components/schemas/V1Error"}}}},"403":{"description":"`insufficient_scope` (the key lacks `admin:webhooks`), or `webhooks_api_not_enabled` when webhook endpoints by API are not turned on for your organization.","content":{"application/json":{"schema":{"$ref":"#/components/schemas/V1Error"}}}},"404":{"description":"No such webhook endpoint in your organization (`webhook_endpoint_not_found`).","content":{"application/json":{"schema":{"$ref":"#/components/schemas/V1Error"}}}},"429":{"description":"Too many requests (`rate_limited`). Retry after the `Retry-After` header.","content":{"application/json":{"schema":{"$ref":"#/components/schemas/V1Error"}}}},"503":{"description":"Settings could not be read (`unavailable`). Retry shortly.","content":{"application/json":{"schema":{"$ref":"#/components/schemas/V1Error"}}}}}},"parameters":[{"$ref":"#/components/parameters/FinnOrg"},{"$ref":"#/components/parameters/FinnVersion"}]},"/events":{"get":{"tags":["Webhooks"],"operationId":"listEventTypes","summary":"List webhook event types","description":"**Scope:** `admin:webhooks`. **Needs** webhook endpoints by API turned on for your organization (ask Finn support).\n\nThe event types: every event a webhook endpoint can subscribe to (the names for `events` on `POST /webhook_endpoints`), with its group and what it means. It lists types, not events that happened: what was sent to an endpoint is `GET /webhook_endpoints/{id}/deliveries`. Their bodies are under **Webhooks** in this document. One page; `next_cursor` is always `null`.\n\n**Rate limit:** 300 requests per minute per API key.","x-required-scope":"admin:webhooks","responses":{"200":{"description":"The events.","headers":{"RateLimit-Policy":{"$ref":"#/components/headers/RateLimitPolicy"},"RateLimit-Limit":{"$ref":"#/components/headers/RateLimitLimit"},"RateLimit-Remaining":{"$ref":"#/components/headers/RateLimitRemaining"},"RateLimit-Reset":{"$ref":"#/components/headers/RateLimitReset"}},"content":{"application/json":{"schema":{"$ref":"#/components/schemas/EventTypeList"}}}},"401":{"description":"Missing or invalid API key (`api_key_required`, `invalid_api_key`).","content":{"application/json":{"schema":{"$ref":"#/components/schemas/V1Error"}}}},"403":{"description":"`insufficient_scope` (the key lacks `admin:webhooks`), or `webhooks_api_not_enabled` when webhook endpoints by API are not turned on for your organization.","content":{"application/json":{"schema":{"$ref":"#/components/schemas/V1Error"}}}},"429":{"description":"Too many requests (`rate_limited`). Retry after the `Retry-After` header.","content":{"application/json":{"schema":{"$ref":"#/components/schemas/V1Error"}}}},"503":{"description":"Settings could not be read (`unavailable`). Retry shortly.","content":{"application/json":{"schema":{"$ref":"#/components/schemas/V1Error"}}}}}},"parameters":[{"$ref":"#/components/parameters/FinnOrg"},{"$ref":"#/components/parameters/FinnVersion"}]},"/organization":{"get":{"tags":["Admin"],"operationId":"getOrganization","summary":"Get the organization","description":"**Scope:** `admin:read`. **Needs** admin reads by API turned on for your organization, and an organization API key. An org admin's own OAuth or MCP token also works where member access to admin reads is turned on too; other members are refused.\n\nThe profile of the organization the API key belongs to.\n\n**Rate limit:** 120 requests per minute per API key, shared by the four admin reads.","responses":{"200":{"description":"The organization.","content":{"application/json":{"schema":{"$ref":"#/components/schemas/OrganizationResponse"}}},"headers":{"RateLimit-Policy":{"$ref":"#/components/headers/RateLimitPolicy"},"RateLimit-Limit":{"$ref":"#/components/headers/RateLimitLimit"},"RateLimit-Remaining":{"$ref":"#/components/headers/RateLimitRemaining"},"RateLimit-Reset":{"$ref":"#/components/headers/RateLimitReset"}}},"401":{"description":"Missing or invalid API key.","content":{"application/json":{"schema":{"$ref":"#/components/schemas/V1Error"}}}},"403":{"description":"`insufficient_scope` (the key lacks `admin:read`), `forbidden` (not an organization API key, or a member who is not an org admin), or `admin_reads_off` when admin reads by API are not turned on for your organization (ask Finn support).","content":{"application/json":{"schema":{"$ref":"#/components/schemas/V1Error"}}}},"404":{"description":"The organization no longer exists (`not_found`).","content":{"application/json":{"schema":{"$ref":"#/components/schemas/V1Error"}}}},"429":{"description":"Too many requests (`rate_limited`). Retry after the `Retry-After` header.","content":{"application/json":{"schema":{"$ref":"#/components/schemas/V1Error"}}}},"500":{"description":"Something went wrong on our side (`internal_error`).","content":{"application/json":{"schema":{"$ref":"#/components/schemas/V1Error"}}}},"503":{"description":"Settings could not be read (`unavailable`). Retry shortly.","content":{"application/json":{"schema":{"$ref":"#/components/schemas/V1Error"}}}}},"x-required-scope":"admin:read","x-mcp-tool":"get_organization"},"parameters":[{"$ref":"#/components/parameters/FinnVersion"}]},"/organization/members":{"get":{"tags":["Admin"],"operationId":"listOrganizationMembers","summary":"List members","description":"**Scope:** `admin:read`. **Needs** admin reads by API turned on for your organization, and an organization API key. An org admin's own OAuth or MCP token also works where member access to admin reads is turned on too; other members are refused.\n\nThe people in your organization and their roles, ordered by user ID.\n\n**Rate limit:** 120 requests per minute per API key, shared by the four admin reads.","parameters":[{"name":"limit","in":"query","description":"Members per page, 1 to 200 (default 50).","schema":{"type":"integer","minimum":1,"maximum":200}},{"name":"cursor","in":"query","description":"`next_cursor` of the previous page.","schema":{"type":"string"}}],"responses":{"200":{"description":"A page of members.","content":{"application/json":{"schema":{"$ref":"#/components/schemas/OrganizationMemberList"}}},"headers":{"RateLimit-Policy":{"$ref":"#/components/headers/RateLimitPolicy"},"RateLimit-Limit":{"$ref":"#/components/headers/RateLimitLimit"},"RateLimit-Remaining":{"$ref":"#/components/headers/RateLimitRemaining"},"RateLimit-Reset":{"$ref":"#/components/headers/RateLimitReset"}}},"400":{"description":"Invalid `limit` (`invalid_request` with `fields`) or a bad cursor (`invalid_cursor`).","content":{"application/json":{"schema":{"$ref":"#/components/schemas/V1Error"}}}},"401":{"description":"Missing or invalid API key.","content":{"application/json":{"schema":{"$ref":"#/components/schemas/V1Error"}}}},"403":{"description":"`insufficient_scope` (the key lacks `admin:read`), `forbidden` (not an organization API key, or a member who is not an org admin), or `admin_reads_off` when admin reads by API are not turned on for your organization (ask Finn support).","content":{"application/json":{"schema":{"$ref":"#/components/schemas/V1Error"}}}},"429":{"description":"Too many requests (`rate_limited`). Retry after the `Retry-After` header.","content":{"application/json":{"schema":{"$ref":"#/components/schemas/V1Error"}}}},"500":{"description":"Something went wrong on our side (`internal_error`).","content":{"application/json":{"schema":{"$ref":"#/components/schemas/V1Error"}}}},"503":{"description":"Settings could not be read (`unavailable`). Retry shortly.","content":{"application/json":{"schema":{"$ref":"#/components/schemas/V1Error"}}}}},"x-required-scope":"admin:read","x-mcp-tool":"list_org_members"},"parameters":[{"$ref":"#/components/parameters/FinnVersion"}]},"/organization/integrations":{"get":{"tags":["Admin"],"operationId":"listOrganizationIntegrations","summary":"List integrations","description":"**Scope:** `admin:read`. **Needs** admin reads by API turned on for your organization, and an organization API key. An org admin's own OAuth or MCP token also works where member access to admin reads is turned on too; other members are refused.\n\nYour organization's integration connections (stores, CRMs, logistics and other connected apps) and their status, newest first. Disconnected ones are left out. Credentials, tokens and connection settings are never returned.\n\n**Rate limit:** 120 requests per minute per API key, shared by the four admin reads.","parameters":[{"name":"limit","in":"query","description":"Integrations per page, 1 to 200 (default 50).","schema":{"type":"integer","minimum":1,"maximum":200}},{"name":"cursor","in":"query","description":"`next_cursor` of the previous page.","schema":{"type":"string"}}],"responses":{"200":{"description":"A page of integrations.","content":{"application/json":{"schema":{"$ref":"#/components/schemas/IntegrationConnectionList"}}},"headers":{"RateLimit-Policy":{"$ref":"#/components/headers/RateLimitPolicy"},"RateLimit-Limit":{"$ref":"#/components/headers/RateLimitLimit"},"RateLimit-Remaining":{"$ref":"#/components/headers/RateLimitRemaining"},"RateLimit-Reset":{"$ref":"#/components/headers/RateLimitReset"}}},"400":{"description":"Invalid `limit` (`invalid_request` with `fields`) or a bad cursor (`invalid_cursor`).","content":{"application/json":{"schema":{"$ref":"#/components/schemas/V1Error"}}}},"401":{"description":"Missing or invalid API key.","content":{"application/json":{"schema":{"$ref":"#/components/schemas/V1Error"}}}},"403":{"description":"`insufficient_scope` (the key lacks `admin:read`), `forbidden` (not an organization API key, or a member who is not an org admin), or `admin_reads_off` when admin reads by API are not turned on for your organization (ask Finn support).","content":{"application/json":{"schema":{"$ref":"#/components/schemas/V1Error"}}}},"429":{"description":"Too many requests (`rate_limited`). Retry after the `Retry-After` header.","content":{"application/json":{"schema":{"$ref":"#/components/schemas/V1Error"}}}},"500":{"description":"Something went wrong on our side (`internal_error`).","content":{"application/json":{"schema":{"$ref":"#/components/schemas/V1Error"}}}},"503":{"description":"Settings could not be read (`unavailable`). Retry shortly.","content":{"application/json":{"schema":{"$ref":"#/components/schemas/V1Error"}}}}},"x-required-scope":"admin:read","x-mcp-tool":"list_org_integrations"},"parameters":[{"$ref":"#/components/parameters/FinnVersion"}]},"/organization/api_keys":{"get":{"tags":["Admin"],"operationId":"listOrganizationApiKeys","summary":"List API keys","description":"**Scope:** `admin:read`. **Needs** admin reads by API turned on for your organization, and an organization API key. An org admin's own OAuth or MCP token also works where member access to admin reads is turned on too; other members are refused.\n\nYour organization's API keys, newest first, revoked ones included. Metadata only: the key itself is shown once, when it is made or rotated, and is never returned by this list. Create, rotate and revoke keys in the dashboard under **Settings → Integrations → API keys**. `expiring_soon` is true for a key that expires within 14 days.\n\n**Rate limit:** 120 requests per minute per API key, shared by the four admin reads.","parameters":[{"name":"limit","in":"query","description":"API keys per page, 1 to 200 (default 50).","schema":{"type":"integer","minimum":1,"maximum":200}},{"name":"cursor","in":"query","description":"`next_cursor` of the previous page.","schema":{"type":"string"}}],"responses":{"200":{"description":"A page of api keys.","content":{"application/json":{"schema":{"$ref":"#/components/schemas/ApiKeyInfoList"}}},"headers":{"RateLimit-Policy":{"$ref":"#/components/headers/RateLimitPolicy"},"RateLimit-Limit":{"$ref":"#/components/headers/RateLimitLimit"},"RateLimit-Remaining":{"$ref":"#/components/headers/RateLimitRemaining"},"RateLimit-Reset":{"$ref":"#/components/headers/RateLimitReset"}}},"400":{"description":"Invalid `limit` (`invalid_request` with `fields`) or a bad cursor (`invalid_cursor`).","content":{"application/json":{"schema":{"$ref":"#/components/schemas/V1Error"}}}},"401":{"description":"Missing or invalid API key.","content":{"application/json":{"schema":{"$ref":"#/components/schemas/V1Error"}}}},"403":{"description":"`insufficient_scope` (the key lacks `admin:read`), `forbidden` (not an organization API key, or a member who is not an org admin), or `admin_reads_off` when admin reads by API are not turned on for your organization (ask Finn support).","content":{"application/json":{"schema":{"$ref":"#/components/schemas/V1Error"}}}},"429":{"description":"Too many requests (`rate_limited`). Retry after the `Retry-After` header.","content":{"application/json":{"schema":{"$ref":"#/components/schemas/V1Error"}}}},"500":{"description":"Something went wrong on our side (`internal_error`).","content":{"application/json":{"schema":{"$ref":"#/components/schemas/V1Error"}}}},"503":{"description":"Settings could not be read (`unavailable`). Retry shortly.","content":{"application/json":{"schema":{"$ref":"#/components/schemas/V1Error"}}}}},"x-required-scope":"admin:read"},"parameters":[{"$ref":"#/components/parameters/FinnVersion"}]},"/usage":{"get":{"tags":["Admin"],"operationId":"getUsage","summary":"Get API usage by key and day","description":"**Scope:** `admin:read`. **Needs** usage by API turned on for your organization (`usage_off` otherwise), and an organization API key. An org admin's own OAuth or MCP token also works where member access to admin reads is turned on; other members are refused.\n\nPer credential (org API key, MCP key or OAuth token) and per UTC day: `calls.write` (successful writes), `calls.read` (reads), `calls.refused` (403 refusals) and `holds_created` (writes held for approval). Spend is the organization's wallet ledger per day (`credits_spent`, `credits_added`, `transactions`); it matches `GET /wallet/transactions` over the same days and is not split by key, because a wallet transaction does not record which key caused it.\n\nCounted from what Finn already records: `calls.read` counts only the reads the audit log records (records, cases, approvals, calls, do-not-call, chat and post-call analysis), not reads of Finns, voices, the wallet or schemas, so it is a lower bound (`coverage.reads` is `sensitive_reads_only`). A write held for approval counts when it is held and again when its approved run succeeds. Days and keys with no activity are left out.\n\n`limits` gives the usual per-minute limits (each response's `RateLimit-*` headers give that route's own), and, where per-credential limits are on for your organization, the overall per-credential ceiling and the daily caps with today's use.\n\n**Rate limit:** 120 requests per minute per API key, shared with the admin reads.","parameters":[{"name":"from","in":"query","description":"First day, `YYYY-MM-DD` (UTC). Default: 6 days before `to`.","schema":{"type":"string","format":"date"}},{"name":"to","in":"query","description":"Last day, `YYYY-MM-DD` (UTC), inclusive. Default and maximum: today. The range is at most 92 days.","schema":{"type":"string","format":"date"}},{"name":"group_by","in":"query","description":"Only `day` today.","schema":{"type":"string","enum":["day"],"default":"day"}},{"name":"credential_id","in":"query","description":"Only this credential (an API key id from `GET /organization/api_keys`, or an MCP key or OAuth grant id).","schema":{"type":"string","format":"uuid"}}],"responses":{"200":{"description":"Usage for the range.","content":{"application/json":{"schema":{"$ref":"#/components/schemas/UsageReport"}}},"headers":{"RateLimit-Policy":{"$ref":"#/components/headers/RateLimitPolicy"},"RateLimit-Limit":{"$ref":"#/components/headers/RateLimitLimit"},"RateLimit-Remaining":{"$ref":"#/components/headers/RateLimitRemaining"},"RateLimit-Reset":{"$ref":"#/components/headers/RateLimitReset"}}},"400":{"description":"Invalid `from`, `to`, `group_by` or `credential_id`, or a range over 92 days (`invalid_request` with `fields`).","content":{"application/json":{"schema":{"$ref":"#/components/schemas/V1Error"}}}},"401":{"description":"Missing or invalid API key.","content":{"application/json":{"schema":{"$ref":"#/components/schemas/V1Error"}}}},"403":{"description":"`insufficient_scope` (the key lacks `admin:read`), `forbidden` (not an organization API key, or a member who is not an org admin), or `usage_off` when usage by API is not turned on for your organization (ask Finn support).","content":{"application/json":{"schema":{"$ref":"#/components/schemas/V1Error"}}}},"429":{"description":"Too many requests (`rate_limited`). Retry after the `Retry-After` header.","content":{"application/json":{"schema":{"$ref":"#/components/schemas/V1Error"}}}},"500":{"description":"Something went wrong on our side (`internal_error`).","content":{"application/json":{"schema":{"$ref":"#/components/schemas/V1Error"}}}},"503":{"description":"Settings could not be read (`unavailable`). Retry shortly.","content":{"application/json":{"schema":{"$ref":"#/components/schemas/V1Error"}}}}},"x-required-scope":"admin:read"},"parameters":[{"$ref":"#/components/parameters/FinnVersion"}]},"/finns/generate":{"post":{"tags":["Finns"],"operationId":"generateFinn","summary":"Draft a Finn with AI","description":"**Scope:** `finn:write` (an org API key may use `voice:write` instead). **Needs** Finn editing by API turned on for your organization (off: every route here answers `404`).\n\nTurns a short brief into a full Finn (name, persona, opening line, prompt, post-call fields) with the same AI copilot as the dashboard, then creates it as a **draft** made by the key's owner (a member's token: that member). `voice` and `language` override the AI's choice; without a `voice` the AI picks one your organization can use.\n\nOne model call per request.\n\n**Rate limit:** 10 AI drafts per minute per API key.","requestBody":{"required":true,"content":{"application/json":{"schema":{"type":"object","required":["brief"],"properties":{"brief":{"type":"string","maxLength":3500},"voice":{"type":"string"},"language":{"type":"string"}}}}}},"responses":{"201":{"description":"The new draft Finn.","headers":{"RateLimit-Policy":{"$ref":"#/components/headers/RateLimitPolicy"},"RateLimit-Limit":{"$ref":"#/components/headers/RateLimitLimit"},"RateLimit-Remaining":{"$ref":"#/components/headers/RateLimitRemaining"},"RateLimit-Reset":{"$ref":"#/components/headers/RateLimitReset"}},"content":{"application/json":{"schema":{"type":"object","required":["success","data"],"properties":{"success":{"type":"boolean","const":true},"data":{"$ref":"#/components/schemas/Finn"}}}}}},"400":{"description":"Invalid input (`invalid_request`, with `fields` where a field is at fault).","content":{"application/json":{"schema":{"$ref":"#/components/schemas/V1Error"}}}},"401":{"description":"Missing or invalid credential.","content":{"application/json":{"schema":{"$ref":"#/components/schemas/V1Error"}}}},"403":{"description":"`insufficient_scope`.","content":{"application/json":{"schema":{"$ref":"#/components/schemas/V1Error"}}}},"404":{"description":"`finn_not_found`, or `not_found` when Finn editing by API is off for your organization.","content":{"application/json":{"schema":{"$ref":"#/components/schemas/V1Error"}}}},"429":{"$ref":"#/components/responses/RateLimited"},"500":{"$ref":"#/components/responses/InternalError"}},"x-required-scope":"finn:write","x-mcp-tool":"generate_finn"},"parameters":[{"$ref":"#/components/parameters/FinnVersion"}]},"/finns/{finn_id}/flow":{"parameters":[{"name":"finn_id","in":"path","required":true,"description":"The Finn's ID.","schema":{"type":"string","format":"uuid"}},{"$ref":"#/components/parameters/FinnVersion"}],"get":{"tags":["Finns"],"operationId":"getFinnFlow","summary":"Read a Finn's call flow","description":"**Scope:** `finn:read` (an org API key may use `voice:read` instead). **Needs** Finn editing by API turned on for your organization (off: every route here answers `404`).\n\nThe call flow canvas as nodes and edges, and whether it runs instead of the prompt.\n\n**Rate limit:** 300 requests per minute per API key.","responses":{"200":{"description":"The call flow.","headers":{"RateLimit-Policy":{"$ref":"#/components/headers/RateLimitPolicy"},"RateLimit-Limit":{"$ref":"#/components/headers/RateLimitLimit"},"RateLimit-Remaining":{"$ref":"#/components/headers/RateLimitRemaining"},"RateLimit-Reset":{"$ref":"#/components/headers/RateLimitReset"}},"content":{"application/json":{"schema":{"type":"object","required":["success","data"],"properties":{"success":{"type":"boolean","const":true},"data":{"$ref":"#/components/schemas/FinnFlow"}}}}}},"400":{"description":"Invalid input (`invalid_request`, with `fields` where a field is at fault).","content":{"application/json":{"schema":{"$ref":"#/components/schemas/V1Error"}}}},"401":{"description":"Missing or invalid credential.","content":{"application/json":{"schema":{"$ref":"#/components/schemas/V1Error"}}}},"403":{"description":"`insufficient_scope`.","content":{"application/json":{"schema":{"$ref":"#/components/schemas/V1Error"}}}},"404":{"description":"`finn_not_found`, or `not_found` when Finn editing by API is off for your organization.","content":{"application/json":{"schema":{"$ref":"#/components/schemas/V1Error"}}}},"429":{"$ref":"#/components/responses/RateLimited"},"500":{"$ref":"#/components/responses/InternalError"}},"x-required-scope":"finn:read","x-mcp-tool":"get_finn_flow"}},"/finns/{finn_id}/flow/enable":{"parameters":[{"name":"finn_id","in":"path","required":true,"description":"The Finn's ID.","schema":{"type":"string","format":"uuid"}},{"$ref":"#/components/parameters/FinnVersion"}],"post":{"tags":["Finns"],"operationId":"enableFinnFlow","summary":"Turn a Finn's call flow on","description":"**Scope:** `finn:write` (an org API key may use `voice:write` instead). **Needs** Finn editing by API turned on for your organization (off: every route here answers `404`).\n\nThe Finn follows its call flow instead of its prompt. The canvas itself is not changed; `409 flow_empty` when there is no flow to turn on.\n\nChanges a **draft** only (`403 finn_not_draft` otherwise); a member's token (OAuth / personal MCP key) only a draft that member created (`403 finn_not_yours`). Nothing here publishes: a person makes the Finn live in Finn.\n\n**Rate limit:** 120 writes per minute per API key.","responses":{"200":{"description":"The call flow.","headers":{"RateLimit-Policy":{"$ref":"#/components/headers/RateLimitPolicy"},"RateLimit-Limit":{"$ref":"#/components/headers/RateLimitLimit"},"RateLimit-Remaining":{"$ref":"#/components/headers/RateLimitRemaining"},"RateLimit-Reset":{"$ref":"#/components/headers/RateLimitReset"}},"content":{"application/json":{"schema":{"type":"object","required":["success","data"],"properties":{"success":{"type":"boolean","const":true},"data":{"$ref":"#/components/schemas/FinnFlow"}}}}}},"400":{"description":"Invalid input (`invalid_request`, with `fields` where a field is at fault).","content":{"application/json":{"schema":{"$ref":"#/components/schemas/V1Error"}}}},"401":{"description":"Missing or invalid credential.","content":{"application/json":{"schema":{"$ref":"#/components/schemas/V1Error"}}}},"403":{"description":"`insufficient_scope`, `finn_not_draft` or `finn_not_yours`.","content":{"application/json":{"schema":{"$ref":"#/components/schemas/V1Error"}}}},"404":{"description":"`finn_not_found`, or `not_found` when Finn editing by API is off for your organization.","content":{"application/json":{"schema":{"$ref":"#/components/schemas/V1Error"}}}},"409":{"description":"`flow_empty`: the Finn has no call flow.","content":{"application/json":{"schema":{"$ref":"#/components/schemas/V1Error"}}}},"429":{"$ref":"#/components/responses/RateLimited"},"500":{"$ref":"#/components/responses/InternalError"}},"x-required-scope":"finn:write","x-mcp-tool":"set_finn_flow_enabled"}},"/finns/{finn_id}/flow/disable":{"parameters":[{"name":"finn_id","in":"path","required":true,"description":"The Finn's ID.","schema":{"type":"string","format":"uuid"}},{"$ref":"#/components/parameters/FinnVersion"}],"post":{"tags":["Finns"],"operationId":"disableFinnFlow","summary":"Turn a Finn's call flow off","description":"**Scope:** `finn:write` (an org API key may use `voice:write` instead). **Needs** Finn editing by API turned on for your organization (off: every route here answers `404`).\n\nThe Finn runs on its prompt; the canvas is kept.\n\nChanges a **draft** only (`403 finn_not_draft` otherwise); a member's token (OAuth / personal MCP key) only a draft that member created (`403 finn_not_yours`). Nothing here publishes: a person makes the Finn live in Finn.\n\n**Rate limit:** 120 writes per minute per API key.","responses":{"200":{"description":"The call flow.","headers":{"RateLimit-Policy":{"$ref":"#/components/headers/RateLimitPolicy"},"RateLimit-Limit":{"$ref":"#/components/headers/RateLimitLimit"},"RateLimit-Remaining":{"$ref":"#/components/headers/RateLimitRemaining"},"RateLimit-Reset":{"$ref":"#/components/headers/RateLimitReset"}},"content":{"application/json":{"schema":{"type":"object","required":["success","data"],"properties":{"success":{"type":"boolean","const":true},"data":{"$ref":"#/components/schemas/FinnFlow"}}}}}},"400":{"description":"Invalid input (`invalid_request`, with `fields` where a field is at fault).","content":{"application/json":{"schema":{"$ref":"#/components/schemas/V1Error"}}}},"401":{"description":"Missing or invalid credential.","content":{"application/json":{"schema":{"$ref":"#/components/schemas/V1Error"}}}},"403":{"description":"`insufficient_scope`, `finn_not_draft` or `finn_not_yours`.","content":{"application/json":{"schema":{"$ref":"#/components/schemas/V1Error"}}}},"404":{"description":"`finn_not_found`, or `not_found` when Finn editing by API is off for your organization.","content":{"application/json":{"schema":{"$ref":"#/components/schemas/V1Error"}}}},"429":{"$ref":"#/components/responses/RateLimited"},"500":{"$ref":"#/components/responses/InternalError"}},"x-required-scope":"finn:write","x-mcp-tool":"set_finn_flow_enabled"}},"/finns/{finn_id}/settings":{"parameters":[{"name":"finn_id","in":"path","required":true,"description":"The Finn's ID.","schema":{"type":"string","format":"uuid"}},{"$ref":"#/components/parameters/FinnVersion"}],"get":{"tags":["Finns"],"operationId":"getFinnSettings","summary":"Read a Finn's settings","description":"**Scope:** `finn:read` (an org API key may use `voice:read` instead). **Needs** Finn editing by API turned on for your organization (off: every route here answers `404`).\n\nEverything `GET /finns/{finn_id}` does not cover: agent handbook, speech settings, call flow summary, task settings and knowledge documents.\n\n**Rate limit:** 300 requests per minute per API key.","responses":{"200":{"description":"The settings.","headers":{"RateLimit-Policy":{"$ref":"#/components/headers/RateLimitPolicy"},"RateLimit-Limit":{"$ref":"#/components/headers/RateLimitLimit"},"RateLimit-Remaining":{"$ref":"#/components/headers/RateLimitRemaining"},"RateLimit-Reset":{"$ref":"#/components/headers/RateLimitReset"}},"content":{"application/json":{"schema":{"type":"object","required":["success","data"],"properties":{"success":{"type":"boolean","const":true},"data":{"$ref":"#/components/schemas/FinnSettings"}}}}}},"400":{"description":"Invalid input (`invalid_request`, with `fields` where a field is at fault).","content":{"application/json":{"schema":{"$ref":"#/components/schemas/V1Error"}}}},"401":{"description":"Missing or invalid credential.","content":{"application/json":{"schema":{"$ref":"#/components/schemas/V1Error"}}}},"403":{"description":"`insufficient_scope`.","content":{"application/json":{"schema":{"$ref":"#/components/schemas/V1Error"}}}},"404":{"description":"`finn_not_found`, or `not_found` when Finn editing by API is off for your organization.","content":{"application/json":{"schema":{"$ref":"#/components/schemas/V1Error"}}}},"429":{"$ref":"#/components/responses/RateLimited"},"500":{"$ref":"#/components/responses/InternalError"}},"x-required-scope":"finn:read","x-mcp-tool":"get_finn_settings"}},"/finns/{finn_id}/variables":{"parameters":[{"name":"finn_id","in":"path","required":true,"description":"The Finn's ID.","schema":{"type":"string","format":"uuid"}},{"$ref":"#/components/parameters/FinnVersion"}],"get":{"tags":["Finns"],"operationId":"getFinnVariables","summary":"Read a Finn's variables","description":"**Scope:** `finn:read` (an org API key may use `voice:read` instead). **Needs** Finn editing by API turned on for your organization (off: every route here answers `404`).\n\nPlaceholders and dynamic variables found in the prompt fields, the placeholder to CSV column mapping, and the post-call fields.\n\n**Rate limit:** 300 requests per minute per API key.","responses":{"200":{"description":"The variables.","headers":{"RateLimit-Policy":{"$ref":"#/components/headers/RateLimitPolicy"},"RateLimit-Limit":{"$ref":"#/components/headers/RateLimitLimit"},"RateLimit-Remaining":{"$ref":"#/components/headers/RateLimitRemaining"},"RateLimit-Reset":{"$ref":"#/components/headers/RateLimitReset"}},"content":{"application/json":{"schema":{"type":"object","required":["success","data"],"properties":{"success":{"type":"boolean","const":true},"data":{"$ref":"#/components/schemas/FinnVariables"}}}}}},"400":{"description":"Invalid input (`invalid_request`, with `fields` where a field is at fault).","content":{"application/json":{"schema":{"$ref":"#/components/schemas/V1Error"}}}},"401":{"description":"Missing or invalid credential.","content":{"application/json":{"schema":{"$ref":"#/components/schemas/V1Error"}}}},"403":{"description":"`insufficient_scope`.","content":{"application/json":{"schema":{"$ref":"#/components/schemas/V1Error"}}}},"404":{"description":"`finn_not_found`, or `not_found` when Finn editing by API is off for your organization.","content":{"application/json":{"schema":{"$ref":"#/components/schemas/V1Error"}}}},"429":{"$ref":"#/components/responses/RateLimited"},"500":{"$ref":"#/components/responses/InternalError"}},"x-required-scope":"finn:read","x-mcp-tool":"get_finn_variables"},"patch":{"tags":["Finns"],"operationId":"updateFinnVariables","summary":"Edit a Finn's variables","description":"**Scope:** `finn:write` (an org API key may use `voice:write` instead). **Needs** Finn editing by API turned on for your organization (off: every route here answers `404`).\n\nChange the placeholder mapping, turn it on or off, or replace the post-call fields.\n\nChanges a **draft** only (`403 finn_not_draft` otherwise); a member's token (OAuth / personal MCP key) only a draft that member created (`403 finn_not_yours`). Nothing here publishes: a person makes the Finn live in Finn.\n\n**Rate limit:** 120 writes per minute per API key.","requestBody":{"required":true,"content":{"application/json":{"schema":{"$ref":"#/components/schemas/UpdateFinnVariablesRequest"}}}},"responses":{"200":{"description":"The variables as saved.","headers":{"RateLimit-Policy":{"$ref":"#/components/headers/RateLimitPolicy"},"RateLimit-Limit":{"$ref":"#/components/headers/RateLimitLimit"},"RateLimit-Remaining":{"$ref":"#/components/headers/RateLimitRemaining"},"RateLimit-Reset":{"$ref":"#/components/headers/RateLimitReset"}},"content":{"application/json":{"schema":{"type":"object","required":["success","data"],"properties":{"success":{"type":"boolean","const":true},"data":{"$ref":"#/components/schemas/FinnVariables"}}}}}},"400":{"description":"Invalid input (`invalid_request`, with `fields` where a field is at fault).","content":{"application/json":{"schema":{"$ref":"#/components/schemas/V1Error"}}}},"401":{"description":"Missing or invalid credential.","content":{"application/json":{"schema":{"$ref":"#/components/schemas/V1Error"}}}},"403":{"description":"`insufficient_scope`, `finn_not_draft` or `finn_not_yours`.","content":{"application/json":{"schema":{"$ref":"#/components/schemas/V1Error"}}}},"404":{"description":"`finn_not_found`, or `not_found` when Finn editing by API is off for your organization.","content":{"application/json":{"schema":{"$ref":"#/components/schemas/V1Error"}}}},"429":{"$ref":"#/components/responses/RateLimited"},"500":{"$ref":"#/components/responses/InternalError"}},"x-required-scope":"finn:write","x-mcp-tool":"update_finn_variables"}},"/finns/{finn_id}/handbook":{"parameters":[{"name":"finn_id","in":"path","required":true,"description":"The Finn's ID.","schema":{"type":"string","format":"uuid"}},{"$ref":"#/components/parameters/FinnVersion"}],"patch":{"tags":["Finns"],"operationId":"updateFinnHandbook","summary":"Edit a Finn's agent handbook","description":"**Scope:** `finn:write` (an org API key may use `voice:write` instead). **Needs** Finn editing by API turned on for your organization (off: every route here answers `404`).\n\nSend only the switches to change, e.g. `{\"handbook\": {\"accuracy\": {\"natoPhonetic\": true}}}`; the rest keep their values.\n\nChanges a **draft** only (`403 finn_not_draft` otherwise); a member's token (OAuth / personal MCP key) only a draft that member created (`403 finn_not_yours`). Nothing here publishes: a person makes the Finn live in Finn.\n\n**Rate limit:** 120 writes per minute per API key.","requestBody":{"required":true,"content":{"application/json":{"schema":{"type":"object","required":["handbook"],"properties":{"handbook":{"type":"object","description":"Any subset of the handbook's sections and switches."}}}}}},"responses":{"200":{"description":"The handbook as saved.","headers":{"RateLimit-Policy":{"$ref":"#/components/headers/RateLimitPolicy"},"RateLimit-Limit":{"$ref":"#/components/headers/RateLimitLimit"},"RateLimit-Remaining":{"$ref":"#/components/headers/RateLimitRemaining"},"RateLimit-Reset":{"$ref":"#/components/headers/RateLimitReset"}},"content":{"application/json":{"schema":{"type":"object","required":["success","data"],"properties":{"success":{"type":"boolean","const":true},"data":{"type":"object","properties":{"finn":{"$ref":"#/components/schemas/FinnSummary"},"handbook":{"$ref":"#/components/schemas/FinnHandbook"},"schema_version":{"type":"integer"}}}}}}}},"400":{"description":"Invalid input (`invalid_request`, with `fields` where a field is at fault).","content":{"application/json":{"schema":{"$ref":"#/components/schemas/V1Error"}}}},"401":{"description":"Missing or invalid credential.","content":{"application/json":{"schema":{"$ref":"#/components/schemas/V1Error"}}}},"403":{"description":"`insufficient_scope`, `finn_not_draft` or `finn_not_yours`.","content":{"application/json":{"schema":{"$ref":"#/components/schemas/V1Error"}}}},"404":{"description":"`finn_not_found`, or `not_found` when Finn editing by API is off for your organization.","content":{"application/json":{"schema":{"$ref":"#/components/schemas/V1Error"}}}},"429":{"$ref":"#/components/responses/RateLimited"},"500":{"$ref":"#/components/responses/InternalError"}},"x-required-scope":"finn:write","x-mcp-tool":"update_finn_handbook"}},"/finns/{finn_id}/speech_settings":{"parameters":[{"name":"finn_id","in":"path","required":true,"description":"The Finn's ID.","schema":{"type":"string","format":"uuid"}},{"$ref":"#/components/parameters/FinnVersion"}],"patch":{"tags":["Finns"],"operationId":"updateFinnSpeechSettings","summary":"Edit a Finn's speech settings","description":"**Scope:** `finn:write` (an org API key may use `voice:write` instead). **Needs** Finn editing by API turned on for your organization (off: every route here answers `404`).\n\nSend only the settings to change, e.g. `{\"speech_settings\": {\"ttsSpeed\": 1.1}}`. `null` or `\"\"` removes a setting. An unknown setting or a value out of range is refused (`400`) rather than dropped.\n\nChanges a **draft** only (`403 finn_not_draft` otherwise); a member's token (OAuth / personal MCP key) only a draft that member created (`403 finn_not_yours`). Nothing here publishes: a person makes the Finn live in Finn.\n\n**Rate limit:** 120 writes per minute per API key.","requestBody":{"required":true,"content":{"application/json":{"schema":{"type":"object","required":["speech_settings"],"properties":{"speech_settings":{"type":"object","additionalProperties":true}}}}}},"responses":{"200":{"description":"The speech settings as saved.","headers":{"RateLimit-Policy":{"$ref":"#/components/headers/RateLimitPolicy"},"RateLimit-Limit":{"$ref":"#/components/headers/RateLimitLimit"},"RateLimit-Remaining":{"$ref":"#/components/headers/RateLimitRemaining"},"RateLimit-Reset":{"$ref":"#/components/headers/RateLimitReset"}},"content":{"application/json":{"schema":{"type":"object","required":["success","data"],"properties":{"success":{"type":"boolean","const":true},"data":{"type":"object","properties":{"finn":{"$ref":"#/components/schemas/FinnSummary"},"speech_settings":{"type":["object","null"],"additionalProperties":true}}}}}}}},"400":{"description":"Invalid input (`invalid_request`, with `fields` where a field is at fault).","content":{"application/json":{"schema":{"$ref":"#/components/schemas/V1Error"}}}},"401":{"description":"Missing or invalid credential.","content":{"application/json":{"schema":{"$ref":"#/components/schemas/V1Error"}}}},"403":{"description":"`insufficient_scope`, `finn_not_draft` or `finn_not_yours`.","content":{"application/json":{"schema":{"$ref":"#/components/schemas/V1Error"}}}},"404":{"description":"`finn_not_found`, or `not_found` when Finn editing by API is off for your organization.","content":{"application/json":{"schema":{"$ref":"#/components/schemas/V1Error"}}}},"429":{"$ref":"#/components/responses/RateLimited"},"500":{"$ref":"#/components/responses/InternalError"}},"x-required-scope":"finn:write","x-mcp-tool":"update_finn_speech_settings"}},"/finns/{finn_id}/draft":{"parameters":[{"name":"finn_id","in":"path","required":true,"description":"The Finn's ID.","schema":{"type":"string","format":"uuid"}},{"$ref":"#/components/parameters/FinnVersion"}],"patch":{"tags":["Finns"],"operationId":"updateFinnDraft","summary":"Edit a draft Finn","description":"**Scope:** `finn:write` (an org API key may use `voice:write` instead). **Needs** Finn editing by API turned on for your organization (off: every route here answers `404`).\n\nThe same body and answer as `PATCH /finns/{finn_id}`, for a draft only.\n\nChanges a **draft** only (`403 finn_not_draft` otherwise); a member's token (OAuth / personal MCP key) only a draft that member created (`403 finn_not_yours`). Nothing here publishes: a person makes the Finn live in Finn.\n\n**Rate limit:** 120 writes per minute per API key.","requestBody":{"required":true,"content":{"application/json":{"schema":{"$ref":"#/components/schemas/UpdateFinnRequest"}}}},"responses":{"200":{"description":"The updated Finn.","headers":{"RateLimit-Policy":{"$ref":"#/components/headers/RateLimitPolicy"},"RateLimit-Limit":{"$ref":"#/components/headers/RateLimitLimit"},"RateLimit-Remaining":{"$ref":"#/components/headers/RateLimitRemaining"},"RateLimit-Reset":{"$ref":"#/components/headers/RateLimitReset"}},"content":{"application/json":{"schema":{"type":"object","required":["success","data"],"properties":{"success":{"type":"boolean","const":true},"data":{"$ref":"#/components/schemas/Finn"}}}}}},"400":{"description":"Invalid input (`invalid_request`, with `fields` where a field is at fault).","content":{"application/json":{"schema":{"$ref":"#/components/schemas/V1Error"}}}},"401":{"description":"Missing or invalid credential.","content":{"application/json":{"schema":{"$ref":"#/components/schemas/V1Error"}}}},"403":{"description":"`insufficient_scope`, `finn_not_draft` or `finn_not_yours`.","content":{"application/json":{"schema":{"$ref":"#/components/schemas/V1Error"}}}},"404":{"description":"`finn_not_found`, or `not_found` when Finn editing by API is off for your organization.","content":{"application/json":{"schema":{"$ref":"#/components/schemas/V1Error"}}}},"429":{"$ref":"#/components/responses/RateLimited"},"500":{"$ref":"#/components/responses/InternalError"}},"x-required-scope":"finn:write","x-mcp-tool":"update_finn_draft"}},"/finns/{finn_id}/duplicate":{"parameters":[{"name":"finn_id","in":"path","required":true,"description":"The Finn's ID.","schema":{"type":"string","format":"uuid"}},{"$ref":"#/components/parameters/FinnVersion"}],"post":{"tags":["Finns"],"operationId":"duplicateFinn","summary":"Copy a Finn","description":"**Scope:** `finn:write` (an org API key may use `voice:write` instead). **Needs** Finn editing by API turned on for your organization (off: every route here answers `404`).\n\nA new **draft** copy of any Finn in your organization, made by the key's owner (a member's token: that member). Without `name` the copy is called \"<name> (copy)\".\n\n**Rate limit:** 120 writes per minute per API key.","requestBody":{"required":true,"content":{"application/json":{"schema":{"type":"object","properties":{"name":{"type":"string","minLength":1,"maxLength":100}}}}}},"responses":{"201":{"description":"The copy.","headers":{"RateLimit-Policy":{"$ref":"#/components/headers/RateLimitPolicy"},"RateLimit-Limit":{"$ref":"#/components/headers/RateLimitLimit"},"RateLimit-Remaining":{"$ref":"#/components/headers/RateLimitRemaining"},"RateLimit-Reset":{"$ref":"#/components/headers/RateLimitReset"}},"content":{"application/json":{"schema":{"type":"object","required":["success","data"],"properties":{"success":{"type":"boolean","const":true},"data":{"$ref":"#/components/schemas/Finn"}}}}}},"400":{"description":"Invalid input (`invalid_request`, with `fields` where a field is at fault).","content":{"application/json":{"schema":{"$ref":"#/components/schemas/V1Error"}}}},"401":{"description":"Missing or invalid credential.","content":{"application/json":{"schema":{"$ref":"#/components/schemas/V1Error"}}}},"403":{"description":"`insufficient_scope`.","content":{"application/json":{"schema":{"$ref":"#/components/schemas/V1Error"}}}},"404":{"description":"`finn_not_found`, or `not_found` when Finn editing by API is off for your organization.","content":{"application/json":{"schema":{"$ref":"#/components/schemas/V1Error"}}}},"409":{"description":"`api_key_owner_required`: the key's owner is no longer in the organization.","content":{"application/json":{"schema":{"$ref":"#/components/schemas/V1Error"}}}},"429":{"$ref":"#/components/responses/RateLimited"},"500":{"$ref":"#/components/responses/InternalError"}},"x-required-scope":"finn:write","x-mcp-tool":"duplicate_finn"}},"/finns/{finn_id}/share_link":{"parameters":[{"name":"finn_id","in":"path","required":true,"description":"The Finn's ID.","schema":{"type":"string","format":"uuid"}},{"$ref":"#/components/parameters/FinnVersion"}],"get":{"tags":["Finns"],"operationId":"getFinnShareLink","summary":"Read a Finn's share link","description":"**Scope:** `finn:read` (an org API key may use `voice:read` instead). **Needs** Finn editing by API turned on for your organization (off: every route here answers `404`).\n\nThe latest share link (a public page where anyone can talk to the Finn), or `null` when there is none.\n\n**Rate limit:** 300 requests per minute per API key.","responses":{"200":{"description":"The link, or null.","headers":{"RateLimit-Policy":{"$ref":"#/components/headers/RateLimitPolicy"},"RateLimit-Limit":{"$ref":"#/components/headers/RateLimitLimit"},"RateLimit-Remaining":{"$ref":"#/components/headers/RateLimitRemaining"},"RateLimit-Reset":{"$ref":"#/components/headers/RateLimitReset"}},"content":{"application/json":{"schema":{"type":"object","required":["success","data"],"properties":{"success":{"type":"boolean","const":true},"data":{"oneOf":[{"$ref":"#/components/schemas/FinnShareLink"},{"type":"null"}]}}}}}},"400":{"description":"Invalid input (`invalid_request`, with `fields` where a field is at fault).","content":{"application/json":{"schema":{"$ref":"#/components/schemas/V1Error"}}}},"401":{"description":"Missing or invalid credential.","content":{"application/json":{"schema":{"$ref":"#/components/schemas/V1Error"}}}},"403":{"description":"`insufficient_scope`.","content":{"application/json":{"schema":{"$ref":"#/components/schemas/V1Error"}}}},"404":{"description":"`finn_not_found`, or `not_found` when Finn editing by API is off for your organization.","content":{"application/json":{"schema":{"$ref":"#/components/schemas/V1Error"}}}},"429":{"$ref":"#/components/responses/RateLimited"},"500":{"$ref":"#/components/responses/InternalError"}},"x-required-scope":"finn:read","x-mcp-tool":"get_share_link"},"post":{"tags":["Finns"],"operationId":"createFinnShareLink","summary":"Get or make a Finn's share link","description":"**Scope:** `finn:write` (an org API key may use `voice:write` instead). **Needs** Finn editing by API turned on for your organization (off: every route here answers `404`).\n\nReturns the latest link when there is one (a turned-off link stays off), else makes one (`201`). Calls on a share link use your organization's credit. A member's token may share only Finns that member created (`403 finn_not_yours`).\n\n**Rate limit:** 120 writes per minute per API key.","responses":{"200":{"description":"The existing link.","headers":{"RateLimit-Policy":{"$ref":"#/components/headers/RateLimitPolicy"},"RateLimit-Limit":{"$ref":"#/components/headers/RateLimitLimit"},"RateLimit-Remaining":{"$ref":"#/components/headers/RateLimitRemaining"},"RateLimit-Reset":{"$ref":"#/components/headers/RateLimitReset"}},"content":{"application/json":{"schema":{"type":"object","required":["success","data"],"properties":{"success":{"type":"boolean","const":true},"data":{"$ref":"#/components/schemas/FinnShareLink"}}}}}},"201":{"description":"A new link.","headers":{"RateLimit-Policy":{"$ref":"#/components/headers/RateLimitPolicy"},"RateLimit-Limit":{"$ref":"#/components/headers/RateLimitLimit"},"RateLimit-Remaining":{"$ref":"#/components/headers/RateLimitRemaining"},"RateLimit-Reset":{"$ref":"#/components/headers/RateLimitReset"}},"content":{"application/json":{"schema":{"type":"object","required":["success","data"],"properties":{"success":{"type":"boolean","const":true},"data":{"$ref":"#/components/schemas/FinnShareLink"}}}}}},"400":{"description":"Invalid input (`invalid_request`, with `fields` where a field is at fault).","content":{"application/json":{"schema":{"$ref":"#/components/schemas/V1Error"}}}},"401":{"description":"Missing or invalid credential.","content":{"application/json":{"schema":{"$ref":"#/components/schemas/V1Error"}}}},"403":{"description":"`insufficient_scope`.","content":{"application/json":{"schema":{"$ref":"#/components/schemas/V1Error"}}}},"404":{"description":"`finn_not_found`, or `not_found` when Finn editing by API is off for your organization.","content":{"application/json":{"schema":{"$ref":"#/components/schemas/V1Error"}}}},"429":{"$ref":"#/components/responses/RateLimited"},"500":{"$ref":"#/components/responses/InternalError"}},"x-required-scope":"finn:write","x-mcp-tool":"get_share_link"}},"/finns/{finn_id}/stats":{"parameters":[{"name":"finn_id","in":"path","required":true,"description":"The Finn's ID.","schema":{"type":"string","format":"uuid"}},{"$ref":"#/components/parameters/FinnVersion"}],"get":{"tags":["Finns"],"operationId":"getFinnStats","summary":"A Finn's call stats","description":"**Scope:** `finn:read` (an org API key may use `voice:read` instead). **Needs** Finn editing by API turned on for your organization (off: every route here answers `404`).\n\nAggregates only: call counts, answered, successful, voicemail, minutes, cost, by status, direction and sentiment. No per-call or per-person data.\n\n**Rate limit:** 300 requests per minute per API key.","parameters":[{"name":"from","in":"query","required":false,"description":"First day, `YYYY-MM-DD` (default: 30 days before `to`).","schema":{"type":"string","format":"date"}},{"name":"to","in":"query","required":false,"description":"Last day, `YYYY-MM-DD` (default: today). At most 366 days.","schema":{"type":"string","format":"date"}}],"responses":{"200":{"description":"The stats.","headers":{"RateLimit-Policy":{"$ref":"#/components/headers/RateLimitPolicy"},"RateLimit-Limit":{"$ref":"#/components/headers/RateLimitLimit"},"RateLimit-Remaining":{"$ref":"#/components/headers/RateLimitRemaining"},"RateLimit-Reset":{"$ref":"#/components/headers/RateLimitReset"}},"content":{"application/json":{"schema":{"type":"object","required":["success","data"],"properties":{"success":{"type":"boolean","const":true},"data":{"type":"object","additionalProperties":true}}}}}},"400":{"description":"Invalid input (`invalid_request`, with `fields` where a field is at fault).","content":{"application/json":{"schema":{"$ref":"#/components/schemas/V1Error"}}}},"401":{"description":"Missing or invalid credential.","content":{"application/json":{"schema":{"$ref":"#/components/schemas/V1Error"}}}},"403":{"description":"`insufficient_scope`.","content":{"application/json":{"schema":{"$ref":"#/components/schemas/V1Error"}}}},"404":{"description":"`finn_not_found`, or `not_found` when Finn editing by API is off for your organization.","content":{"application/json":{"schema":{"$ref":"#/components/schemas/V1Error"}}}},"429":{"$ref":"#/components/responses/RateLimited"},"500":{"$ref":"#/components/responses/InternalError"}},"x-required-scope":"finn:read","x-mcp-tool":"get_finn_stats"}},"/finns/{finn_id}/outcomes":{"parameters":[{"name":"finn_id","in":"path","required":true,"description":"The Finn's ID.","schema":{"type":"string","format":"uuid"}},{"$ref":"#/components/parameters/FinnVersion"}],"get":{"tags":["Finns"],"operationId":"getFinnOutcomes","summary":"Summarize a Finn's call outcomes","description":"**Scope:** `finn:read` (an org API key may use `voice:read` instead). **Needs** Finn editing by API turned on for your organization (off: every route here answers `404`).\n\nPost-call answers summarized per field: counts per choice, number ranges, and free-text answers only when the same answer came up at least 3 times (digits masked).\n\n**Rate limit:** 300 requests per minute per API key.","parameters":[{"name":"from","in":"query","required":false,"description":"First day, `YYYY-MM-DD` (default: 30 days before `to`).","schema":{"type":"string","format":"date"}},{"name":"to","in":"query","required":false,"description":"Last day, `YYYY-MM-DD` (default: today). At most 366 days.","schema":{"type":"string","format":"date"}}],"responses":{"200":{"description":"The summary.","headers":{"RateLimit-Policy":{"$ref":"#/components/headers/RateLimitPolicy"},"RateLimit-Limit":{"$ref":"#/components/headers/RateLimitLimit"},"RateLimit-Remaining":{"$ref":"#/components/headers/RateLimitRemaining"},"RateLimit-Reset":{"$ref":"#/components/headers/RateLimitReset"}},"content":{"application/json":{"schema":{"type":"object","required":["success","data"],"properties":{"success":{"type":"boolean","const":true},"data":{"type":"object","additionalProperties":true}}}}}},"400":{"description":"Invalid input (`invalid_request`, with `fields` where a field is at fault).","content":{"application/json":{"schema":{"$ref":"#/components/schemas/V1Error"}}}},"401":{"description":"Missing or invalid credential.","content":{"application/json":{"schema":{"$ref":"#/components/schemas/V1Error"}}}},"403":{"description":"`insufficient_scope`.","content":{"application/json":{"schema":{"$ref":"#/components/schemas/V1Error"}}}},"404":{"description":"`finn_not_found`, or `not_found` when Finn editing by API is off for your organization.","content":{"application/json":{"schema":{"$ref":"#/components/schemas/V1Error"}}}},"429":{"$ref":"#/components/responses/RateLimited"},"500":{"$ref":"#/components/responses/InternalError"}},"x-required-scope":"finn:read","x-mcp-tool":"summarize_finn_outcomes"}},"/sales/plays":{"get":{"tags":["Sales"],"operationId":"listSalesPlays","summary":"List Sales AI plays","description":"**Scope:** `sales:read`.\n\nEvery play in this organization. One page; `next_cursor` is always `null`. Objects are sdr-service's, passed through as they are (camelCase).\n\n**Rate limit:** 300 reads and 60 writes per minute per API key across the Sales API.","responses":{"200":{"description":"The plays.","content":{"application/json":{"schema":{"$ref":"#/components/schemas/ChatList"}}}},"400":{"description":"The request is not valid (`invalid_request`, with `fields`), or `cursor` came from another list or other filters (`invalid_cursor`).","content":{"application/json":{"schema":{"allOf":[{"$ref":"#/components/schemas/V1Error"},{"type":"object","properties":{"error":{"type":"object","properties":{"code":{"type":"string","enum":["invalid_request","invalid_cursor"]}}}}}]}}}},"401":{"description":"Missing, unknown or revoked credentials.","content":{"application/json":{"schema":{"allOf":[{"$ref":"#/components/schemas/V1Error"},{"type":"object","properties":{"error":{"type":"object","properties":{"code":{"type":"string","enum":["api_key_required","invalid_api_key","unauthorized","key_expired"]}}}}}]}}}},"403":{"description":"The key lacks the scope (`insufficient_scope`), the credential cannot use this endpoint (`credential_not_supported`), or Sales AI refused (`forbidden`, `admin_only`).","content":{"application/json":{"schema":{"allOf":[{"$ref":"#/components/schemas/V1Error"},{"type":"object","properties":{"error":{"type":"object","properties":{"code":{"type":"string","enum":["insufficient_scope","credential_not_supported","forbidden","admin_only"]}}}}}]}}}},"404":{"description":"Not found, or the Sales AI API is not switched on for this organization (registry flag `v1_sales`): `not_found`.","content":{"application/json":{"schema":{"allOf":[{"$ref":"#/components/schemas/V1Error"},{"type":"object","properties":{"error":{"type":"object","properties":{"code":{"type":"string","enum":["not_found"]}}}}}]}}}},"429":{"description":"Too many requests (`rate_limited`). Retry after the `Retry-After` header.","content":{"application/json":{"schema":{"allOf":[{"$ref":"#/components/schemas/V1Error"},{"type":"object","properties":{"error":{"type":"object","properties":{"code":{"type":"string","enum":["rate_limited"]}}}}}]}}}},"503":{"description":"Sales AI is briefly unavailable (`unavailable`). Safe to retry.","content":{"application/json":{"schema":{"allOf":[{"$ref":"#/components/schemas/V1Error"},{"type":"object","properties":{"error":{"type":"object","properties":{"code":{"type":"string","enum":["unavailable"]}}}}}]}}}}},"x-required-scope":"sales:read","x-mcp-tool":"list_sales_plays"},"parameters":[{"$ref":"#/components/parameters/FinnVersion"}]},"/sales/plays/{slug}/runs":{"get":{"tags":["Sales"],"operationId":"listSalesPlayRuns","summary":"List a play's runs","description":"**Scope:** `sales:read`.\n\nThe play's latest runs, newest first. One page of up to `limit`. Objects are sdr-service's, passed through as they are (camelCase).\n\n**Rate limit:** 300 reads and 60 writes per minute per API key across the Sales API.","parameters":[{"name":"slug","in":"path","required":true,"description":"The play slug.","schema":{"type":"string","pattern":"^[a-zA-Z0-9][a-zA-Z0-9_-]{0,79}$"}},{"name":"limit","in":"query","required":false,"description":"Page size, 1 to 100 (default 20).","schema":{"type":"integer","minimum":1,"maximum":100,"default":20}}],"responses":{"200":{"description":"The runs.","content":{"application/json":{"schema":{"$ref":"#/components/schemas/ChatList"}}}},"400":{"description":"The request is not valid (`invalid_request`, with `fields`), or `cursor` came from another list or other filters (`invalid_cursor`).","content":{"application/json":{"schema":{"allOf":[{"$ref":"#/components/schemas/V1Error"},{"type":"object","properties":{"error":{"type":"object","properties":{"code":{"type":"string","enum":["invalid_request","invalid_cursor"]}}}}}]}}}},"401":{"description":"Missing, unknown or revoked credentials.","content":{"application/json":{"schema":{"allOf":[{"$ref":"#/components/schemas/V1Error"},{"type":"object","properties":{"error":{"type":"object","properties":{"code":{"type":"string","enum":["api_key_required","invalid_api_key","unauthorized","key_expired"]}}}}}]}}}},"403":{"description":"The key lacks the scope (`insufficient_scope`), the credential cannot use this endpoint (`credential_not_supported`), or Sales AI refused (`forbidden`, `admin_only`).","content":{"application/json":{"schema":{"allOf":[{"$ref":"#/components/schemas/V1Error"},{"type":"object","properties":{"error":{"type":"object","properties":{"code":{"type":"string","enum":["insufficient_scope","credential_not_supported","forbidden","admin_only"]}}}}}]}}}},"404":{"description":"Not found, or the Sales AI API is not switched on for this organization (registry flag `v1_sales`): `not_found`, `play_not_found`.","content":{"application/json":{"schema":{"allOf":[{"$ref":"#/components/schemas/V1Error"},{"type":"object","properties":{"error":{"type":"object","properties":{"code":{"type":"string","enum":["not_found","play_not_found"]}}}}}]}}}},"429":{"description":"Too many requests (`rate_limited`). Retry after the `Retry-After` header.","content":{"application/json":{"schema":{"allOf":[{"$ref":"#/components/schemas/V1Error"},{"type":"object","properties":{"error":{"type":"object","properties":{"code":{"type":"string","enum":["rate_limited"]}}}}}]}}}},"503":{"description":"Sales AI is briefly unavailable (`unavailable`). Safe to retry.","content":{"application/json":{"schema":{"allOf":[{"$ref":"#/components/schemas/V1Error"},{"type":"object","properties":{"error":{"type":"object","properties":{"code":{"type":"string","enum":["unavailable"]}}}}}]}}}}},"x-required-scope":"sales:read","x-mcp-tool":"list_sales_play_runs"},"parameters":[{"$ref":"#/components/parameters/FinnVersion"}]},"/sales/plays/{slug}/prospects":{"get":{"tags":["Sales"],"operationId":"listSalesProspects","summary":"List a play's prospects","description":"**Scope:** `sales:read`.\n\nThe people the play found. Objects are sdr-service's, passed through as they are (camelCase).\n\n**Rate limit:** 300 reads and 60 writes per minute per API key across the Sales API.","parameters":[{"name":"slug","in":"path","required":true,"description":"The play slug.","schema":{"type":"string","pattern":"^[a-zA-Z0-9][a-zA-Z0-9_-]{0,79}$"}},{"name":"country","in":"query","required":false,"description":"ISO country code.","schema":{"type":"string","maxLength":100}},{"name":"email_status","in":"query","required":false,"description":"valid, catch_all, invalid, unknown or unverified.","schema":{"type":"string","maxLength":100}},{"name":"limit","in":"query","required":false,"description":"Page size, 1 to 100 (default 50).","schema":{"type":"integer","minimum":1,"maximum":100,"default":50}},{"name":"cursor","in":"query","required":false,"description":"The `next_cursor` of the previous page, with the same filters.","schema":{"type":"string"}}],"responses":{"200":{"description":"A page of prospects.","content":{"application/json":{"schema":{"allOf":[{"$ref":"#/components/schemas/ChatList"},{"type":"object","properties":{"total":{"type":["integer","null"]}}}]}}}},"400":{"description":"The request is not valid (`invalid_request`, with `fields`), or `cursor` came from another list or other filters (`invalid_cursor`).","content":{"application/json":{"schema":{"allOf":[{"$ref":"#/components/schemas/V1Error"},{"type":"object","properties":{"error":{"type":"object","properties":{"code":{"type":"string","enum":["invalid_request","invalid_cursor"]}}}}}]}}}},"401":{"description":"Missing, unknown or revoked credentials.","content":{"application/json":{"schema":{"allOf":[{"$ref":"#/components/schemas/V1Error"},{"type":"object","properties":{"error":{"type":"object","properties":{"code":{"type":"string","enum":["api_key_required","invalid_api_key","unauthorized","key_expired"]}}}}}]}}}},"403":{"description":"The key lacks the scope (`insufficient_scope`), the credential cannot use this endpoint (`credential_not_supported`), or Sales AI refused (`forbidden`, `admin_only`).","content":{"application/json":{"schema":{"allOf":[{"$ref":"#/components/schemas/V1Error"},{"type":"object","properties":{"error":{"type":"object","properties":{"code":{"type":"string","enum":["insufficient_scope","credential_not_supported","forbidden","admin_only"]}}}}}]}}}},"404":{"description":"Not found, or the Sales AI API is not switched on for this organization (registry flag `v1_sales`): `not_found`, `play_not_found`.","content":{"application/json":{"schema":{"allOf":[{"$ref":"#/components/schemas/V1Error"},{"type":"object","properties":{"error":{"type":"object","properties":{"code":{"type":"string","enum":["not_found","play_not_found"]}}}}}]}}}},"429":{"description":"Too many requests (`rate_limited`). Retry after the `Retry-After` header.","content":{"application/json":{"schema":{"allOf":[{"$ref":"#/components/schemas/V1Error"},{"type":"object","properties":{"error":{"type":"object","properties":{"code":{"type":"string","enum":["rate_limited"]}}}}}]}}}},"503":{"description":"Sales AI is briefly unavailable (`unavailable`). Safe to retry.","content":{"application/json":{"schema":{"allOf":[{"$ref":"#/components/schemas/V1Error"},{"type":"object","properties":{"error":{"type":"object","properties":{"code":{"type":"string","enum":["unavailable"]}}}}}]}}}}},"x-required-scope":"sales:read","x-mcp-tool":"list_sales_prospects"},"parameters":[{"$ref":"#/components/parameters/FinnVersion"}]},"/sales/plays/{slug}/run":{"post":{"tags":["Sales"],"operationId":"runSalesPlay","summary":"Run a play","description":"**Scope:** `sales:write`.\n\nQueues a run of the play (202). The drafts it makes wait for a person to approve them in Finn: the API never approves or sends. Objects are sdr-service's, passed through as they are (camelCase).\n\n**Rate limit:** 300 reads and 60 writes per minute per API key across the Sales API.\n\n**Approval hold (off by default):** where the organization has `v1_hold_inbound_sales_runs` on, this request can be held for a person to approve, like a campaign launch: it answers 202 `PendingApproval` and nothing runs until it is approved. A member's token is held unless the organization relaxed `sales.run`; an org key is held where the hold policy holds org keys for it.","parameters":[{"name":"slug","in":"path","required":true,"description":"The play slug.","schema":{"type":"string","pattern":"^[a-zA-Z0-9][a-zA-Z0-9_-]{0,79}$"}},{"$ref":"#/components/parameters/IdempotencyKey"}],"responses":{"202":{"description":"Queued. Or a `PendingApproval` when the request is held for a person to approve (nothing has run yet).","content":{"application/json":{"schema":{"oneOf":[{"type":"object","additionalProperties":true},{"$ref":"#/components/schemas/PendingApproval"}]}}}},"400":{"description":"The request is not valid (`invalid_request`, with `fields`), or `cursor` came from another list or other filters (`invalid_cursor`).","content":{"application/json":{"schema":{"allOf":[{"$ref":"#/components/schemas/V1Error"},{"type":"object","properties":{"error":{"type":"object","properties":{"code":{"type":"string","enum":["invalid_request","invalid_cursor"]}}}}}]}}}},"401":{"description":"Missing, unknown or revoked credentials.","content":{"application/json":{"schema":{"allOf":[{"$ref":"#/components/schemas/V1Error"},{"type":"object","properties":{"error":{"type":"object","properties":{"code":{"type":"string","enum":["api_key_required","invalid_api_key","unauthorized","key_expired"]}}}}}]}}}},"403":{"description":"The key lacks the scope (`insufficient_scope`), the credential cannot use this endpoint (`credential_not_supported`), or Sales AI refused (`forbidden`, `admin_only`).","content":{"application/json":{"schema":{"allOf":[{"$ref":"#/components/schemas/V1Error"},{"type":"object","properties":{"error":{"type":"object","properties":{"code":{"type":"string","enum":["insufficient_scope","credential_not_supported","forbidden","admin_only"]}}}}}]}}}},"404":{"description":"Not found, or the Sales AI API is not switched on for this organization (registry flag `v1_sales`): `not_found`, `play_not_found`.","content":{"application/json":{"schema":{"allOf":[{"$ref":"#/components/schemas/V1Error"},{"type":"object","properties":{"error":{"type":"object","properties":{"code":{"type":"string","enum":["not_found","play_not_found"]}}}}}]}}}},"429":{"description":"Too many requests (`rate_limited`). Retry after the `Retry-After` header.","content":{"application/json":{"schema":{"allOf":[{"$ref":"#/components/schemas/V1Error"},{"type":"object","properties":{"error":{"type":"object","properties":{"code":{"type":"string","enum":["rate_limited"]}}}}}]}}}},"503":{"description":"Sales AI is briefly unavailable (`unavailable`). Safe to retry.","content":{"application/json":{"schema":{"allOf":[{"$ref":"#/components/schemas/V1Error"},{"type":"object","properties":{"error":{"type":"object","properties":{"code":{"type":"string","enum":["unavailable"]}}}}}]}}}}},"x-required-scope":"sales:write","x-mcp-tool":"run_sales_play","x-finn-hold":{"action":"sales.run","class":"outward","feature":"v1_hold_inbound_sales_runs"}},"parameters":[{"$ref":"#/components/parameters/FinnVersion"}]},"/sales/drafts":{"get":{"tags":["Sales"],"operationId":"listSalesDrafts","summary":"List drafts","description":"**Scope:** `sales:read`.\n\nEmail drafts, newest first. Objects are sdr-service's, passed through as they are (camelCase).\n\n**Rate limit:** 300 reads and 60 writes per minute per API key across the Sales API.","parameters":[{"name":"status","in":"query","required":false,"description":"draft, approved, scheduled, sending, sent, skipped, expired or failed.","schema":{"type":"string","maxLength":100}},{"name":"kind","in":"query","required":false,"description":"first, follow_up or reply.","schema":{"type":"string","maxLength":100}},{"name":"play","in":"query","required":false,"description":"A play slug.","schema":{"type":"string","maxLength":100}},{"name":"limit","in":"query","required":false,"description":"Page size, 1 to 100 (default 50).","schema":{"type":"integer","minimum":1,"maximum":100,"default":50}},{"name":"cursor","in":"query","required":false,"description":"The `next_cursor` of the previous page, with the same filters.","schema":{"type":"string"}}],"responses":{"200":{"description":"A page of drafts.","content":{"application/json":{"schema":{"allOf":[{"$ref":"#/components/schemas/ChatList"},{"type":"object","properties":{"total":{"type":["integer","null"]}}}]}}}},"400":{"description":"The request is not valid (`invalid_request`, with `fields`), or `cursor` came from another list or other filters (`invalid_cursor`).","content":{"application/json":{"schema":{"allOf":[{"$ref":"#/components/schemas/V1Error"},{"type":"object","properties":{"error":{"type":"object","properties":{"code":{"type":"string","enum":["invalid_request","invalid_cursor"]}}}}}]}}}},"401":{"description":"Missing, unknown or revoked credentials.","content":{"application/json":{"schema":{"allOf":[{"$ref":"#/components/schemas/V1Error"},{"type":"object","properties":{"error":{"type":"object","properties":{"code":{"type":"string","enum":["api_key_required","invalid_api_key","unauthorized","key_expired"]}}}}}]}}}},"403":{"description":"The key lacks the scope (`insufficient_scope`), the credential cannot use this endpoint (`credential_not_supported`), or Sales AI refused (`forbidden`, `admin_only`).","content":{"application/json":{"schema":{"allOf":[{"$ref":"#/components/schemas/V1Error"},{"type":"object","properties":{"error":{"type":"object","properties":{"code":{"type":"string","enum":["insufficient_scope","credential_not_supported","forbidden","admin_only"]}}}}}]}}}},"404":{"description":"Not found, or the Sales AI API is not switched on for this organization (registry flag `v1_sales`): `not_found`.","content":{"application/json":{"schema":{"allOf":[{"$ref":"#/components/schemas/V1Error"},{"type":"object","properties":{"error":{"type":"object","properties":{"code":{"type":"string","enum":["not_found"]}}}}}]}}}},"429":{"description":"Too many requests (`rate_limited`). Retry after the `Retry-After` header.","content":{"application/json":{"schema":{"allOf":[{"$ref":"#/components/schemas/V1Error"},{"type":"object","properties":{"error":{"type":"object","properties":{"code":{"type":"string","enum":["rate_limited"]}}}}}]}}}},"503":{"description":"Sales AI is briefly unavailable (`unavailable`). Safe to retry.","content":{"application/json":{"schema":{"allOf":[{"$ref":"#/components/schemas/V1Error"},{"type":"object","properties":{"error":{"type":"object","properties":{"code":{"type":"string","enum":["unavailable"]}}}}}]}}}}},"x-required-scope":"sales:read","x-mcp-tool":"list_sales_drafts"},"parameters":[{"$ref":"#/components/parameters/FinnVersion"}]},"/sales/drafts/{id}":{"patch":{"tags":["Sales"],"operationId":"updateSalesDraft","summary":"Edit a draft","description":"**Scope:** `sales:write`.\n\nReplaces the draft's subject and body. It still waits for a person to approve it. Objects are sdr-service's, passed through as they are (camelCase).\n\n**Rate limit:** 300 reads and 60 writes per minute per API key across the Sales API.","parameters":[{"name":"id","in":"path","required":true,"description":"The draft id (digits).","schema":{"type":"string","pattern":"^\\d{1,18}$"}}],"requestBody":{"required":true,"content":{"application/json":{"schema":{"type":"object","required":["subject","body"],"properties":{"subject":{"type":"string","minLength":1,"maxLength":200},"body":{"type":"string","minLength":1,"maxLength":5000}}}}}},"responses":{"200":{"description":"The draft.","content":{"application/json":{"schema":{"type":"object","additionalProperties":true}}}},"400":{"description":"The request is not valid (`invalid_request`, with `fields`), or `cursor` came from another list or other filters (`invalid_cursor`).","content":{"application/json":{"schema":{"allOf":[{"$ref":"#/components/schemas/V1Error"},{"type":"object","properties":{"error":{"type":"object","properties":{"code":{"type":"string","enum":["invalid_request","invalid_cursor"]}}}}}]}}}},"401":{"description":"Missing, unknown or revoked credentials.","content":{"application/json":{"schema":{"allOf":[{"$ref":"#/components/schemas/V1Error"},{"type":"object","properties":{"error":{"type":"object","properties":{"code":{"type":"string","enum":["api_key_required","invalid_api_key","unauthorized","key_expired"]}}}}}]}}}},"403":{"description":"The key lacks the scope (`insufficient_scope`), the credential cannot use this endpoint (`credential_not_supported`), or Sales AI refused (`forbidden`, `admin_only`).","content":{"application/json":{"schema":{"allOf":[{"$ref":"#/components/schemas/V1Error"},{"type":"object","properties":{"error":{"type":"object","properties":{"code":{"type":"string","enum":["insufficient_scope","credential_not_supported","forbidden","admin_only"]}}}}}]}}}},"404":{"description":"Not found, or the Sales AI API is not switched on for this organization (registry flag `v1_sales`): `not_found`, `draft_not_found`.","content":{"application/json":{"schema":{"allOf":[{"$ref":"#/components/schemas/V1Error"},{"type":"object","properties":{"error":{"type":"object","properties":{"code":{"type":"string","enum":["not_found","draft_not_found"]}}}}}]}}}},"429":{"description":"Too many requests (`rate_limited`). Retry after the `Retry-After` header.","content":{"application/json":{"schema":{"allOf":[{"$ref":"#/components/schemas/V1Error"},{"type":"object","properties":{"error":{"type":"object","properties":{"code":{"type":"string","enum":["rate_limited"]}}}}}]}}}},"503":{"description":"Sales AI is briefly unavailable (`unavailable`). Safe to retry.","content":{"application/json":{"schema":{"allOf":[{"$ref":"#/components/schemas/V1Error"},{"type":"object","properties":{"error":{"type":"object","properties":{"code":{"type":"string","enum":["unavailable"]}}}}}]}}}}},"x-required-scope":"sales:write","x-mcp-tool":"edit_sales_draft"},"parameters":[{"$ref":"#/components/parameters/FinnVersion"}]},"/sales/drafts/{id}/skip":{"post":{"tags":["Sales"],"operationId":"skipSalesDraft","summary":"Skip a draft","description":"**Scope:** `sales:write`.\n\nMarks the draft skipped so it is never sent. Objects are sdr-service's, passed through as they are (camelCase).\n\n**Rate limit:** 300 reads and 60 writes per minute per API key across the Sales API.","parameters":[{"name":"id","in":"path","required":true,"description":"The draft id (digits).","schema":{"type":"string","pattern":"^\\d{1,18}$"}},{"$ref":"#/components/parameters/IdempotencyKey"}],"requestBody":{"required":true,"content":{"application/json":{"schema":{"type":"object","properties":{"reason":{"type":"string","maxLength":300}}}}}},"responses":{"200":{"description":"The draft.","content":{"application/json":{"schema":{"type":"object","additionalProperties":true}}}},"400":{"description":"The request is not valid (`invalid_request`, with `fields`), or `cursor` came from another list or other filters (`invalid_cursor`).","content":{"application/json":{"schema":{"allOf":[{"$ref":"#/components/schemas/V1Error"},{"type":"object","properties":{"error":{"type":"object","properties":{"code":{"type":"string","enum":["invalid_request","invalid_cursor"]}}}}}]}}}},"401":{"description":"Missing, unknown or revoked credentials.","content":{"application/json":{"schema":{"allOf":[{"$ref":"#/components/schemas/V1Error"},{"type":"object","properties":{"error":{"type":"object","properties":{"code":{"type":"string","enum":["api_key_required","invalid_api_key","unauthorized","key_expired"]}}}}}]}}}},"403":{"description":"The key lacks the scope (`insufficient_scope`), the credential cannot use this endpoint (`credential_not_supported`), or Sales AI refused (`forbidden`, `admin_only`).","content":{"application/json":{"schema":{"allOf":[{"$ref":"#/components/schemas/V1Error"},{"type":"object","properties":{"error":{"type":"object","properties":{"code":{"type":"string","enum":["insufficient_scope","credential_not_supported","forbidden","admin_only"]}}}}}]}}}},"404":{"description":"Not found, or the Sales AI API is not switched on for this organization (registry flag `v1_sales`): `not_found`, `draft_not_found`.","content":{"application/json":{"schema":{"allOf":[{"$ref":"#/components/schemas/V1Error"},{"type":"object","properties":{"error":{"type":"object","properties":{"code":{"type":"string","enum":["not_found","draft_not_found"]}}}}}]}}}},"429":{"description":"Too many requests (`rate_limited`). Retry after the `Retry-After` header.","content":{"application/json":{"schema":{"allOf":[{"$ref":"#/components/schemas/V1Error"},{"type":"object","properties":{"error":{"type":"object","properties":{"code":{"type":"string","enum":["rate_limited"]}}}}}]}}}},"503":{"description":"Sales AI is briefly unavailable (`unavailable`). Safe to retry.","content":{"application/json":{"schema":{"allOf":[{"$ref":"#/components/schemas/V1Error"},{"type":"object","properties":{"error":{"type":"object","properties":{"code":{"type":"string","enum":["unavailable"]}}}}}]}}}}},"x-required-scope":"sales:write","x-mcp-tool":"skip_sales_draft"},"parameters":[{"$ref":"#/components/parameters/FinnVersion"}]},"/sales/threads":{"get":{"tags":["Sales"],"operationId":"listSalesThreads","summary":"List threads","description":"**Scope:** `sales:read`.\n\nEmail threads with prospects, latest first. Objects are sdr-service's, passed through as they are (camelCase).\n\n**Rate limit:** 300 reads and 60 writes per minute per API key across the Sales API.","parameters":[{"name":"label","in":"query","required":false,"description":"A reply label.","schema":{"type":"string","maxLength":100}},{"name":"play","in":"query","required":false,"description":"A play slug.","schema":{"type":"string","maxLength":100}},{"name":"limit","in":"query","required":false,"description":"Page size, 1 to 100 (default 50).","schema":{"type":"integer","minimum":1,"maximum":100,"default":50}},{"name":"cursor","in":"query","required":false,"description":"The `next_cursor` of the previous page, with the same filters.","schema":{"type":"string"}}],"responses":{"200":{"description":"A page of threads.","content":{"application/json":{"schema":{"allOf":[{"$ref":"#/components/schemas/ChatList"},{"type":"object","properties":{"total":{"type":["integer","null"]}}}]}}}},"400":{"description":"The request is not valid (`invalid_request`, with `fields`), or `cursor` came from another list or other filters (`invalid_cursor`).","content":{"application/json":{"schema":{"allOf":[{"$ref":"#/components/schemas/V1Error"},{"type":"object","properties":{"error":{"type":"object","properties":{"code":{"type":"string","enum":["invalid_request","invalid_cursor"]}}}}}]}}}},"401":{"description":"Missing, unknown or revoked credentials.","content":{"application/json":{"schema":{"allOf":[{"$ref":"#/components/schemas/V1Error"},{"type":"object","properties":{"error":{"type":"object","properties":{"code":{"type":"string","enum":["api_key_required","invalid_api_key","unauthorized","key_expired"]}}}}}]}}}},"403":{"description":"The key lacks the scope (`insufficient_scope`), the credential cannot use this endpoint (`credential_not_supported`), or Sales AI refused (`forbidden`, `admin_only`).","content":{"application/json":{"schema":{"allOf":[{"$ref":"#/components/schemas/V1Error"},{"type":"object","properties":{"error":{"type":"object","properties":{"code":{"type":"string","enum":["insufficient_scope","credential_not_supported","forbidden","admin_only"]}}}}}]}}}},"404":{"description":"Not found, or the Sales AI API is not switched on for this organization (registry flag `v1_sales`): `not_found`.","content":{"application/json":{"schema":{"allOf":[{"$ref":"#/components/schemas/V1Error"},{"type":"object","properties":{"error":{"type":"object","properties":{"code":{"type":"string","enum":["not_found"]}}}}}]}}}},"429":{"description":"Too many requests (`rate_limited`). Retry after the `Retry-After` header.","content":{"application/json":{"schema":{"allOf":[{"$ref":"#/components/schemas/V1Error"},{"type":"object","properties":{"error":{"type":"object","properties":{"code":{"type":"string","enum":["rate_limited"]}}}}}]}}}},"503":{"description":"Sales AI is briefly unavailable (`unavailable`). Safe to retry.","content":{"application/json":{"schema":{"allOf":[{"$ref":"#/components/schemas/V1Error"},{"type":"object","properties":{"error":{"type":"object","properties":{"code":{"type":"string","enum":["unavailable"]}}}}}]}}}}},"x-required-scope":"sales:read","x-mcp-tool":"list_sales_threads"},"parameters":[{"$ref":"#/components/parameters/FinnVersion"}]},"/sales/threads/{id}":{"get":{"tags":["Sales"],"operationId":"getSalesThread","summary":"Get a thread","description":"**Scope:** `sales:read`.\n\nThe thread and its messages. Objects are sdr-service's, passed through as they are (camelCase).\n\n**Rate limit:** 300 reads and 60 writes per minute per API key across the Sales API.","parameters":[{"name":"id","in":"path","required":true,"description":"The thread id (digits).","schema":{"type":"string","pattern":"^\\d{1,18}$"}}],"responses":{"200":{"description":"OK.","content":{"application/json":{"schema":{"type":"object","additionalProperties":true}}}},"400":{"description":"The request is not valid (`invalid_request`, with `fields`), or `cursor` came from another list or other filters (`invalid_cursor`).","content":{"application/json":{"schema":{"allOf":[{"$ref":"#/components/schemas/V1Error"},{"type":"object","properties":{"error":{"type":"object","properties":{"code":{"type":"string","enum":["invalid_request","invalid_cursor"]}}}}}]}}}},"401":{"description":"Missing, unknown or revoked credentials.","content":{"application/json":{"schema":{"allOf":[{"$ref":"#/components/schemas/V1Error"},{"type":"object","properties":{"error":{"type":"object","properties":{"code":{"type":"string","enum":["api_key_required","invalid_api_key","unauthorized","key_expired"]}}}}}]}}}},"403":{"description":"The key lacks the scope (`insufficient_scope`), the credential cannot use this endpoint (`credential_not_supported`), or Sales AI refused (`forbidden`, `admin_only`).","content":{"application/json":{"schema":{"allOf":[{"$ref":"#/components/schemas/V1Error"},{"type":"object","properties":{"error":{"type":"object","properties":{"code":{"type":"string","enum":["insufficient_scope","credential_not_supported","forbidden","admin_only"]}}}}}]}}}},"404":{"description":"Not found, or the Sales AI API is not switched on for this organization (registry flag `v1_sales`): `not_found`, `thread_not_found`.","content":{"application/json":{"schema":{"allOf":[{"$ref":"#/components/schemas/V1Error"},{"type":"object","properties":{"error":{"type":"object","properties":{"code":{"type":"string","enum":["not_found","thread_not_found"]}}}}}]}}}},"429":{"description":"Too many requests (`rate_limited`). Retry after the `Retry-After` header.","content":{"application/json":{"schema":{"allOf":[{"$ref":"#/components/schemas/V1Error"},{"type":"object","properties":{"error":{"type":"object","properties":{"code":{"type":"string","enum":["rate_limited"]}}}}}]}}}},"503":{"description":"Sales AI is briefly unavailable (`unavailable`). Safe to retry.","content":{"application/json":{"schema":{"allOf":[{"$ref":"#/components/schemas/V1Error"},{"type":"object","properties":{"error":{"type":"object","properties":{"code":{"type":"string","enum":["unavailable"]}}}}}]}}}}},"x-required-scope":"sales:read","x-mcp-tool":"read_sales_thread"},"parameters":[{"$ref":"#/components/parameters/FinnVersion"}]},"/dialer/sessions":{"get":{"tags":["Dialer"],"operationId":"listDialerSessions","summary":"List dialer sessions","description":"**Scope:** `dialer:read`.\n\nEnded dialing sessions, latest first. An org key reads the whole team. Objects are rep-dialer-service's, passed through as they are (camelCase).\n\n**Rate limit:** 300 requests per minute per API key across the Dialer API.","parameters":[{"name":"status","in":"query","required":false,"description":"Only `ended`.","schema":{"type":"string","enum":["ended"]}},{"name":"limit","in":"query","required":false,"description":"Page size, 1 to 100 (default 50).","schema":{"type":"integer","minimum":1,"maximum":100,"default":50}},{"name":"cursor","in":"query","required":false,"description":"The `next_cursor` of the previous page, with the same filters.","schema":{"type":"string"}}],"responses":{"200":{"description":"A page of sessions.","content":{"application/json":{"schema":{"$ref":"#/components/schemas/ChatList"}}}},"400":{"description":"The request is not valid (`invalid_request`, with `fields`), or `cursor` came from another list or other filters (`invalid_cursor`).","content":{"application/json":{"schema":{"allOf":[{"$ref":"#/components/schemas/V1Error"},{"type":"object","properties":{"error":{"type":"object","properties":{"code":{"type":"string","enum":["invalid_request","invalid_cursor"]}}}}}]}}}},"401":{"description":"Missing, unknown or revoked credentials.","content":{"application/json":{"schema":{"allOf":[{"$ref":"#/components/schemas/V1Error"},{"type":"object","properties":{"error":{"type":"object","properties":{"code":{"type":"string","enum":["api_key_required","invalid_api_key","unauthorized","key_expired"]}}}}}]}}}},"403":{"description":"The key lacks the scope (`insufficient_scope`), the credential cannot use this endpoint (`credential_not_supported`), or The dialer refused (`forbidden`, `admin_only`).","content":{"application/json":{"schema":{"allOf":[{"$ref":"#/components/schemas/V1Error"},{"type":"object","properties":{"error":{"type":"object","properties":{"code":{"type":"string","enum":["insufficient_scope","credential_not_supported","forbidden","admin_only"]}}}}}]}}}},"404":{"description":"Not found, or the The dialer API is not switched on for this organization (registry flag `v1_dialer`): `not_found`.","content":{"application/json":{"schema":{"allOf":[{"$ref":"#/components/schemas/V1Error"},{"type":"object","properties":{"error":{"type":"object","properties":{"code":{"type":"string","enum":["not_found"]}}}}}]}}}},"429":{"description":"Too many requests (`rate_limited`). Retry after the `Retry-After` header.","content":{"application/json":{"schema":{"allOf":[{"$ref":"#/components/schemas/V1Error"},{"type":"object","properties":{"error":{"type":"object","properties":{"code":{"type":"string","enum":["rate_limited"]}}}}}]}}}},"503":{"description":"The dialer is briefly unavailable (`unavailable`). Safe to retry.","content":{"application/json":{"schema":{"allOf":[{"$ref":"#/components/schemas/V1Error"},{"type":"object","properties":{"error":{"type":"object","properties":{"code":{"type":"string","enum":["unavailable"]}}}}}]}}}}},"x-required-scope":"dialer:read","x-mcp-tool":"list_rep_sessions"},"parameters":[{"$ref":"#/components/parameters/FinnVersion"}]},"/dialer/sessions/{id}":{"get":{"tags":["Dialer"],"operationId":"getDialerSession","summary":"Get a dialer session","description":"**Scope:** `dialer:read`.\n\nOne session. An org key reads the whole team. Objects are rep-dialer-service's, passed through as they are (camelCase).\n\n**Rate limit:** 300 requests per minute per API key across the Dialer API.","parameters":[{"name":"id","in":"path","required":true,"description":"The session id (UUID).","schema":{"type":"string","pattern":"^[0-9a-fA-F]{8}-[0-9a-fA-F]{4}-[0-9a-fA-F]{4}-[0-9a-fA-F]{4}-[0-9a-fA-F]{12}$"}}],"responses":{"200":{"description":"OK.","content":{"application/json":{"schema":{"type":"object","additionalProperties":true}}}},"400":{"description":"The request is not valid (`invalid_request`, with `fields`), or `cursor` came from another list or other filters (`invalid_cursor`).","content":{"application/json":{"schema":{"allOf":[{"$ref":"#/components/schemas/V1Error"},{"type":"object","properties":{"error":{"type":"object","properties":{"code":{"type":"string","enum":["invalid_request","invalid_cursor"]}}}}}]}}}},"401":{"description":"Missing, unknown or revoked credentials.","content":{"application/json":{"schema":{"allOf":[{"$ref":"#/components/schemas/V1Error"},{"type":"object","properties":{"error":{"type":"object","properties":{"code":{"type":"string","enum":["api_key_required","invalid_api_key","unauthorized","key_expired"]}}}}}]}}}},"403":{"description":"The key lacks the scope (`insufficient_scope`), the credential cannot use this endpoint (`credential_not_supported`), or The dialer refused (`forbidden`, `admin_only`).","content":{"application/json":{"schema":{"allOf":[{"$ref":"#/components/schemas/V1Error"},{"type":"object","properties":{"error":{"type":"object","properties":{"code":{"type":"string","enum":["insufficient_scope","credential_not_supported","forbidden","admin_only"]}}}}}]}}}},"404":{"description":"Not found, or the The dialer API is not switched on for this organization (registry flag `v1_dialer`): `not_found`, `session_not_found`.","content":{"application/json":{"schema":{"allOf":[{"$ref":"#/components/schemas/V1Error"},{"type":"object","properties":{"error":{"type":"object","properties":{"code":{"type":"string","enum":["not_found","session_not_found"]}}}}}]}}}},"429":{"description":"Too many requests (`rate_limited`). Retry after the `Retry-After` header.","content":{"application/json":{"schema":{"allOf":[{"$ref":"#/components/schemas/V1Error"},{"type":"object","properties":{"error":{"type":"object","properties":{"code":{"type":"string","enum":["rate_limited"]}}}}}]}}}},"503":{"description":"The dialer is briefly unavailable (`unavailable`). Safe to retry.","content":{"application/json":{"schema":{"allOf":[{"$ref":"#/components/schemas/V1Error"},{"type":"object","properties":{"error":{"type":"object","properties":{"code":{"type":"string","enum":["unavailable"]}}}}}]}}}}},"x-required-scope":"dialer:read","x-mcp-tool":"get_rep_session"},"parameters":[{"$ref":"#/components/parameters/FinnVersion"}]},"/dialer/analytics":{"get":{"tags":["Dialer"],"operationId":"getDialerAnalytics","summary":"Dialer analytics","description":"**Scope:** `dialer:read`.\n\nTotals for sessions started in [from, to) (unix ms, at most a year; default up to now). An org key reads the whole team. Objects are rep-dialer-service's, passed through as they are (camelCase).\n\n**Rate limit:** 300 requests per minute per API key across the Dialer API.","parameters":[{"name":"from","in":"query","required":false,"description":"Unix milliseconds.","schema":{"type":"string","pattern":"^\\d{1,15}$"}},{"name":"to","in":"query","required":false,"description":"Unix milliseconds.","schema":{"type":"string","pattern":"^\\d{1,15}$"}}],"responses":{"200":{"description":"OK.","content":{"application/json":{"schema":{"type":"object","additionalProperties":true}}}},"400":{"description":"The request is not valid (`invalid_request`, with `fields`), or `cursor` came from another list or other filters (`invalid_cursor`).","content":{"application/json":{"schema":{"allOf":[{"$ref":"#/components/schemas/V1Error"},{"type":"object","properties":{"error":{"type":"object","properties":{"code":{"type":"string","enum":["invalid_request","invalid_cursor"]}}}}}]}}}},"401":{"description":"Missing, unknown or revoked credentials.","content":{"application/json":{"schema":{"allOf":[{"$ref":"#/components/schemas/V1Error"},{"type":"object","properties":{"error":{"type":"object","properties":{"code":{"type":"string","enum":["api_key_required","invalid_api_key","unauthorized","key_expired"]}}}}}]}}}},"403":{"description":"The key lacks the scope (`insufficient_scope`), the credential cannot use this endpoint (`credential_not_supported`), or The dialer refused (`forbidden`, `admin_only`).","content":{"application/json":{"schema":{"allOf":[{"$ref":"#/components/schemas/V1Error"},{"type":"object","properties":{"error":{"type":"object","properties":{"code":{"type":"string","enum":["insufficient_scope","credential_not_supported","forbidden","admin_only"]}}}}}]}}}},"404":{"description":"Not found, or the The dialer API is not switched on for this organization (registry flag `v1_dialer`): `not_found`.","content":{"application/json":{"schema":{"allOf":[{"$ref":"#/components/schemas/V1Error"},{"type":"object","properties":{"error":{"type":"object","properties":{"code":{"type":"string","enum":["not_found"]}}}}}]}}}},"429":{"description":"Too many requests (`rate_limited`). Retry after the `Retry-After` header.","content":{"application/json":{"schema":{"allOf":[{"$ref":"#/components/schemas/V1Error"},{"type":"object","properties":{"error":{"type":"object","properties":{"code":{"type":"string","enum":["rate_limited"]}}}}}]}}}},"503":{"description":"The dialer is briefly unavailable (`unavailable`). Safe to retry.","content":{"application/json":{"schema":{"allOf":[{"$ref":"#/components/schemas/V1Error"},{"type":"object","properties":{"error":{"type":"object","properties":{"code":{"type":"string","enum":["unavailable"]}}}}}]}}}}},"x-required-scope":"dialer:read","x-mcp-tool":"get_rep_analytics"},"parameters":[{"$ref":"#/components/parameters/FinnVersion"}]},"/dialer/follow-ups":{"get":{"tags":["Dialer"],"operationId":"listDialerFollowUps","summary":"List dialer follow-ups","description":"**Scope:** `dialer:read`.\n\nCalls waiting for a follow-up. One page; `next_cursor` is always `null`. An org key reads the whole team. Objects are rep-dialer-service's, passed through as they are (camelCase).\n\n**Rate limit:** 300 requests per minute per API key across the Dialer API.","responses":{"200":{"description":"The follow-ups.","content":{"application/json":{"schema":{"$ref":"#/components/schemas/ChatList"}}}},"400":{"description":"The request is not valid (`invalid_request`, with `fields`), or `cursor` came from another list or other filters (`invalid_cursor`).","content":{"application/json":{"schema":{"allOf":[{"$ref":"#/components/schemas/V1Error"},{"type":"object","properties":{"error":{"type":"object","properties":{"code":{"type":"string","enum":["invalid_request","invalid_cursor"]}}}}}]}}}},"401":{"description":"Missing, unknown or revoked credentials.","content":{"application/json":{"schema":{"allOf":[{"$ref":"#/components/schemas/V1Error"},{"type":"object","properties":{"error":{"type":"object","properties":{"code":{"type":"string","enum":["api_key_required","invalid_api_key","unauthorized","key_expired"]}}}}}]}}}},"403":{"description":"The key lacks the scope (`insufficient_scope`), the credential cannot use this endpoint (`credential_not_supported`), or The dialer refused (`forbidden`, `admin_only`).","content":{"application/json":{"schema":{"allOf":[{"$ref":"#/components/schemas/V1Error"},{"type":"object","properties":{"error":{"type":"object","properties":{"code":{"type":"string","enum":["insufficient_scope","credential_not_supported","forbidden","admin_only"]}}}}}]}}}},"404":{"description":"Not found, or the The dialer API is not switched on for this organization (registry flag `v1_dialer`): `not_found`.","content":{"application/json":{"schema":{"allOf":[{"$ref":"#/components/schemas/V1Error"},{"type":"object","properties":{"error":{"type":"object","properties":{"code":{"type":"string","enum":["not_found"]}}}}}]}}}},"429":{"description":"Too many requests (`rate_limited`). Retry after the `Retry-After` header.","content":{"application/json":{"schema":{"allOf":[{"$ref":"#/components/schemas/V1Error"},{"type":"object","properties":{"error":{"type":"object","properties":{"code":{"type":"string","enum":["rate_limited"]}}}}}]}}}},"503":{"description":"The dialer is briefly unavailable (`unavailable`). Safe to retry.","content":{"application/json":{"schema":{"allOf":[{"$ref":"#/components/schemas/V1Error"},{"type":"object","properties":{"error":{"type":"object","properties":{"code":{"type":"string","enum":["unavailable"]}}}}}]}}}}},"x-required-scope":"dialer:read","x-mcp-tool":"list_rep_follow_ups"},"parameters":[{"$ref":"#/components/parameters/FinnVersion"}]},"/dialer/leads/{id}/brief":{"get":{"tags":["Dialer"],"operationId":"getDialerLeadBrief","summary":"Get a lead's cached brief","description":"**Scope:** `dialer:read`.\n\nThe brief the dialer already made for this lead. Never starts new research: no brief yet answers 404 `brief_not_cached`. An org key reads the whole team. Objects are rep-dialer-service's, passed through as they are (camelCase).\n\n**Rate limit:** 300 requests per minute per API key across the Dialer API.","parameters":[{"name":"id","in":"path","required":true,"description":"The lead id.","schema":{"type":"string","pattern":"^[A-Za-z0-9_-]{1,80}$"}},{"name":"product_key","in":"query","required":false,"description":"The product the brief is for (default `none`).","schema":{"type":"string","maxLength":100}}],"responses":{"200":{"description":"OK.","content":{"application/json":{"schema":{"type":"object","additionalProperties":true}}}},"400":{"description":"The request is not valid (`invalid_request`, with `fields`), or `cursor` came from another list or other filters (`invalid_cursor`).","content":{"application/json":{"schema":{"allOf":[{"$ref":"#/components/schemas/V1Error"},{"type":"object","properties":{"error":{"type":"object","properties":{"code":{"type":"string","enum":["invalid_request","invalid_cursor"]}}}}}]}}}},"401":{"description":"Missing, unknown or revoked credentials.","content":{"application/json":{"schema":{"allOf":[{"$ref":"#/components/schemas/V1Error"},{"type":"object","properties":{"error":{"type":"object","properties":{"code":{"type":"string","enum":["api_key_required","invalid_api_key","unauthorized","key_expired"]}}}}}]}}}},"403":{"description":"The key lacks the scope (`insufficient_scope`), the credential cannot use this endpoint (`credential_not_supported`), or The dialer refused (`forbidden`, `admin_only`).","content":{"application/json":{"schema":{"allOf":[{"$ref":"#/components/schemas/V1Error"},{"type":"object","properties":{"error":{"type":"object","properties":{"code":{"type":"string","enum":["insufficient_scope","credential_not_supported","forbidden","admin_only"]}}}}}]}}}},"404":{"description":"Not found, or the The dialer API is not switched on for this organization (registry flag `v1_dialer`): `not_found`, `lead_not_found`, `brief_not_cached`.","content":{"application/json":{"schema":{"allOf":[{"$ref":"#/components/schemas/V1Error"},{"type":"object","properties":{"error":{"type":"object","properties":{"code":{"type":"string","enum":["not_found","lead_not_found","brief_not_cached"]}}}}}]}}}},"429":{"description":"Too many requests (`rate_limited`). Retry after the `Retry-After` header.","content":{"application/json":{"schema":{"allOf":[{"$ref":"#/components/schemas/V1Error"},{"type":"object","properties":{"error":{"type":"object","properties":{"code":{"type":"string","enum":["rate_limited"]}}}}}]}}}},"503":{"description":"The dialer is briefly unavailable (`unavailable`). Safe to retry.","content":{"application/json":{"schema":{"allOf":[{"$ref":"#/components/schemas/V1Error"},{"type":"object","properties":{"error":{"type":"object","properties":{"code":{"type":"string","enum":["unavailable"]}}}}}]}}}}},"x-required-scope":"dialer:read","x-mcp-tool":"get_lead_brief"},"parameters":[{"$ref":"#/components/parameters/FinnVersion"}]},"/crm/setup":{"get":{"tags":["CRM"],"summary":"Get the CRM setup","operationId":"getCrmSetup","description":"**Scope:** `crm:read`.\n\nStages, close reasons, fields, record kinds and workspace settings of the organization's CRM.\n\nCall with a member's OAuth token or MCP key (acts as that person, with their contact visibility). Org API keys get 403 `user_token_required`; they use `/contacts`. Beta: switched on per organization (`v1_crm_parity`); while it is off the endpoint answers as an unknown route. Errors use the `V1Error` body. A CRM check that refuses the request keeps its own `error.code` (for example `invalid_spec`, `contact_exists`) and any details it returns (missing stage checks, rejected filters) in `error.details`.\n\n**MCP tool:** `crm_get_schema`. **Rate limit:** 120 requests per minute per credential, shared with the same credential's other CRM reads.","responses":{"200":{"description":"The CRM setup.","content":{"application/json":{"schema":{"type":"object","additionalProperties":true}}}},"400":{"description":"The request is not valid (`invalid_request`, `org_conflict`).","content":{"application/json":{"schema":{"allOf":[{"$ref":"#/components/schemas/V1Error"},{"type":"object","properties":{"error":{"type":"object","properties":{"code":{"type":"string","examples":["invalid_request","org_conflict"]}}}}}]}}}},"401":{"$ref":"#/components/responses/Unauthorized"},"403":{"description":"The credential lacks the scope (`insufficient_scope`), is an org API key (`user_token_required`), or the member may not see this (`forbidden`).","content":{"application/json":{"schema":{"allOf":[{"$ref":"#/components/schemas/V1Error"},{"type":"object","properties":{"error":{"type":"object","properties":{"code":{"type":"string","examples":["insufficient_scope","user_token_required","forbidden"]}}}}}]}}}},"429":{"$ref":"#/components/responses/RateLimited"},"500":{"$ref":"#/components/responses/InternalError"},"503":{"description":"The organization settings could not be checked (`unavailable`). Safe to retry.","content":{"application/json":{"schema":{"allOf":[{"$ref":"#/components/schemas/V1Error"},{"type":"object","properties":{"error":{"type":"object","properties":{"code":{"type":"string","examples":["unavailable"]}}}}}]}}}}},"x-required-scope":"crm:read","x-mcp-tool":"crm_get_schema"},"parameters":[{"$ref":"#/components/parameters/FinnVersion"}]},"/crm/filter_schema":{"get":{"tags":["CRM"],"summary":"Get the filter schema","operationId":"getCrmFilterSchema","description":"**Scope:** `crm:read`.\n\nThe fields and operators a query `spec` may use.\n\nCall with a member's OAuth token or MCP key (acts as that person, with their contact visibility). Org API keys get 403 `user_token_required`; they use `/contacts`. Beta: switched on per organization (`v1_crm_parity`); while it is off the endpoint answers as an unknown route. Errors use the `V1Error` body. A CRM check that refuses the request keeps its own `error.code` (for example `invalid_spec`, `contact_exists`) and any details it returns (missing stage checks, rejected filters) in `error.details`.\n\n**MCP tool:** `crm_get_schema`. **Rate limit:** 120 requests per minute per credential, shared with the same credential's other CRM reads.","responses":{"200":{"description":"The filter schema.","content":{"application/json":{"schema":{"type":"object","additionalProperties":true}}}},"400":{"description":"The request is not valid (`invalid_request`, `org_conflict`).","content":{"application/json":{"schema":{"allOf":[{"$ref":"#/components/schemas/V1Error"},{"type":"object","properties":{"error":{"type":"object","properties":{"code":{"type":"string","examples":["invalid_request","org_conflict"]}}}}}]}}}},"401":{"$ref":"#/components/responses/Unauthorized"},"403":{"description":"The credential lacks the scope (`insufficient_scope`), is an org API key (`user_token_required`), or the member may not see this (`forbidden`).","content":{"application/json":{"schema":{"allOf":[{"$ref":"#/components/schemas/V1Error"},{"type":"object","properties":{"error":{"type":"object","properties":{"code":{"type":"string","examples":["insufficient_scope","user_token_required","forbidden"]}}}}}]}}}},"429":{"$ref":"#/components/responses/RateLimited"},"500":{"$ref":"#/components/responses/InternalError"},"503":{"description":"The organization settings could not be checked (`unavailable`). Safe to retry.","content":{"application/json":{"schema":{"allOf":[{"$ref":"#/components/schemas/V1Error"},{"type":"object","properties":{"error":{"type":"object","properties":{"code":{"type":"string","examples":["unavailable"]}}}}}]}}}}},"x-required-scope":"crm:read","x-mcp-tool":"crm_get_schema"},"parameters":[{"$ref":"#/components/parameters/FinnVersion"}]},"/crm/records/search":{"post":{"tags":["CRM"],"summary":"Search records","operationId":"searchCrmRecords","description":"**Scope:** `crm:read`.\n\nFree-text search over the records this member can see.\n\nCall with a member's OAuth token or MCP key (acts as that person, with their contact visibility). Org API keys get 403 `user_token_required`; they use `/contacts`. Beta: switched on per organization (`v1_crm_parity`); while it is off the endpoint answers as an unknown route. Errors use the `V1Error` body. A CRM check that refuses the request keeps its own `error.code` (for example `invalid_spec`, `contact_exists`) and any details it returns (missing stage checks, rejected filters) in `error.details`.\n\n**MCP tool:** `crm_search_records`. **Rate limit:** 120 requests per minute per credential, shared with the same credential's other CRM reads.","requestBody":{"required":true,"content":{"application/json":{"schema":{"type":"object","properties":{"q":{"type":"string"},"limit":{"type":"integer"}},"required":["q"]}}}},"responses":{"200":{"description":"Matching records.","content":{"application/json":{"schema":{"type":"object","additionalProperties":true}}}},"400":{"description":"The request is not valid (`invalid_request`, `org_conflict`).","content":{"application/json":{"schema":{"allOf":[{"$ref":"#/components/schemas/V1Error"},{"type":"object","properties":{"error":{"type":"object","properties":{"code":{"type":"string","examples":["invalid_request","org_conflict"]}}}}}]}}}},"401":{"$ref":"#/components/responses/Unauthorized"},"403":{"description":"The credential lacks the scope (`insufficient_scope`), is an org API key (`user_token_required`), or the member may not see this (`forbidden`).","content":{"application/json":{"schema":{"allOf":[{"$ref":"#/components/schemas/V1Error"},{"type":"object","properties":{"error":{"type":"object","properties":{"code":{"type":"string","examples":["insufficient_scope","user_token_required","forbidden"]}}}}}]}}}},"429":{"$ref":"#/components/responses/RateLimited"},"500":{"$ref":"#/components/responses/InternalError"},"503":{"description":"The organization settings could not be checked (`unavailable`). Safe to retry.","content":{"application/json":{"schema":{"allOf":[{"$ref":"#/components/schemas/V1Error"},{"type":"object","properties":{"error":{"type":"object","properties":{"code":{"type":"string","examples":["unavailable"]}}}}}]}}}}},"x-required-scope":"crm:read","x-mcp-tool":"crm_search_records"},"parameters":[{"$ref":"#/components/parameters/FinnVersion"}]},"/crm/records/query":{"post":{"tags":["CRM"],"summary":"Query records","operationId":"queryCrmRecords","description":"**Scope:** `crm:read`.\n\nRecords matching a filter `spec` (see `GET /crm/filter_schema`), sorted by `sort`. `limit` and `cursor` go in the query string.\n\nPass `next_cursor` back as `cursor` with the same filters; it is `null` on the last page. `total_count` is the number of matching rows.\n\nCall with a member's OAuth token or MCP key (acts as that person, with their contact visibility). Org API keys get 403 `user_token_required`; they use `/contacts`. Beta: switched on per organization (`v1_crm_parity`); while it is off the endpoint answers as an unknown route. Errors use the `V1Error` body. A CRM check that refuses the request keeps its own `error.code` (for example `invalid_spec`, `contact_exists`) and any details it returns (missing stage checks, rejected filters) in `error.details`.\n\n**MCP tool:** `crm_search_records`. **Rate limit:** 120 requests per minute per credential, shared with the same credential's other CRM reads.","parameters":[{"name":"limit","in":"query","description":"Page size, 1 to 100 (default 25).","schema":{"type":"integer","minimum":1,"maximum":100}},{"name":"cursor","in":"query","description":"`next_cursor` of the previous page. Send the same filters with it.","schema":{"type":"string"}}],"requestBody":{"required":false,"content":{"application/json":{"schema":{"type":"object","properties":{"spec":{"type":"object","additionalProperties":true},"sort":{"type":"object","additionalProperties":true},"dir":{"type":"string"}}}}}},"responses":{"200":{"description":"A page of records.","content":{"application/json":{"schema":{"type":"object","required":["data","next_cursor","has_more"],"properties":{"data":{"type":"array","items":{"type":"object","additionalProperties":true}},"next_cursor":{"type":["string","null"]},"has_more":{"type":"boolean"},"total_count":{"type":"integer"}}}}}},"400":{"description":"The request is not valid (`invalid_request`, `org_conflict`), or `cursor` came from another list or other filters (`invalid_cursor`).","content":{"application/json":{"schema":{"allOf":[{"$ref":"#/components/schemas/V1Error"},{"type":"object","properties":{"error":{"type":"object","properties":{"code":{"type":"string","examples":["invalid_request","invalid_cursor","org_conflict"]}}}}}]}}}},"401":{"$ref":"#/components/responses/Unauthorized"},"403":{"description":"The credential lacks the scope (`insufficient_scope`), is an org API key (`user_token_required`), or the member may not see this (`forbidden`).","content":{"application/json":{"schema":{"allOf":[{"$ref":"#/components/schemas/V1Error"},{"type":"object","properties":{"error":{"type":"object","properties":{"code":{"type":"string","examples":["insufficient_scope","user_token_required","forbidden"]}}}}}]}}}},"429":{"$ref":"#/components/responses/RateLimited"},"500":{"$ref":"#/components/responses/InternalError"},"503":{"description":"The organization settings could not be checked (`unavailable`). Safe to retry.","content":{"application/json":{"schema":{"allOf":[{"$ref":"#/components/schemas/V1Error"},{"type":"object","properties":{"error":{"type":"object","properties":{"code":{"type":"string","examples":["unavailable"]}}}}}]}}}}},"x-required-scope":"crm:read","x-mcp-tool":"crm_search_records"},"parameters":[{"$ref":"#/components/parameters/FinnVersion"}]},"/crm/records/query/count":{"post":{"tags":["CRM"],"summary":"Count records","operationId":"countCrmRecords","description":"**Scope:** `crm:read`.\n\nHow many records match a filter `spec`.\n\nCall with a member's OAuth token or MCP key (acts as that person, with their contact visibility). Org API keys get 403 `user_token_required`; they use `/contacts`. Beta: switched on per organization (`v1_crm_parity`); while it is off the endpoint answers as an unknown route. Errors use the `V1Error` body. A CRM check that refuses the request keeps its own `error.code` (for example `invalid_spec`, `contact_exists`) and any details it returns (missing stage checks, rejected filters) in `error.details`.\n\n**MCP tool:** `crm_count_records`. **Rate limit:** 120 requests per minute per credential, shared with the same credential's other CRM reads.","requestBody":{"required":false,"content":{"application/json":{"schema":{"type":"object","properties":{"spec":{"type":"object","additionalProperties":true}}}}}},"responses":{"200":{"description":"The count.","content":{"application/json":{"schema":{"type":"object","additionalProperties":true}}}},"400":{"description":"The request is not valid (`invalid_request`, `org_conflict`).","content":{"application/json":{"schema":{"allOf":[{"$ref":"#/components/schemas/V1Error"},{"type":"object","properties":{"error":{"type":"object","properties":{"code":{"type":"string","examples":["invalid_request","org_conflict"]}}}}}]}}}},"401":{"$ref":"#/components/responses/Unauthorized"},"403":{"description":"The credential lacks the scope (`insufficient_scope`), is an org API key (`user_token_required`), or the member may not see this (`forbidden`).","content":{"application/json":{"schema":{"allOf":[{"$ref":"#/components/schemas/V1Error"},{"type":"object","properties":{"error":{"type":"object","properties":{"code":{"type":"string","examples":["insufficient_scope","user_token_required","forbidden"]}}}}}]}}}},"429":{"$ref":"#/components/responses/RateLimited"},"500":{"$ref":"#/components/responses/InternalError"},"503":{"description":"The organization settings could not be checked (`unavailable`). Safe to retry.","content":{"application/json":{"schema":{"allOf":[{"$ref":"#/components/schemas/V1Error"},{"type":"object","properties":{"error":{"type":"object","properties":{"code":{"type":"string","examples":["unavailable"]}}}}}]}}}}},"x-required-scope":"crm:read","x-mcp-tool":"crm_count_records"},"parameters":[{"$ref":"#/components/parameters/FinnVersion"}]},"/crm/records/query/stage_counts":{"post":{"tags":["CRM"],"summary":"Count records by stage","operationId":"countCrmRecordsByStage","description":"**Scope:** `crm:read`.\n\nPer-stage counts of the records matching a filter `spec`.\n\nCall with a member's OAuth token or MCP key (acts as that person, with their contact visibility). Org API keys get 403 `user_token_required`; they use `/contacts`. Beta: switched on per organization (`v1_crm_parity`); while it is off the endpoint answers as an unknown route. Errors use the `V1Error` body. A CRM check that refuses the request keeps its own `error.code` (for example `invalid_spec`, `contact_exists`) and any details it returns (missing stage checks, rejected filters) in `error.details`.\n\n**MCP tool:** `crm_count_records`. **Rate limit:** 120 requests per minute per credential, shared with the same credential's other CRM reads.","requestBody":{"required":false,"content":{"application/json":{"schema":{"type":"object","properties":{"spec":{"type":"object","additionalProperties":true}}}}}},"responses":{"200":{"description":"Counts per stage.","content":{"application/json":{"schema":{"type":"object","additionalProperties":true}}}},"400":{"description":"The request is not valid (`invalid_request`, `org_conflict`).","content":{"application/json":{"schema":{"allOf":[{"$ref":"#/components/schemas/V1Error"},{"type":"object","properties":{"error":{"type":"object","properties":{"code":{"type":"string","examples":["invalid_request","org_conflict"]}}}}}]}}}},"401":{"$ref":"#/components/responses/Unauthorized"},"403":{"description":"The credential lacks the scope (`insufficient_scope`), is an org API key (`user_token_required`), or the member may not see this (`forbidden`).","content":{"application/json":{"schema":{"allOf":[{"$ref":"#/components/schemas/V1Error"},{"type":"object","properties":{"error":{"type":"object","properties":{"code":{"type":"string","examples":["insufficient_scope","user_token_required","forbidden"]}}}}}]}}}},"429":{"$ref":"#/components/responses/RateLimited"},"500":{"$ref":"#/components/responses/InternalError"},"503":{"description":"The organization settings could not be checked (`unavailable`). Safe to retry.","content":{"application/json":{"schema":{"allOf":[{"$ref":"#/components/schemas/V1Error"},{"type":"object","properties":{"error":{"type":"object","properties":{"code":{"type":"string","examples":["unavailable"]}}}}}]}}}}},"x-required-scope":"crm:read","x-mcp-tool":"crm_count_records"},"parameters":[{"$ref":"#/components/parameters/FinnVersion"}]},"/crm/records/{id}":{"patch":{"tags":["CRM"],"summary":"Update a record's fields","operationId":"updateCrmRecord","description":"**Scope:** `crm:write`.\n\nName, custom fields, tags and links. `clearFields` empties fields; `expectCustomFields` refuses the change if those values moved meanwhile. The stage moves with `POST /crm/records/{id}/stage_moves`.\n\nCall with a member's OAuth token or MCP key (acts as that person, with their contact visibility). Org API keys get 403 `user_token_required`; they use `/contacts`. Beta: switched on per organization (`v1_crm_parity`); while it is off the endpoint answers as an unknown route. Errors use the `V1Error` body. A CRM check that refuses the request keeps its own `error.code` (for example `invalid_spec`, `contact_exists`) and any details it returns (missing stage checks, rejected filters) in `error.details`.\n\n**MCP tool:** `crm_update_fields`. **Rate limit:** 30 writes per minute per credential, shared with the same credential's other CRM writes.","parameters":[{"name":"id","in":"path","required":true,"description":"The record id, or its phone number in E.164.","schema":{"type":"string"}},{"$ref":"#/components/parameters/IdempotencyKey"}],"requestBody":{"required":true,"content":{"application/json":{"schema":{"type":"object","additionalProperties":false,"properties":{"name":{"type":"string"},"customFields":{"type":"object","additionalProperties":true},"clearFields":{"type":"array","items":{"type":"string"}},"tags":{"type":"array","items":{"type":"string"}},"links":{"type":"array","items":{"type":"object","additionalProperties":true}},"expectCustomFields":{"type":"object","additionalProperties":true}}}}}},"responses":{"200":{"description":"The record.","content":{"application/json":{"schema":{"type":"object","additionalProperties":true}}}},"400":{"description":"The request is not valid (`invalid_request`, `org_conflict`).","content":{"application/json":{"schema":{"allOf":[{"$ref":"#/components/schemas/V1Error"},{"type":"object","properties":{"error":{"type":"object","properties":{"code":{"type":"string","examples":["invalid_request","org_conflict"]}}}}}]}}}},"401":{"$ref":"#/components/responses/Unauthorized"},"403":{"description":"The credential lacks the scope (`insufficient_scope`), is an org API key (`user_token_required`), or the member may not see this (`forbidden`).","content":{"application/json":{"schema":{"allOf":[{"$ref":"#/components/schemas/V1Error"},{"type":"object","properties":{"error":{"type":"object","properties":{"code":{"type":"string","examples":["insufficient_scope","user_token_required","forbidden"]}}}}}]}}}},"404":{"description":"No such record visible to this member (`not_found`).","content":{"application/json":{"schema":{"allOf":[{"$ref":"#/components/schemas/V1Error"},{"type":"object","properties":{"error":{"type":"object","properties":{"code":{"type":"string","examples":["not_found"]}}}}}]}}}},"409":{"description":"A value changed meanwhile (`conflict`).","content":{"application/json":{"schema":{"allOf":[{"$ref":"#/components/schemas/V1Error"},{"type":"object","properties":{"error":{"type":"object","properties":{"code":{"type":"string","examples":["conflict"]}}}}}]}}}},"429":{"$ref":"#/components/responses/RateLimited"},"500":{"$ref":"#/components/responses/InternalError"},"503":{"description":"The organization settings could not be checked (`unavailable`). Safe to retry.","content":{"application/json":{"schema":{"allOf":[{"$ref":"#/components/schemas/V1Error"},{"type":"object","properties":{"error":{"type":"object","properties":{"code":{"type":"string","examples":["unavailable"]}}}}}]}}}}},"x-required-scope":"crm:write","x-mcp-tool":"crm_update_fields"},"get":{"tags":["CRM"],"summary":"Get a record","operationId":"getCrmRecord","description":"**Scope:** `crm:read`.\n\nOne record with its fields, stage and links. Sensitive field values are hidden unless the credential also has `crm:sensitive:read`.\n\nCall with a member's OAuth token or MCP key (acts as that person, with their contact visibility). Org API keys get 403 `user_token_required`; they use `/contacts`. Beta: switched on per organization (`v1_crm_parity`); while it is off the endpoint answers as an unknown route. Errors use the `V1Error` body. A CRM check that refuses the request keeps its own `error.code` (for example `invalid_spec`, `contact_exists`) and any details it returns (missing stage checks, rejected filters) in `error.details`.\n\n**MCP tool:** `crm_get_record`. **Rate limit:** 120 requests per minute and 500 single-record reads a day per credential.","parameters":[{"name":"id","in":"path","required":true,"description":"The record id, or its phone number in E.164.","schema":{"type":"string"}}],"responses":{"200":{"description":"The record.","content":{"application/json":{"schema":{"type":"object","additionalProperties":true}}}},"400":{"description":"The request is not valid (`invalid_request`, `org_conflict`).","content":{"application/json":{"schema":{"allOf":[{"$ref":"#/components/schemas/V1Error"},{"type":"object","properties":{"error":{"type":"object","properties":{"code":{"type":"string","examples":["invalid_request","org_conflict"]}}}}}]}}}},"401":{"$ref":"#/components/responses/Unauthorized"},"403":{"description":"The credential lacks the scope (`insufficient_scope`), is an org API key (`user_token_required`), or the member may not see this (`forbidden`).","content":{"application/json":{"schema":{"allOf":[{"$ref":"#/components/schemas/V1Error"},{"type":"object","properties":{"error":{"type":"object","properties":{"code":{"type":"string","examples":["insufficient_scope","user_token_required","forbidden"]}}}}}]}}}},"404":{"description":"No such record visible to this member (`not_found`).","content":{"application/json":{"schema":{"allOf":[{"$ref":"#/components/schemas/V1Error"},{"type":"object","properties":{"error":{"type":"object","properties":{"code":{"type":"string","examples":["not_found"]}}}}}]}}}},"429":{"$ref":"#/components/responses/RateLimited"},"500":{"$ref":"#/components/responses/InternalError"},"503":{"description":"The organization settings could not be checked (`unavailable`). Safe to retry.","content":{"application/json":{"schema":{"allOf":[{"$ref":"#/components/schemas/V1Error"},{"type":"object","properties":{"error":{"type":"object","properties":{"code":{"type":"string","examples":["unavailable"]}}}}}]}}}}},"x-required-scope":"crm:read","x-mcp-tool":"crm_get_record"},"parameters":[{"$ref":"#/components/parameters/FinnVersion"}]},"/crm/records/{id}/timeline":{"get":{"tags":["CRM"],"summary":"Get a record's timeline","operationId":"getCrmRecordTimeline","description":"**Scope:** `crm:read`.\n\nNotes, calls, messages, stage moves and other activity on the record, newest first.\n\nCall with a member's OAuth token or MCP key (acts as that person, with their contact visibility). Org API keys get 403 `user_token_required`; they use `/contacts`. Beta: switched on per organization (`v1_crm_parity`); while it is off the endpoint answers as an unknown route. Errors use the `V1Error` body. A CRM check that refuses the request keeps its own `error.code` (for example `invalid_spec`, `contact_exists`) and any details it returns (missing stage checks, rejected filters) in `error.details`.\n\n**MCP tool:** `crm_get_timeline`. **Rate limit:** 120 requests per minute per credential, shared with the same credential's other CRM reads.","parameters":[{"name":"id","in":"path","required":true,"description":"The record id, or its phone number in E.164.","schema":{"type":"string"}},{"name":"before","in":"query","description":"Only activity before this ISO 8601 time.","schema":{"type":"string"}},{"name":"limit","in":"query","description":"How many items.","schema":{"type":"string"}},{"name":"kind","in":"query","description":"Only this kind of activity.","schema":{"type":"string"}},{"name":"channel","in":"query","description":"Only this channel.","schema":{"type":"string"}},{"name":"include","in":"query","description":"`audit` adds the record history.","schema":{"type":"string"}}],"responses":{"200":{"description":"The timeline.","content":{"application/json":{"schema":{"type":"object","additionalProperties":true}}}},"400":{"description":"The request is not valid (`invalid_request`, `org_conflict`).","content":{"application/json":{"schema":{"allOf":[{"$ref":"#/components/schemas/V1Error"},{"type":"object","properties":{"error":{"type":"object","properties":{"code":{"type":"string","examples":["invalid_request","org_conflict"]}}}}}]}}}},"401":{"$ref":"#/components/responses/Unauthorized"},"403":{"description":"The credential lacks the scope (`insufficient_scope`), is an org API key (`user_token_required`), or the member may not see this (`forbidden`).","content":{"application/json":{"schema":{"allOf":[{"$ref":"#/components/schemas/V1Error"},{"type":"object","properties":{"error":{"type":"object","properties":{"code":{"type":"string","examples":["insufficient_scope","user_token_required","forbidden"]}}}}}]}}}},"404":{"description":"No such record visible to this member (`not_found`).","content":{"application/json":{"schema":{"allOf":[{"$ref":"#/components/schemas/V1Error"},{"type":"object","properties":{"error":{"type":"object","properties":{"code":{"type":"string","examples":["not_found"]}}}}}]}}}},"429":{"$ref":"#/components/responses/RateLimited"},"500":{"$ref":"#/components/responses/InternalError"},"503":{"description":"The organization settings could not be checked (`unavailable`). Safe to retry.","content":{"application/json":{"schema":{"allOf":[{"$ref":"#/components/schemas/V1Error"},{"type":"object","properties":{"error":{"type":"object","properties":{"code":{"type":"string","examples":["unavailable"]}}}}}]}}}}},"x-required-scope":"crm:read","x-mcp-tool":"crm_get_timeline"},"parameters":[{"$ref":"#/components/parameters/FinnVersion"}]},"/crm/records/{id}/stage_gate":{"get":{"tags":["CRM"],"summary":"Get a stage gate","operationId":"getCrmStageGate","description":"**Scope:** `crm:read`.\n\nThe checks a record needs before it can move to stage `to`, and which are done.\n\nCall with a member's OAuth token or MCP key (acts as that person, with their contact visibility). Org API keys get 403 `user_token_required`; they use `/contacts`. Beta: switched on per organization (`v1_crm_parity`); while it is off the endpoint answers as an unknown route. Errors use the `V1Error` body. A CRM check that refuses the request keeps its own `error.code` (for example `invalid_spec`, `contact_exists`) and any details it returns (missing stage checks, rejected filters) in `error.details`.\n\n**MCP tool:** `crm_get_stage_gate`. **Rate limit:** 120 requests per minute per credential, shared with the same credential's other CRM reads.","parameters":[{"name":"id","in":"path","required":true,"description":"The record id, or its phone number in E.164.","schema":{"type":"string"}},{"name":"to","in":"query","description":"The stage to check.","schema":{"type":"string"}}],"responses":{"200":{"description":"The gate and its checks.","content":{"application/json":{"schema":{"type":"object","additionalProperties":true}}}},"400":{"description":"The request is not valid (`invalid_request`, `org_conflict`).","content":{"application/json":{"schema":{"allOf":[{"$ref":"#/components/schemas/V1Error"},{"type":"object","properties":{"error":{"type":"object","properties":{"code":{"type":"string","examples":["invalid_request","org_conflict"]}}}}}]}}}},"401":{"$ref":"#/components/responses/Unauthorized"},"403":{"description":"The credential lacks the scope (`insufficient_scope`), is an org API key (`user_token_required`), or the member may not see this (`forbidden`).","content":{"application/json":{"schema":{"allOf":[{"$ref":"#/components/schemas/V1Error"},{"type":"object","properties":{"error":{"type":"object","properties":{"code":{"type":"string","examples":["insufficient_scope","user_token_required","forbidden"]}}}}}]}}}},"404":{"description":"No such record visible to this member (`not_found`).","content":{"application/json":{"schema":{"allOf":[{"$ref":"#/components/schemas/V1Error"},{"type":"object","properties":{"error":{"type":"object","properties":{"code":{"type":"string","examples":["not_found"]}}}}}]}}}},"429":{"$ref":"#/components/responses/RateLimited"},"500":{"$ref":"#/components/responses/InternalError"},"503":{"description":"The organization settings could not be checked (`unavailable`). Safe to retry.","content":{"application/json":{"schema":{"allOf":[{"$ref":"#/components/schemas/V1Error"},{"type":"object","properties":{"error":{"type":"object","properties":{"code":{"type":"string","examples":["unavailable"]}}}}}]}}}}},"x-required-scope":"crm:read","x-mcp-tool":"crm_get_stage_gate"},"parameters":[{"$ref":"#/components/parameters/FinnVersion"}]},"/crm/records/{id}/documents":{"get":{"tags":["CRM"],"summary":"List a record's documents","operationId":"listCrmRecordDocuments","description":"**Scope:** `crm:read`.\n\nThe document checklist of the record and each document's status.\n\nCall with a member's OAuth token or MCP key (acts as that person, with their contact visibility). Org API keys get 403 `user_token_required`; they use `/contacts`. Beta: switched on per organization (`v1_crm_parity`); while it is off the endpoint answers as an unknown route. Errors use the `V1Error` body. A CRM check that refuses the request keeps its own `error.code` (for example `invalid_spec`, `contact_exists`) and any details it returns (missing stage checks, rejected filters) in `error.details`.\n\n**MCP tool:** `crm_list_documents`. **Rate limit:** 120 requests per minute per credential, shared with the same credential's other CRM reads.","parameters":[{"name":"id","in":"path","required":true,"description":"The record id, or its phone number in E.164.","schema":{"type":"string"}}],"responses":{"200":{"description":"The documents.","content":{"application/json":{"schema":{"type":"object","additionalProperties":true}}}},"400":{"description":"The request is not valid (`invalid_request`, `org_conflict`).","content":{"application/json":{"schema":{"allOf":[{"$ref":"#/components/schemas/V1Error"},{"type":"object","properties":{"error":{"type":"object","properties":{"code":{"type":"string","examples":["invalid_request","org_conflict"]}}}}}]}}}},"401":{"$ref":"#/components/responses/Unauthorized"},"403":{"description":"The credential lacks the scope (`insufficient_scope`), is an org API key (`user_token_required`), or the member may not see this (`forbidden`).","content":{"application/json":{"schema":{"allOf":[{"$ref":"#/components/schemas/V1Error"},{"type":"object","properties":{"error":{"type":"object","properties":{"code":{"type":"string","examples":["insufficient_scope","user_token_required","forbidden"]}}}}}]}}}},"404":{"description":"No such record visible to this member (`not_found`).","content":{"application/json":{"schema":{"allOf":[{"$ref":"#/components/schemas/V1Error"},{"type":"object","properties":{"error":{"type":"object","properties":{"code":{"type":"string","examples":["not_found"]}}}}}]}}}},"429":{"$ref":"#/components/responses/RateLimited"},"500":{"$ref":"#/components/responses/InternalError"},"503":{"description":"The organization settings could not be checked (`unavailable`). Safe to retry.","content":{"application/json":{"schema":{"allOf":[{"$ref":"#/components/schemas/V1Error"},{"type":"object","properties":{"error":{"type":"object","properties":{"code":{"type":"string","examples":["unavailable"]}}}}}]}}}}},"x-required-scope":"crm:read","x-mcp-tool":"crm_list_documents"},"parameters":[{"$ref":"#/components/parameters/FinnVersion"}]},"/crm/records/{id}/files":{"post":{"tags":["CRM"],"summary":"Attach a file","operationId":"attachCrmFile","description":"**Scope:** `crm:docs:write`.\n\nAttaches files (multipart field `file`) to the record, optionally as document type `doc_type`. Not held.\n\nCall with a member's OAuth token or MCP key (acts as that person, with their contact visibility). Org API keys get 403 `user_token_required`; they use `/contacts`. Beta: switched on per organization (`v1_crm_parity`); while it is off the endpoint answers as an unknown route. Errors use the `V1Error` body. A CRM check that refuses the request keeps its own `error.code` (for example `invalid_spec`, `contact_exists`) and any details it returns (missing stage checks, rejected filters) in `error.details`.\n\n**MCP tool:** `crm_attach_document`. **Rate limit:** 30 writes per minute per credential, shared with the same credential's other CRM writes.","parameters":[{"name":"id","in":"path","required":true,"description":"The record id, or its phone number in E.164.","schema":{"type":"string"}},{"$ref":"#/components/parameters/IdempotencyKey"}],"requestBody":{"required":true,"content":{"multipart/form-data":{"schema":{"type":"object","properties":{"file":{"type":"array","items":{"type":"string","format":"binary"}},"doc_type":{"type":"string"}},"required":["file"]}}}},"responses":{"201":{"description":"The files.","content":{"application/json":{"schema":{"type":"object","additionalProperties":true}}}},"400":{"description":"The request is not valid (`invalid_request`, `org_conflict`).","content":{"application/json":{"schema":{"allOf":[{"$ref":"#/components/schemas/V1Error"},{"type":"object","properties":{"error":{"type":"object","properties":{"code":{"type":"string","examples":["invalid_request","org_conflict"]}}}}}]}}}},"401":{"$ref":"#/components/responses/Unauthorized"},"403":{"description":"The credential lacks the scope (`insufficient_scope`), is an org API key (`user_token_required`), or the member may not see this (`forbidden`).","content":{"application/json":{"schema":{"allOf":[{"$ref":"#/components/schemas/V1Error"},{"type":"object","properties":{"error":{"type":"object","properties":{"code":{"type":"string","examples":["insufficient_scope","user_token_required","forbidden"]}}}}}]}}}},"404":{"description":"No such record visible to this member (`not_found`).","content":{"application/json":{"schema":{"allOf":[{"$ref":"#/components/schemas/V1Error"},{"type":"object","properties":{"error":{"type":"object","properties":{"code":{"type":"string","examples":["not_found"]}}}}}]}}}},"429":{"$ref":"#/components/responses/RateLimited"},"500":{"$ref":"#/components/responses/InternalError"},"503":{"description":"The organization settings could not be checked (`unavailable`). Safe to retry.","content":{"application/json":{"schema":{"allOf":[{"$ref":"#/components/schemas/V1Error"},{"type":"object","properties":{"error":{"type":"object","properties":{"code":{"type":"string","examples":["unavailable"]}}}}}]}}}}},"x-required-scope":"crm:docs:write","x-mcp-tool":"crm_attach_document"},"get":{"tags":["CRM"],"summary":"List a record's files","operationId":"listCrmRecordFiles","description":"**Scope:** `crm:read`.\n\nFiles attached to the record (names, types, dates; no download links).\n\nCall with a member's OAuth token or MCP key (acts as that person, with their contact visibility). Org API keys get 403 `user_token_required`; they use `/contacts`. Beta: switched on per organization (`v1_crm_parity`); while it is off the endpoint answers as an unknown route. Errors use the `V1Error` body. A CRM check that refuses the request keeps its own `error.code` (for example `invalid_spec`, `contact_exists`) and any details it returns (missing stage checks, rejected filters) in `error.details`.\n\n**MCP tool:** `crm_list_documents`. **Rate limit:** 120 requests per minute per credential, shared with the same credential's other CRM reads.","parameters":[{"name":"id","in":"path","required":true,"description":"The record id, or its phone number in E.164.","schema":{"type":"string"}}],"responses":{"200":{"description":"The files.","content":{"application/json":{"schema":{"type":"object","additionalProperties":true}}}},"400":{"description":"The request is not valid (`invalid_request`, `org_conflict`).","content":{"application/json":{"schema":{"allOf":[{"$ref":"#/components/schemas/V1Error"},{"type":"object","properties":{"error":{"type":"object","properties":{"code":{"type":"string","examples":["invalid_request","org_conflict"]}}}}}]}}}},"401":{"$ref":"#/components/responses/Unauthorized"},"403":{"description":"The credential lacks the scope (`insufficient_scope`), is an org API key (`user_token_required`), or the member may not see this (`forbidden`).","content":{"application/json":{"schema":{"allOf":[{"$ref":"#/components/schemas/V1Error"},{"type":"object","properties":{"error":{"type":"object","properties":{"code":{"type":"string","examples":["insufficient_scope","user_token_required","forbidden"]}}}}}]}}}},"404":{"description":"No such record visible to this member (`not_found`).","content":{"application/json":{"schema":{"allOf":[{"$ref":"#/components/schemas/V1Error"},{"type":"object","properties":{"error":{"type":"object","properties":{"code":{"type":"string","examples":["not_found"]}}}}}]}}}},"429":{"$ref":"#/components/responses/RateLimited"},"500":{"$ref":"#/components/responses/InternalError"},"503":{"description":"The organization settings could not be checked (`unavailable`). Safe to retry.","content":{"application/json":{"schema":{"allOf":[{"$ref":"#/components/schemas/V1Error"},{"type":"object","properties":{"error":{"type":"object","properties":{"code":{"type":"string","examples":["unavailable"]}}}}}]}}}}},"x-required-scope":"crm:read","x-mcp-tool":"crm_list_documents"},"parameters":[{"$ref":"#/components/parameters/FinnVersion"}]},"/crm/records/{id}/forms_checklist":{"get":{"tags":["CRM"],"summary":"Get a record's forms checklist","operationId":"getCrmFormsChecklist","description":"**Scope:** `crm:read`.\n\nThe forms this record needs and which are done.\n\nCall with a member's OAuth token or MCP key (acts as that person, with their contact visibility). Org API keys get 403 `user_token_required`; they use `/contacts`. Beta: switched on per organization (`v1_crm_parity`); while it is off the endpoint answers as an unknown route. Errors use the `V1Error` body. A CRM check that refuses the request keeps its own `error.code` (for example `invalid_spec`, `contact_exists`) and any details it returns (missing stage checks, rejected filters) in `error.details`.\n\n**MCP tool:** `crm_list_documents`. **Rate limit:** 120 requests per minute per credential, shared with the same credential's other CRM reads.","parameters":[{"name":"id","in":"path","required":true,"description":"The record id, or its phone number in E.164.","schema":{"type":"string"}}],"responses":{"200":{"description":"The checklist.","content":{"application/json":{"schema":{"type":"object","additionalProperties":true}}}},"400":{"description":"The request is not valid (`invalid_request`, `org_conflict`).","content":{"application/json":{"schema":{"allOf":[{"$ref":"#/components/schemas/V1Error"},{"type":"object","properties":{"error":{"type":"object","properties":{"code":{"type":"string","examples":["invalid_request","org_conflict"]}}}}}]}}}},"401":{"$ref":"#/components/responses/Unauthorized"},"403":{"description":"The credential lacks the scope (`insufficient_scope`), is an org API key (`user_token_required`), or the member may not see this (`forbidden`).","content":{"application/json":{"schema":{"allOf":[{"$ref":"#/components/schemas/V1Error"},{"type":"object","properties":{"error":{"type":"object","properties":{"code":{"type":"string","examples":["insufficient_scope","user_token_required","forbidden"]}}}}}]}}}},"404":{"description":"No such record visible to this member (`not_found`).","content":{"application/json":{"schema":{"allOf":[{"$ref":"#/components/schemas/V1Error"},{"type":"object","properties":{"error":{"type":"object","properties":{"code":{"type":"string","examples":["not_found"]}}}}}]}}}},"429":{"$ref":"#/components/responses/RateLimited"},"500":{"$ref":"#/components/responses/InternalError"},"503":{"description":"The organization settings could not be checked (`unavailable`). Safe to retry.","content":{"application/json":{"schema":{"allOf":[{"$ref":"#/components/schemas/V1Error"},{"type":"object","properties":{"error":{"type":"object","properties":{"code":{"type":"string","examples":["unavailable"]}}}}}]}}}}},"x-required-scope":"crm:read","x-mcp-tool":"crm_list_documents"},"parameters":[{"$ref":"#/components/parameters/FinnVersion"}]},"/crm/records/{id}/submissions":{"get":{"tags":["CRM"],"summary":"List a record's form submissions","operationId":"listCrmFormSubmissions","description":"**Scope:** `crm:read` and `crm:sensitive:read`.\n\nForm submissions on the record, with their answers.\n\nCall with a member's OAuth token or MCP key (acts as that person, with their contact visibility). Org API keys get 403 `user_token_required`; they use `/contacts`. Beta: switched on per organization (`v1_crm_parity`); while it is off the endpoint answers as an unknown route. Errors use the `V1Error` body. A CRM check that refuses the request keeps its own `error.code` (for example `invalid_spec`, `contact_exists`) and any details it returns (missing stage checks, rejected filters) in `error.details`.\n\n**MCP tool:** `crm_get_form_submissions`. **Rate limit:** 120 requests per minute and 500 single-record reads a day per credential.","parameters":[{"name":"id","in":"path","required":true,"description":"The record id, or its phone number in E.164.","schema":{"type":"string"}}],"responses":{"200":{"description":"The submissions.","content":{"application/json":{"schema":{"type":"object","additionalProperties":true}}}},"400":{"description":"The request is not valid (`invalid_request`, `org_conflict`).","content":{"application/json":{"schema":{"allOf":[{"$ref":"#/components/schemas/V1Error"},{"type":"object","properties":{"error":{"type":"object","properties":{"code":{"type":"string","examples":["invalid_request","org_conflict"]}}}}}]}}}},"401":{"$ref":"#/components/responses/Unauthorized"},"403":{"description":"The credential lacks the scope (`insufficient_scope`), is an org API key (`user_token_required`), or the member may not see this (`forbidden`).","content":{"application/json":{"schema":{"allOf":[{"$ref":"#/components/schemas/V1Error"},{"type":"object","properties":{"error":{"type":"object","properties":{"code":{"type":"string","examples":["insufficient_scope","user_token_required","forbidden"]}}}}}]}}}},"404":{"description":"No such record visible to this member (`not_found`).","content":{"application/json":{"schema":{"allOf":[{"$ref":"#/components/schemas/V1Error"},{"type":"object","properties":{"error":{"type":"object","properties":{"code":{"type":"string","examples":["not_found"]}}}}}]}}}},"429":{"$ref":"#/components/responses/RateLimited"},"500":{"$ref":"#/components/responses/InternalError"},"503":{"description":"The organization settings could not be checked (`unavailable`). Safe to retry.","content":{"application/json":{"schema":{"allOf":[{"$ref":"#/components/schemas/V1Error"},{"type":"object","properties":{"error":{"type":"object","properties":{"code":{"type":"string","examples":["unavailable"]}}}}}]}}}}},"x-required-scope":"crm:read","x-mcp-tool":"crm_get_form_submissions"},"parameters":[{"$ref":"#/components/parameters/FinnVersion"}]},"/crm/records/{id}/form_drafts":{"get":{"tags":["CRM"],"summary":"List a record's form drafts","operationId":"listCrmFormDrafts","description":"**Scope:** `crm:read` and `crm:sensitive:read`.\n\nUnsubmitted form drafts on the record, with their answers.\n\nCall with a member's OAuth token or MCP key (acts as that person, with their contact visibility). Org API keys get 403 `user_token_required`; they use `/contacts`. Beta: switched on per organization (`v1_crm_parity`); while it is off the endpoint answers as an unknown route. Errors use the `V1Error` body. A CRM check that refuses the request keeps its own `error.code` (for example `invalid_spec`, `contact_exists`) and any details it returns (missing stage checks, rejected filters) in `error.details`.\n\n**MCP tool:** `crm_get_form_submissions`. **Rate limit:** 120 requests per minute and 500 single-record reads a day per credential.","parameters":[{"name":"id","in":"path","required":true,"description":"The record id, or its phone number in E.164.","schema":{"type":"string"}}],"responses":{"200":{"description":"The drafts.","content":{"application/json":{"schema":{"type":"object","additionalProperties":true}}}},"400":{"description":"The request is not valid (`invalid_request`, `org_conflict`).","content":{"application/json":{"schema":{"allOf":[{"$ref":"#/components/schemas/V1Error"},{"type":"object","properties":{"error":{"type":"object","properties":{"code":{"type":"string","examples":["invalid_request","org_conflict"]}}}}}]}}}},"401":{"$ref":"#/components/responses/Unauthorized"},"403":{"description":"The credential lacks the scope (`insufficient_scope`), is an org API key (`user_token_required`), or the member may not see this (`forbidden`).","content":{"application/json":{"schema":{"allOf":[{"$ref":"#/components/schemas/V1Error"},{"type":"object","properties":{"error":{"type":"object","properties":{"code":{"type":"string","examples":["insufficient_scope","user_token_required","forbidden"]}}}}}]}}}},"404":{"description":"No such record visible to this member (`not_found`).","content":{"application/json":{"schema":{"allOf":[{"$ref":"#/components/schemas/V1Error"},{"type":"object","properties":{"error":{"type":"object","properties":{"code":{"type":"string","examples":["not_found"]}}}}}]}}}},"429":{"$ref":"#/components/responses/RateLimited"},"500":{"$ref":"#/components/responses/InternalError"},"503":{"description":"The organization settings could not be checked (`unavailable`). Safe to retry.","content":{"application/json":{"schema":{"allOf":[{"$ref":"#/components/schemas/V1Error"},{"type":"object","properties":{"error":{"type":"object","properties":{"code":{"type":"string","examples":["unavailable"]}}}}}]}}}}},"x-required-scope":"crm:read","x-mcp-tool":"crm_get_form_submissions"},"parameters":[{"$ref":"#/components/parameters/FinnVersion"}]},"/crm/forms":{"get":{"tags":["CRM"],"summary":"List forms","operationId":"listCrmForms","description":"**Scope:** `crm:read`.\n\nThe organization's forms.\n\nCall with a member's OAuth token or MCP key (acts as that person, with their contact visibility). Org API keys get 403 `user_token_required`; they use `/contacts`. Beta: switched on per organization (`v1_crm_parity`); while it is off the endpoint answers as an unknown route. Errors use the `V1Error` body. A CRM check that refuses the request keeps its own `error.code` (for example `invalid_spec`, `contact_exists`) and any details it returns (missing stage checks, rejected filters) in `error.details`.\n\n**MCP tool:** `crm_list_forms`. **Rate limit:** 120 requests per minute per credential, shared with the same credential's other CRM reads.","responses":{"200":{"description":"The forms.","content":{"application/json":{"schema":{"type":"object","additionalProperties":true}}}},"400":{"description":"The request is not valid (`invalid_request`, `org_conflict`).","content":{"application/json":{"schema":{"allOf":[{"$ref":"#/components/schemas/V1Error"},{"type":"object","properties":{"error":{"type":"object","properties":{"code":{"type":"string","examples":["invalid_request","org_conflict"]}}}}}]}}}},"401":{"$ref":"#/components/responses/Unauthorized"},"403":{"description":"The credential lacks the scope (`insufficient_scope`), is an org API key (`user_token_required`), or the member may not see this (`forbidden`).","content":{"application/json":{"schema":{"allOf":[{"$ref":"#/components/schemas/V1Error"},{"type":"object","properties":{"error":{"type":"object","properties":{"code":{"type":"string","examples":["insufficient_scope","user_token_required","forbidden"]}}}}}]}}}},"429":{"$ref":"#/components/responses/RateLimited"},"500":{"$ref":"#/components/responses/InternalError"},"503":{"description":"The organization settings could not be checked (`unavailable`). Safe to retry.","content":{"application/json":{"schema":{"allOf":[{"$ref":"#/components/schemas/V1Error"},{"type":"object","properties":{"error":{"type":"object","properties":{"code":{"type":"string","examples":["unavailable"]}}}}}]}}}}},"x-required-scope":"crm:read","x-mcp-tool":"crm_list_forms"},"parameters":[{"$ref":"#/components/parameters/FinnVersion"}]},"/crm/follow_ups":{"get":{"tags":["CRM"],"summary":"List follow-ups","operationId":"listCrmFollowUps","description":"**Scope:** `crm:read`.\n\nFollow-ups, soonest due first.\n\nPass `next_cursor` back as `cursor` with the same filters; it is `null` on the last page. `total_count` is the number of matching rows.\n\nCall with a member's OAuth token or MCP key (acts as that person, with their contact visibility). Org API keys get 403 `user_token_required`; they use `/contacts`. Beta: switched on per organization (`v1_crm_parity`); while it is off the endpoint answers as an unknown route. Errors use the `V1Error` body. A CRM check that refuses the request keeps its own `error.code` (for example `invalid_spec`, `contact_exists`) and any details it returns (missing stage checks, rejected filters) in `error.details`.\n\n**MCP tool:** `crm_list_follow_ups`. **Rate limit:** 120 requests per minute per credential, shared with the same credential's other CRM reads.","parameters":[{"name":"view","in":"query","description":"`mine` (default), `team` or `unassigned`.","schema":{"type":"string"}},{"name":"status","in":"query","description":"`open` (default), `overdue`, `done` or `all`.","schema":{"type":"string"}},{"name":"due","in":"query","description":"`today` or `week`.","schema":{"type":"string"}},{"name":"assignee","in":"query","description":"Only this user id.","schema":{"type":"string"}},{"name":"contact","in":"query","description":"Only this record.","schema":{"type":"string"}},{"name":"tz","in":"query","description":"Time zone for `due`, e.g. `Asia/Kolkata` (default UTC).","schema":{"type":"string"}},{"name":"limit","in":"query","description":"Page size, 1 to 100 (default 25).","schema":{"type":"integer","minimum":1,"maximum":100}},{"name":"cursor","in":"query","description":"`next_cursor` of the previous page. Send the same filters with it.","schema":{"type":"string"}}],"responses":{"200":{"description":"A page of follow-ups.","content":{"application/json":{"schema":{"type":"object","required":["data","next_cursor","has_more"],"properties":{"data":{"type":"array","items":{"type":"object","additionalProperties":true}},"next_cursor":{"type":["string","null"]},"has_more":{"type":"boolean"},"total_count":{"type":"integer"}}}}}},"400":{"description":"The request is not valid (`invalid_request`, `org_conflict`), or `cursor` came from another list or other filters (`invalid_cursor`).","content":{"application/json":{"schema":{"allOf":[{"$ref":"#/components/schemas/V1Error"},{"type":"object","properties":{"error":{"type":"object","properties":{"code":{"type":"string","examples":["invalid_request","invalid_cursor","org_conflict"]}}}}}]}}}},"401":{"$ref":"#/components/responses/Unauthorized"},"403":{"description":"The credential lacks the scope (`insufficient_scope`), is an org API key (`user_token_required`), or the member may not see this (`forbidden`).","content":{"application/json":{"schema":{"allOf":[{"$ref":"#/components/schemas/V1Error"},{"type":"object","properties":{"error":{"type":"object","properties":{"code":{"type":"string","examples":["insufficient_scope","user_token_required","forbidden"]}}}}}]}}}},"429":{"$ref":"#/components/responses/RateLimited"},"500":{"$ref":"#/components/responses/InternalError"},"503":{"description":"The organization settings could not be checked (`unavailable`). Safe to retry.","content":{"application/json":{"schema":{"allOf":[{"$ref":"#/components/schemas/V1Error"},{"type":"object","properties":{"error":{"type":"object","properties":{"code":{"type":"string","examples":["unavailable"]}}}}}]}}}}},"x-required-scope":"crm:read","x-mcp-tool":"crm_list_follow_ups"},"parameters":[{"$ref":"#/components/parameters/FinnVersion"}]},"/crm/follow_ups/stats":{"get":{"tags":["CRM"],"summary":"Get follow-up counts","operationId":"getCrmFollowUpStats","description":"**Scope:** `crm:read`.\n\nOpen, overdue and due-today follow-up counts.\n\nCall with a member's OAuth token or MCP key (acts as that person, with their contact visibility). Org API keys get 403 `user_token_required`; they use `/contacts`. Beta: switched on per organization (`v1_crm_parity`); while it is off the endpoint answers as an unknown route. Errors use the `V1Error` body. A CRM check that refuses the request keeps its own `error.code` (for example `invalid_spec`, `contact_exists`) and any details it returns (missing stage checks, rejected filters) in `error.details`.\n\n**MCP tool:** `crm_list_follow_ups`. **Rate limit:** 120 requests per minute per credential, shared with the same credential's other CRM reads.","parameters":[{"name":"tz","in":"query","description":"Time zone for \"today\", e.g. `Asia/Kolkata`.","schema":{"type":"string"}}],"responses":{"200":{"description":"The counts.","content":{"application/json":{"schema":{"type":"object","additionalProperties":true}}}},"400":{"description":"The request is not valid (`invalid_request`, `org_conflict`).","content":{"application/json":{"schema":{"allOf":[{"$ref":"#/components/schemas/V1Error"},{"type":"object","properties":{"error":{"type":"object","properties":{"code":{"type":"string","examples":["invalid_request","org_conflict"]}}}}}]}}}},"401":{"$ref":"#/components/responses/Unauthorized"},"403":{"description":"The credential lacks the scope (`insufficient_scope`), is an org API key (`user_token_required`), or the member may not see this (`forbidden`).","content":{"application/json":{"schema":{"allOf":[{"$ref":"#/components/schemas/V1Error"},{"type":"object","properties":{"error":{"type":"object","properties":{"code":{"type":"string","examples":["insufficient_scope","user_token_required","forbidden"]}}}}}]}}}},"429":{"$ref":"#/components/responses/RateLimited"},"500":{"$ref":"#/components/responses/InternalError"},"503":{"description":"The organization settings could not be checked (`unavailable`). Safe to retry.","content":{"application/json":{"schema":{"allOf":[{"$ref":"#/components/schemas/V1Error"},{"type":"object","properties":{"error":{"type":"object","properties":{"code":{"type":"string","examples":["unavailable"]}}}}}]}}}}},"x-required-scope":"crm:read","x-mcp-tool":"crm_list_follow_ups"},"parameters":[{"$ref":"#/components/parameters/FinnVersion"}]},"/crm/my_work":{"get":{"tags":["CRM"],"summary":"Get my work","operationId":"getCrmMyWork","description":"**Scope:** `crm:read`.\n\nThe member's queue: their records and follow-ups that need action.\n\nCall with a member's OAuth token or MCP key (acts as that person, with their contact visibility). Org API keys get 403 `user_token_required`; they use `/contacts`. Beta: switched on per organization (`v1_crm_parity`); while it is off the endpoint answers as an unknown route. Errors use the `V1Error` body. A CRM check that refuses the request keeps its own `error.code` (for example `invalid_spec`, `contact_exists`) and any details it returns (missing stage checks, rejected filters) in `error.details`.\n\n**MCP tool:** `crm_my_work`. **Rate limit:** 120 requests per minute per credential, shared with the same credential's other CRM reads.","responses":{"200":{"description":"The queue.","content":{"application/json":{"schema":{"type":"object","additionalProperties":true}}}},"400":{"description":"The request is not valid (`invalid_request`, `org_conflict`).","content":{"application/json":{"schema":{"allOf":[{"$ref":"#/components/schemas/V1Error"},{"type":"object","properties":{"error":{"type":"object","properties":{"code":{"type":"string","examples":["invalid_request","org_conflict"]}}}}}]}}}},"401":{"$ref":"#/components/responses/Unauthorized"},"403":{"description":"The credential lacks the scope (`insufficient_scope`), is an org API key (`user_token_required`), or the member may not see this (`forbidden`).","content":{"application/json":{"schema":{"allOf":[{"$ref":"#/components/schemas/V1Error"},{"type":"object","properties":{"error":{"type":"object","properties":{"code":{"type":"string","examples":["insufficient_scope","user_token_required","forbidden"]}}}}}]}}}},"429":{"$ref":"#/components/responses/RateLimited"},"500":{"$ref":"#/components/responses/InternalError"},"503":{"description":"The organization settings could not be checked (`unavailable`). Safe to retry.","content":{"application/json":{"schema":{"allOf":[{"$ref":"#/components/schemas/V1Error"},{"type":"object","properties":{"error":{"type":"object","properties":{"code":{"type":"string","examples":["unavailable"]}}}}}]}}}}},"x-required-scope":"crm:read","x-mcp-tool":"crm_my_work"},"parameters":[{"$ref":"#/components/parameters/FinnVersion"}]},"/crm/views":{"post":{"tags":["CRM"],"summary":"Save a view","operationId":"saveCrmView","description":"**Scope:** `crm:write`.\n\nA personal saved view.\n\nCall with a member's OAuth token or MCP key (acts as that person, with their contact visibility). Org API keys get 403 `user_token_required`; they use `/contacts`. Beta: switched on per organization (`v1_crm_parity`); while it is off the endpoint answers as an unknown route. Errors use the `V1Error` body. A CRM check that refuses the request keeps its own `error.code` (for example `invalid_spec`, `contact_exists`) and any details it returns (missing stage checks, rejected filters) in `error.details`.\n\n**MCP tool:** `crm_save_view`. **Rate limit:** 30 writes per minute per credential, shared with the same credential's other CRM writes.","parameters":[{"$ref":"#/components/parameters/IdempotencyKey"}],"requestBody":{"required":true,"content":{"application/json":{"schema":{"type":"object","additionalProperties":false,"properties":{"name":{"type":"string"},"filters":{"type":"object","additionalProperties":true},"columns":{"type":"array","items":{"type":"string"}},"display":{"type":"object","additionalProperties":true}},"required":["name"]}}}},"responses":{"201":{"description":"The view.","content":{"application/json":{"schema":{"type":"object","additionalProperties":true}}}},"400":{"description":"The request is not valid (`invalid_request`, `org_conflict`).","content":{"application/json":{"schema":{"allOf":[{"$ref":"#/components/schemas/V1Error"},{"type":"object","properties":{"error":{"type":"object","properties":{"code":{"type":"string","examples":["invalid_request","org_conflict"]}}}}}]}}}},"401":{"$ref":"#/components/responses/Unauthorized"},"403":{"description":"The credential lacks the scope (`insufficient_scope`), is an org API key (`user_token_required`), or the member may not see this (`forbidden`).","content":{"application/json":{"schema":{"allOf":[{"$ref":"#/components/schemas/V1Error"},{"type":"object","properties":{"error":{"type":"object","properties":{"code":{"type":"string","examples":["insufficient_scope","user_token_required","forbidden"]}}}}}]}}}},"429":{"$ref":"#/components/responses/RateLimited"},"500":{"$ref":"#/components/responses/InternalError"},"503":{"description":"The organization settings could not be checked (`unavailable`). Safe to retry.","content":{"application/json":{"schema":{"allOf":[{"$ref":"#/components/schemas/V1Error"},{"type":"object","properties":{"error":{"type":"object","properties":{"code":{"type":"string","examples":["unavailable"]}}}}}]}}}}},"x-required-scope":"crm:write","x-mcp-tool":"crm_save_view"},"get":{"tags":["CRM"],"summary":"List saved views","operationId":"listCrmViews","description":"**Scope:** `crm:read`.\n\nSaved views (filters and columns) the member can use.\n\nCall with a member's OAuth token or MCP key (acts as that person, with their contact visibility). Org API keys get 403 `user_token_required`; they use `/contacts`. Beta: switched on per organization (`v1_crm_parity`); while it is off the endpoint answers as an unknown route. Errors use the `V1Error` body. A CRM check that refuses the request keeps its own `error.code` (for example `invalid_spec`, `contact_exists`) and any details it returns (missing stage checks, rejected filters) in `error.details`.\n\n**MCP tool:** `crm_list_views`. **Rate limit:** 120 requests per minute per credential, shared with the same credential's other CRM reads.","responses":{"200":{"description":"The views.","content":{"application/json":{"schema":{"type":"object","additionalProperties":true}}}},"400":{"description":"The request is not valid (`invalid_request`, `org_conflict`).","content":{"application/json":{"schema":{"allOf":[{"$ref":"#/components/schemas/V1Error"},{"type":"object","properties":{"error":{"type":"object","properties":{"code":{"type":"string","examples":["invalid_request","org_conflict"]}}}}}]}}}},"401":{"$ref":"#/components/responses/Unauthorized"},"403":{"description":"The credential lacks the scope (`insufficient_scope`), is an org API key (`user_token_required`), or the member may not see this (`forbidden`).","content":{"application/json":{"schema":{"allOf":[{"$ref":"#/components/schemas/V1Error"},{"type":"object","properties":{"error":{"type":"object","properties":{"code":{"type":"string","examples":["insufficient_scope","user_token_required","forbidden"]}}}}}]}}}},"429":{"$ref":"#/components/responses/RateLimited"},"500":{"$ref":"#/components/responses/InternalError"},"503":{"description":"The organization settings could not be checked (`unavailable`). Safe to retry.","content":{"application/json":{"schema":{"allOf":[{"$ref":"#/components/schemas/V1Error"},{"type":"object","properties":{"error":{"type":"object","properties":{"code":{"type":"string","examples":["unavailable"]}}}}}]}}}}},"x-required-scope":"crm:read","x-mcp-tool":"crm_list_views"},"parameters":[{"$ref":"#/components/parameters/FinnVersion"}]},"/crm/summary":{"get":{"tags":["CRM"],"summary":"Get the pipeline summary","operationId":"getCrmSummary","description":"**Scope:** `crm:read`.\n\nTotals across the pipeline.\n\nCall with a member's OAuth token or MCP key (acts as that person, with their contact visibility). Org API keys get 403 `user_token_required`; they use `/contacts`. Beta: switched on per organization (`v1_crm_parity`); while it is off the endpoint answers as an unknown route. Errors use the `V1Error` body. A CRM check that refuses the request keeps its own `error.code` (for example `invalid_spec`, `contact_exists`) and any details it returns (missing stage checks, rejected filters) in `error.details`.\n\n**MCP tool:** `crm_get_pipeline_stats`. **Rate limit:** 120 requests per minute per credential, shared with the same credential's other CRM reads.","responses":{"200":{"description":"The summary.","content":{"application/json":{"schema":{"type":"object","additionalProperties":true}}}},"400":{"description":"The request is not valid (`invalid_request`, `org_conflict`).","content":{"application/json":{"schema":{"allOf":[{"$ref":"#/components/schemas/V1Error"},{"type":"object","properties":{"error":{"type":"object","properties":{"code":{"type":"string","examples":["invalid_request","org_conflict"]}}}}}]}}}},"401":{"$ref":"#/components/responses/Unauthorized"},"403":{"description":"The credential lacks the scope (`insufficient_scope`), is an org API key (`user_token_required`), or the member may not see this (`forbidden`).","content":{"application/json":{"schema":{"allOf":[{"$ref":"#/components/schemas/V1Error"},{"type":"object","properties":{"error":{"type":"object","properties":{"code":{"type":"string","examples":["insufficient_scope","user_token_required","forbidden"]}}}}}]}}}},"429":{"$ref":"#/components/responses/RateLimited"},"500":{"$ref":"#/components/responses/InternalError"},"503":{"description":"The organization settings could not be checked (`unavailable`). Safe to retry.","content":{"application/json":{"schema":{"allOf":[{"$ref":"#/components/schemas/V1Error"},{"type":"object","properties":{"error":{"type":"object","properties":{"code":{"type":"string","examples":["unavailable"]}}}}}]}}}}},"x-required-scope":"crm:read","x-mcp-tool":"crm_get_pipeline_stats"},"parameters":[{"$ref":"#/components/parameters/FinnVersion"}]},"/crm/stage_counts":{"get":{"tags":["CRM"],"summary":"Get counts per stage","operationId":"getCrmStageCounts","description":"**Scope:** `crm:read`.\n\nHow many records are in each stage.\n\nCall with a member's OAuth token or MCP key (acts as that person, with their contact visibility). Org API keys get 403 `user_token_required`; they use `/contacts`. Beta: switched on per organization (`v1_crm_parity`); while it is off the endpoint answers as an unknown route. Errors use the `V1Error` body. A CRM check that refuses the request keeps its own `error.code` (for example `invalid_spec`, `contact_exists`) and any details it returns (missing stage checks, rejected filters) in `error.details`.\n\n**MCP tool:** `crm_get_pipeline_stats`. **Rate limit:** 120 requests per minute per credential, shared with the same credential's other CRM reads.","responses":{"200":{"description":"Counts per stage.","content":{"application/json":{"schema":{"type":"object","additionalProperties":true}}}},"400":{"description":"The request is not valid (`invalid_request`, `org_conflict`).","content":{"application/json":{"schema":{"allOf":[{"$ref":"#/components/schemas/V1Error"},{"type":"object","properties":{"error":{"type":"object","properties":{"code":{"type":"string","examples":["invalid_request","org_conflict"]}}}}}]}}}},"401":{"$ref":"#/components/responses/Unauthorized"},"403":{"description":"The credential lacks the scope (`insufficient_scope`), is an org API key (`user_token_required`), or the member may not see this (`forbidden`).","content":{"application/json":{"schema":{"allOf":[{"$ref":"#/components/schemas/V1Error"},{"type":"object","properties":{"error":{"type":"object","properties":{"code":{"type":"string","examples":["insufficient_scope","user_token_required","forbidden"]}}}}}]}}}},"429":{"$ref":"#/components/responses/RateLimited"},"500":{"$ref":"#/components/responses/InternalError"},"503":{"description":"The organization settings could not be checked (`unavailable`). Safe to retry.","content":{"application/json":{"schema":{"allOf":[{"$ref":"#/components/schemas/V1Error"},{"type":"object","properties":{"error":{"type":"object","properties":{"code":{"type":"string","examples":["unavailable"]}}}}}]}}}}},"x-required-scope":"crm:read","x-mcp-tool":"crm_get_pipeline_stats"},"parameters":[{"$ref":"#/components/parameters/FinnVersion"}]},"/crm/speed":{"get":{"tags":["CRM"],"summary":"Get speed to lead","operationId":"getCrmSpeed","description":"**Scope:** `crm:read`.\n\nResponse-time figures for new leads.\n\nCall with a member's OAuth token or MCP key (acts as that person, with their contact visibility). Org API keys get 403 `user_token_required`; they use `/contacts`. Beta: switched on per organization (`v1_crm_parity`); while it is off the endpoint answers as an unknown route. Errors use the `V1Error` body. A CRM check that refuses the request keeps its own `error.code` (for example `invalid_spec`, `contact_exists`) and any details it returns (missing stage checks, rejected filters) in `error.details`.\n\n**MCP tool:** `crm_get_pipeline_stats`. **Rate limit:** 120 requests per minute per credential, shared with the same credential's other CRM reads.","parameters":[{"name":"period","in":"query","description":"The period, e.g. `7d` or `30d`.","schema":{"type":"string"}}],"responses":{"200":{"description":"The figures.","content":{"application/json":{"schema":{"type":"object","additionalProperties":true}}}},"400":{"description":"The request is not valid (`invalid_request`, `org_conflict`).","content":{"application/json":{"schema":{"allOf":[{"$ref":"#/components/schemas/V1Error"},{"type":"object","properties":{"error":{"type":"object","properties":{"code":{"type":"string","examples":["invalid_request","org_conflict"]}}}}}]}}}},"401":{"$ref":"#/components/responses/Unauthorized"},"403":{"description":"The credential lacks the scope (`insufficient_scope`), is an org API key (`user_token_required`), or the member may not see this (`forbidden`).","content":{"application/json":{"schema":{"allOf":[{"$ref":"#/components/schemas/V1Error"},{"type":"object","properties":{"error":{"type":"object","properties":{"code":{"type":"string","examples":["insufficient_scope","user_token_required","forbidden"]}}}}}]}}}},"429":{"$ref":"#/components/responses/RateLimited"},"500":{"$ref":"#/components/responses/InternalError"},"503":{"description":"The organization settings could not be checked (`unavailable`). Safe to retry.","content":{"application/json":{"schema":{"allOf":[{"$ref":"#/components/schemas/V1Error"},{"type":"object","properties":{"error":{"type":"object","properties":{"code":{"type":"string","examples":["unavailable"]}}}}}]}}}}},"x-required-scope":"crm:read","x-mcp-tool":"crm_get_pipeline_stats"},"parameters":[{"$ref":"#/components/parameters/FinnVersion"}]},"/crm/duplicates":{"get":{"tags":["CRM"],"summary":"Find duplicates","operationId":"listCrmDuplicates","description":"**Scope:** `crm:read`.\n\nGroups of records that look like the same person or company.\n\nCall with a member's OAuth token or MCP key (acts as that person, with their contact visibility). Org API keys get 403 `user_token_required`; they use `/contacts`. Beta: switched on per organization (`v1_crm_parity`); while it is off the endpoint answers as an unknown route. Errors use the `V1Error` body. A CRM check that refuses the request keeps its own `error.code` (for example `invalid_spec`, `contact_exists`) and any details it returns (missing stage checks, rejected filters) in `error.details`.\n\n**MCP tool:** `crm_find_duplicates`. **Rate limit:** 120 requests per minute per credential, shared with the same credential's other CRM reads.","responses":{"200":{"description":"Duplicate groups.","content":{"application/json":{"schema":{"type":"object","additionalProperties":true}}}},"400":{"description":"The request is not valid (`invalid_request`, `org_conflict`).","content":{"application/json":{"schema":{"allOf":[{"$ref":"#/components/schemas/V1Error"},{"type":"object","properties":{"error":{"type":"object","properties":{"code":{"type":"string","examples":["invalid_request","org_conflict"]}}}}}]}}}},"401":{"$ref":"#/components/responses/Unauthorized"},"403":{"description":"The credential lacks the scope (`insufficient_scope`), is an org API key (`user_token_required`), or the member may not see this (`forbidden`).","content":{"application/json":{"schema":{"allOf":[{"$ref":"#/components/schemas/V1Error"},{"type":"object","properties":{"error":{"type":"object","properties":{"code":{"type":"string","examples":["insufficient_scope","user_token_required","forbidden"]}}}}}]}}}},"429":{"$ref":"#/components/responses/RateLimited"},"500":{"$ref":"#/components/responses/InternalError"},"503":{"description":"The organization settings could not be checked (`unavailable`). Safe to retry.","content":{"application/json":{"schema":{"allOf":[{"$ref":"#/components/schemas/V1Error"},{"type":"object","properties":{"error":{"type":"object","properties":{"code":{"type":"string","examples":["unavailable"]}}}}}]}}}}},"x-required-scope":"crm:read","x-mcp-tool":"crm_find_duplicates"},"parameters":[{"$ref":"#/components/parameters/FinnVersion"}]},"/crm/ask":{"post":{"tags":["CRM"],"summary":"Ask the CRM","operationId":"askCrm","description":"**Scope:** `crm:read`.\n\nA question in plain words, answered from the records this member can see.\n\nCall with a member's OAuth token or MCP key (acts as that person, with their contact visibility). Org API keys get 403 `user_token_required`; they use `/contacts`. Beta: switched on per organization (`v1_crm_parity`); while it is off the endpoint answers as an unknown route. Errors use the `V1Error` body. A CRM check that refuses the request keeps its own `error.code` (for example `invalid_spec`, `contact_exists`) and any details it returns (missing stage checks, rejected filters) in `error.details`.\n\n**MCP tool:** `crm_ask`. **Rate limit:** 120 requests per minute per credential, shared with the same credential's other CRM reads.","requestBody":{"required":true,"content":{"application/json":{"schema":{"type":"object","properties":{"text":{"type":"string"}},"required":["text"]}}}},"responses":{"200":{"description":"The answer.","content":{"application/json":{"schema":{"type":"object","additionalProperties":true}}}},"400":{"description":"The request is not valid (`invalid_request`, `org_conflict`).","content":{"application/json":{"schema":{"allOf":[{"$ref":"#/components/schemas/V1Error"},{"type":"object","properties":{"error":{"type":"object","properties":{"code":{"type":"string","examples":["invalid_request","org_conflict"]}}}}}]}}}},"401":{"$ref":"#/components/responses/Unauthorized"},"403":{"description":"The credential lacks the scope (`insufficient_scope`), is an org API key (`user_token_required`), or the member may not see this (`forbidden`).","content":{"application/json":{"schema":{"allOf":[{"$ref":"#/components/schemas/V1Error"},{"type":"object","properties":{"error":{"type":"object","properties":{"code":{"type":"string","examples":["insufficient_scope","user_token_required","forbidden"]}}}}}]}}}},"429":{"$ref":"#/components/responses/RateLimited"},"500":{"$ref":"#/components/responses/InternalError"},"503":{"description":"The organization settings could not be checked (`unavailable`). Safe to retry.","content":{"application/json":{"schema":{"allOf":[{"$ref":"#/components/schemas/V1Error"},{"type":"object","properties":{"error":{"type":"object","properties":{"code":{"type":"string","examples":["unavailable"]}}}}}]}}}}},"x-required-scope":"crm:read","x-mcp-tool":"crm_ask"},"parameters":[{"$ref":"#/components/parameters/FinnVersion"}]},"/crm/records/{id}/notes":{"post":{"tags":["CRM"],"summary":"Add a note","operationId":"addCrmNote","description":"**Scope:** `crm:write`.\n\nA comment on the record's timeline. `type` can only be `comment`; `mentions` are user ids to notify; `clientId` lets a retry be spotted.\n\nCall with a member's OAuth token or MCP key (acts as that person, with their contact visibility). Org API keys get 403 `user_token_required`; they use `/contacts`. Beta: switched on per organization (`v1_crm_parity`); while it is off the endpoint answers as an unknown route. Errors use the `V1Error` body. A CRM check that refuses the request keeps its own `error.code` (for example `invalid_spec`, `contact_exists`) and any details it returns (missing stage checks, rejected filters) in `error.details`.\n\n**MCP tool:** `crm_add_note`. **Rate limit:** 30 writes per minute per credential, shared with the same credential's other CRM writes.","parameters":[{"name":"id","in":"path","required":true,"description":"The record id, or its phone number in E.164.","schema":{"type":"string"}},{"$ref":"#/components/parameters/IdempotencyKey"}],"requestBody":{"required":true,"content":{"application/json":{"schema":{"type":"object","additionalProperties":false,"properties":{"type":{"type":"string","enum":["comment"]},"text":{"type":"string"},"mentions":{"type":"array","items":{"type":"string"}},"clientId":{"type":"string"}},"required":["text"]}}}},"responses":{"201":{"description":"The note.","content":{"application/json":{"schema":{"type":"object","additionalProperties":true}}}},"400":{"description":"The request is not valid (`invalid_request`, `org_conflict`).","content":{"application/json":{"schema":{"allOf":[{"$ref":"#/components/schemas/V1Error"},{"type":"object","properties":{"error":{"type":"object","properties":{"code":{"type":"string","examples":["invalid_request","org_conflict"]}}}}}]}}}},"401":{"$ref":"#/components/responses/Unauthorized"},"403":{"description":"The credential lacks the scope (`insufficient_scope`), is an org API key (`user_token_required`), or the member may not see this (`forbidden`).","content":{"application/json":{"schema":{"allOf":[{"$ref":"#/components/schemas/V1Error"},{"type":"object","properties":{"error":{"type":"object","properties":{"code":{"type":"string","examples":["insufficient_scope","user_token_required","forbidden"]}}}}}]}}}},"404":{"description":"No such record visible to this member (`not_found`).","content":{"application/json":{"schema":{"allOf":[{"$ref":"#/components/schemas/V1Error"},{"type":"object","properties":{"error":{"type":"object","properties":{"code":{"type":"string","examples":["not_found"]}}}}}]}}}},"429":{"$ref":"#/components/responses/RateLimited"},"500":{"$ref":"#/components/responses/InternalError"},"503":{"description":"The organization settings could not be checked (`unavailable`). Safe to retry.","content":{"application/json":{"schema":{"allOf":[{"$ref":"#/components/schemas/V1Error"},{"type":"object","properties":{"error":{"type":"object","properties":{"code":{"type":"string","examples":["unavailable"]}}}}}]}}}}},"x-required-scope":"crm:write","x-mcp-tool":"crm_add_note"},"parameters":[{"$ref":"#/components/parameters/FinnVersion"}]},"/crm/records/{id}/follow_ups":{"post":{"tags":["CRM"],"summary":"Create a follow-up","operationId":"createCrmFollowUp","description":"**Scope:** `crm:write`.\n\nA follow-up on the record.\n\nCall with a member's OAuth token or MCP key (acts as that person, with their contact visibility). Org API keys get 403 `user_token_required`; they use `/contacts`. Beta: switched on per organization (`v1_crm_parity`); while it is off the endpoint answers as an unknown route. Errors use the `V1Error` body. A CRM check that refuses the request keeps its own `error.code` (for example `invalid_spec`, `contact_exists`) and any details it returns (missing stage checks, rejected filters) in `error.details`.\n\n**MCP tool:** `crm_create_follow_up`. **Rate limit:** 30 writes per minute per credential, shared with the same credential's other CRM writes.","parameters":[{"name":"id","in":"path","required":true,"description":"The record id, or its phone number in E.164.","schema":{"type":"string"}},{"$ref":"#/components/parameters/IdempotencyKey"}],"requestBody":{"required":true,"content":{"application/json":{"schema":{"type":"object","additionalProperties":false,"properties":{"title":{"type":"string"},"dueAt":{"type":"string"},"note":{"type":"string"},"priority":{"type":"string"},"assigneeId":{"type":"string"}},"required":["title"]}}}},"responses":{"201":{"description":"The follow-up.","content":{"application/json":{"schema":{"type":"object","additionalProperties":true}}}},"400":{"description":"The request is not valid (`invalid_request`, `org_conflict`).","content":{"application/json":{"schema":{"allOf":[{"$ref":"#/components/schemas/V1Error"},{"type":"object","properties":{"error":{"type":"object","properties":{"code":{"type":"string","examples":["invalid_request","org_conflict"]}}}}}]}}}},"401":{"$ref":"#/components/responses/Unauthorized"},"403":{"description":"The credential lacks the scope (`insufficient_scope`), is an org API key (`user_token_required`), or the member may not see this (`forbidden`).","content":{"application/json":{"schema":{"allOf":[{"$ref":"#/components/schemas/V1Error"},{"type":"object","properties":{"error":{"type":"object","properties":{"code":{"type":"string","examples":["insufficient_scope","user_token_required","forbidden"]}}}}}]}}}},"404":{"description":"No such record visible to this member (`not_found`).","content":{"application/json":{"schema":{"allOf":[{"$ref":"#/components/schemas/V1Error"},{"type":"object","properties":{"error":{"type":"object","properties":{"code":{"type":"string","examples":["not_found"]}}}}}]}}}},"429":{"$ref":"#/components/responses/RateLimited"},"500":{"$ref":"#/components/responses/InternalError"},"503":{"description":"The organization settings could not be checked (`unavailable`). Safe to retry.","content":{"application/json":{"schema":{"allOf":[{"$ref":"#/components/schemas/V1Error"},{"type":"object","properties":{"error":{"type":"object","properties":{"code":{"type":"string","examples":["unavailable"]}}}}}]}}}}},"x-required-scope":"crm:write","x-mcp-tool":"crm_create_follow_up"},"parameters":[{"$ref":"#/components/parameters/FinnVersion"}]},"/crm/follow_ups/{id}":{"patch":{"tags":["CRM"],"summary":"Update a follow-up","operationId":"updateCrmFollowUp","description":"**Scope:** `crm:write`.\n\nComplete, snooze, reschedule or reassign a follow-up. `expectDueAt` refuses the change if the due date moved meanwhile. The `id` is the follow-up id.\n\nCall with a member's OAuth token or MCP key (acts as that person, with their contact visibility). Org API keys get 403 `user_token_required`; they use `/contacts`. Beta: switched on per organization (`v1_crm_parity`); while it is off the endpoint answers as an unknown route. Errors use the `V1Error` body. A CRM check that refuses the request keeps its own `error.code` (for example `invalid_spec`, `contact_exists`) and any details it returns (missing stage checks, rejected filters) in `error.details`.\n\n**MCP tool:** `crm_update_follow_up`. **Rate limit:** 30 writes per minute per credential, shared with the same credential's other CRM writes.","parameters":[{"name":"id","in":"path","required":true,"description":"The record id, or its phone number in E.164.","schema":{"type":"string"}},{"$ref":"#/components/parameters/IdempotencyKey"}],"requestBody":{"required":true,"content":{"application/json":{"schema":{"type":"object","additionalProperties":false,"properties":{"status":{"type":"string"},"snoozedUntil":{"type":"string"},"dueAt":{"type":"string"},"assigneeId":{"type":"string"},"priority":{"type":"string"},"title":{"type":"string"},"note":{"type":"string"},"expectDueAt":{"type":"string"}}}}}},"responses":{"200":{"description":"The follow-up.","content":{"application/json":{"schema":{"type":"object","additionalProperties":true}}}},"400":{"description":"The request is not valid (`invalid_request`, `org_conflict`).","content":{"application/json":{"schema":{"allOf":[{"$ref":"#/components/schemas/V1Error"},{"type":"object","properties":{"error":{"type":"object","properties":{"code":{"type":"string","examples":["invalid_request","org_conflict"]}}}}}]}}}},"401":{"$ref":"#/components/responses/Unauthorized"},"403":{"description":"The credential lacks the scope (`insufficient_scope`), is an org API key (`user_token_required`), or the member may not see this (`forbidden`).","content":{"application/json":{"schema":{"allOf":[{"$ref":"#/components/schemas/V1Error"},{"type":"object","properties":{"error":{"type":"object","properties":{"code":{"type":"string","examples":["insufficient_scope","user_token_required","forbidden"]}}}}}]}}}},"404":{"description":"No such record visible to this member (`not_found`).","content":{"application/json":{"schema":{"allOf":[{"$ref":"#/components/schemas/V1Error"},{"type":"object","properties":{"error":{"type":"object","properties":{"code":{"type":"string","examples":["not_found"]}}}}}]}}}},"429":{"$ref":"#/components/responses/RateLimited"},"500":{"$ref":"#/components/responses/InternalError"},"503":{"description":"The organization settings could not be checked (`unavailable`). Safe to retry.","content":{"application/json":{"schema":{"allOf":[{"$ref":"#/components/schemas/V1Error"},{"type":"object","properties":{"error":{"type":"object","properties":{"code":{"type":"string","examples":["unavailable"]}}}}}]}}}}},"x-required-scope":"crm:write","x-mcp-tool":"crm_update_follow_up"},"parameters":[{"$ref":"#/components/parameters/FinnVersion"}]},"/crm/records":{"post":{"tags":["CRM"],"summary":"Create a record","operationId":"createCrmRecord","description":"**Scope:** `crm:write`.\n\nA new record. A number the organization already has (live or deleted) is refused with 409; edit that record instead.\n\nCall with a member's OAuth token or MCP key (acts as that person, with their contact visibility). Org API keys get 403 `user_token_required`; they use `/contacts`. Beta: switched on per organization (`v1_crm_parity`); while it is off the endpoint answers as an unknown route. Errors use the `V1Error` body. A CRM check that refuses the request keeps its own `error.code` (for example `invalid_spec`, `contact_exists`) and any details it returns (missing stage checks, rejected filters) in `error.details`.\n\n**MCP tool:** `crm_create_record`. **Rate limit:** 30 writes per minute per credential, shared with the same credential's other CRM writes.","parameters":[{"$ref":"#/components/parameters/IdempotencyKey"}],"requestBody":{"required":true,"content":{"application/json":{"schema":{"type":"object","additionalProperties":false,"properties":{"phone":{"type":"string"},"name":{"type":"string"},"customFields":{"type":"object","additionalProperties":true},"kind":{"type":"string"},"links":{"type":"array","items":{"type":"object","additionalProperties":true}}},"required":["phone"]}}}},"responses":{"201":{"description":"The record.","content":{"application/json":{"schema":{"type":"object","additionalProperties":true}}}},"400":{"description":"The request is not valid (`invalid_request`, `org_conflict`).","content":{"application/json":{"schema":{"allOf":[{"$ref":"#/components/schemas/V1Error"},{"type":"object","properties":{"error":{"type":"object","properties":{"code":{"type":"string","examples":["invalid_request","org_conflict"]}}}}}]}}}},"401":{"$ref":"#/components/responses/Unauthorized"},"403":{"description":"The credential lacks the scope (`insufficient_scope`), is an org API key (`user_token_required`), or the member may not see this (`forbidden`).","content":{"application/json":{"schema":{"allOf":[{"$ref":"#/components/schemas/V1Error"},{"type":"object","properties":{"error":{"type":"object","properties":{"code":{"type":"string","examples":["insufficient_scope","user_token_required","forbidden"]}}}}}]}}}},"409":{"description":"The organization already has this number (`conflict`).","content":{"application/json":{"schema":{"allOf":[{"$ref":"#/components/schemas/V1Error"},{"type":"object","properties":{"error":{"type":"object","properties":{"code":{"type":"string","examples":["conflict"]}}}}}]}}}},"429":{"$ref":"#/components/responses/RateLimited"},"500":{"$ref":"#/components/responses/InternalError"},"503":{"description":"The organization settings could not be checked (`unavailable`). Safe to retry.","content":{"application/json":{"schema":{"allOf":[{"$ref":"#/components/schemas/V1Error"},{"type":"object","properties":{"error":{"type":"object","properties":{"code":{"type":"string","examples":["unavailable"]}}}}}]}}}}},"x-required-scope":"crm:write","x-mcp-tool":"crm_create_record"},"parameters":[{"$ref":"#/components/parameters/FinnVersion"}]},"/crm/records/{id}/stage_checks/{stage}/{check}":{"put":{"tags":["CRM"],"summary":"Tick a stage check","operationId":"setCrmStageCheck","description":"**Scope:** `crm:write`.\n\nTick (`done`) or clear (`null`) a stage gate check. Checks cannot be waived here.\n\nCall with a member's OAuth token or MCP key (acts as that person, with their contact visibility). Org API keys get 403 `user_token_required`; they use `/contacts`. Beta: switched on per organization (`v1_crm_parity`); while it is off the endpoint answers as an unknown route. Errors use the `V1Error` body. A CRM check that refuses the request keeps its own `error.code` (for example `invalid_spec`, `contact_exists`) and any details it returns (missing stage checks, rejected filters) in `error.details`.\n\n**MCP tool:** `crm_tick_stage_check`. **Rate limit:** 30 writes per minute per credential, shared with the same credential's other CRM writes.","parameters":[{"name":"id","in":"path","required":true,"description":"The record id, or its phone number in E.164.","schema":{"type":"string"}},{"name":"stage","in":"path","required":true,"description":"The stage.","schema":{"type":"string"}},{"name":"check","in":"path","required":true,"description":"The check id.","schema":{"type":"string"}},{"$ref":"#/components/parameters/IdempotencyKey"}],"requestBody":{"required":true,"content":{"application/json":{"schema":{"type":"object","additionalProperties":false,"properties":{"state":{"type":["string","null"],"enum":["done",null]}},"required":["state"]}}}},"responses":{"200":{"description":"The gate.","content":{"application/json":{"schema":{"type":"object","additionalProperties":true}}}},"400":{"description":"The request is not valid (`invalid_request`, `org_conflict`).","content":{"application/json":{"schema":{"allOf":[{"$ref":"#/components/schemas/V1Error"},{"type":"object","properties":{"error":{"type":"object","properties":{"code":{"type":"string","examples":["invalid_request","org_conflict"]}}}}}]}}}},"401":{"$ref":"#/components/responses/Unauthorized"},"403":{"description":"The credential lacks the scope (`insufficient_scope`), is an org API key (`user_token_required`), or the member may not see this (`forbidden`).","content":{"application/json":{"schema":{"allOf":[{"$ref":"#/components/schemas/V1Error"},{"type":"object","properties":{"error":{"type":"object","properties":{"code":{"type":"string","examples":["insufficient_scope","user_token_required","forbidden"]}}}}}]}}}},"404":{"description":"No such record visible to this member (`not_found`).","content":{"application/json":{"schema":{"allOf":[{"$ref":"#/components/schemas/V1Error"},{"type":"object","properties":{"error":{"type":"object","properties":{"code":{"type":"string","examples":["not_found"]}}}}}]}}}},"429":{"$ref":"#/components/responses/RateLimited"},"500":{"$ref":"#/components/responses/InternalError"},"503":{"description":"The organization settings could not be checked (`unavailable`). Safe to retry.","content":{"application/json":{"schema":{"allOf":[{"$ref":"#/components/schemas/V1Error"},{"type":"object","properties":{"error":{"type":"object","properties":{"code":{"type":"string","examples":["unavailable"]}}}}}]}}}}},"x-required-scope":"crm:write","x-mcp-tool":"crm_tick_stage_check"},"parameters":[{"$ref":"#/components/parameters/FinnVersion"}]},"/crm/records/{id}/ref_no":{"post":{"tags":["CRM"],"summary":"Assign a reference number","operationId":"assignCrmReferenceNo","description":"**Scope:** `crm:write`.\n\nGives the record its next reference number (case id) if it has none. No body.\n\nCall with a member's OAuth token or MCP key (acts as that person, with their contact visibility). Org API keys get 403 `user_token_required`; they use `/contacts`. Beta: switched on per organization (`v1_crm_parity`); while it is off the endpoint answers as an unknown route. Errors use the `V1Error` body. A CRM check that refuses the request keeps its own `error.code` (for example `invalid_spec`, `contact_exists`) and any details it returns (missing stage checks, rejected filters) in `error.details`.\n\n**MCP tool:** `crm_assign_reference_no, crm_assign_case_id`. **Rate limit:** 30 writes per minute per credential, shared with the same credential's other CRM writes.","parameters":[{"name":"id","in":"path","required":true,"description":"The record id, or its phone number in E.164.","schema":{"type":"string"}},{"$ref":"#/components/parameters/IdempotencyKey"}],"responses":{"200":{"description":"The record's reference number.","content":{"application/json":{"schema":{"type":"object","additionalProperties":true}}}},"400":{"description":"The request is not valid (`invalid_request`, `org_conflict`).","content":{"application/json":{"schema":{"allOf":[{"$ref":"#/components/schemas/V1Error"},{"type":"object","properties":{"error":{"type":"object","properties":{"code":{"type":"string","examples":["invalid_request","org_conflict"]}}}}}]}}}},"401":{"$ref":"#/components/responses/Unauthorized"},"403":{"description":"The credential lacks the scope (`insufficient_scope`), is an org API key (`user_token_required`), or the member may not see this (`forbidden`).","content":{"application/json":{"schema":{"allOf":[{"$ref":"#/components/schemas/V1Error"},{"type":"object","properties":{"error":{"type":"object","properties":{"code":{"type":"string","examples":["insufficient_scope","user_token_required","forbidden"]}}}}}]}}}},"404":{"description":"No such record visible to this member (`not_found`).","content":{"application/json":{"schema":{"allOf":[{"$ref":"#/components/schemas/V1Error"},{"type":"object","properties":{"error":{"type":"object","properties":{"code":{"type":"string","examples":["not_found"]}}}}}]}}}},"429":{"$ref":"#/components/responses/RateLimited"},"500":{"$ref":"#/components/responses/InternalError"},"503":{"description":"The organization settings could not be checked (`unavailable`). Safe to retry.","content":{"application/json":{"schema":{"allOf":[{"$ref":"#/components/schemas/V1Error"},{"type":"object","properties":{"error":{"type":"object","properties":{"code":{"type":"string","examples":["unavailable"]}}}}}]}}}}},"x-required-scope":"crm:write","x-mcp-tool":"crm_assign_reference_no"},"parameters":[{"$ref":"#/components/parameters/FinnVersion"}]},"/crm/records/{id}/documents/{type}/status":{"post":{"tags":["CRM"],"summary":"Set a document status","operationId":"setCrmDocumentStatus","description":"**Scope:** `crm:write`.\n\nMoves a document on the record's checklist to status `to`, with an optional reason, note and file.\n\nCall with a member's OAuth token or MCP key (acts as that person, with their contact visibility). Org API keys get 403 `user_token_required`; they use `/contacts`. Beta: switched on per organization (`v1_crm_parity`); while it is off the endpoint answers as an unknown route. Errors use the `V1Error` body. A CRM check that refuses the request keeps its own `error.code` (for example `invalid_spec`, `contact_exists`) and any details it returns (missing stage checks, rejected filters) in `error.details`.\n\n**MCP tool:** `crm_set_document_status`. **Rate limit:** 30 writes per minute per credential, shared with the same credential's other CRM writes.","parameters":[{"name":"id","in":"path","required":true,"description":"The record id, or its phone number in E.164.","schema":{"type":"string"}},{"name":"type","in":"path","required":true,"description":"The document type.","schema":{"type":"string"}},{"$ref":"#/components/parameters/IdempotencyKey"}],"requestBody":{"required":true,"content":{"application/json":{"schema":{"type":"object","additionalProperties":false,"properties":{"to":{"type":"string"},"reason":{"type":"string"},"note":{"type":"string"},"file_id":{"type":"string"}},"required":["to"]}}}},"responses":{"200":{"description":"The document.","content":{"application/json":{"schema":{"type":"object","additionalProperties":true}}}},"400":{"description":"The request is not valid (`invalid_request`, `org_conflict`).","content":{"application/json":{"schema":{"allOf":[{"$ref":"#/components/schemas/V1Error"},{"type":"object","properties":{"error":{"type":"object","properties":{"code":{"type":"string","examples":["invalid_request","org_conflict"]}}}}}]}}}},"401":{"$ref":"#/components/responses/Unauthorized"},"403":{"description":"The credential lacks the scope (`insufficient_scope`), is an org API key (`user_token_required`), or the member may not see this (`forbidden`).","content":{"application/json":{"schema":{"allOf":[{"$ref":"#/components/schemas/V1Error"},{"type":"object","properties":{"error":{"type":"object","properties":{"code":{"type":"string","examples":["insufficient_scope","user_token_required","forbidden"]}}}}}]}}}},"404":{"description":"No such record visible to this member (`not_found`).","content":{"application/json":{"schema":{"allOf":[{"$ref":"#/components/schemas/V1Error"},{"type":"object","properties":{"error":{"type":"object","properties":{"code":{"type":"string","examples":["not_found"]}}}}}]}}}},"429":{"$ref":"#/components/responses/RateLimited"},"500":{"$ref":"#/components/responses/InternalError"},"503":{"description":"The organization settings could not be checked (`unavailable`). Safe to retry.","content":{"application/json":{"schema":{"allOf":[{"$ref":"#/components/schemas/V1Error"},{"type":"object","properties":{"error":{"type":"object","properties":{"code":{"type":"string","examples":["unavailable"]}}}}}]}}}}},"x-required-scope":"crm:write","x-mcp-tool":"crm_set_document_status"},"parameters":[{"$ref":"#/components/parameters/FinnVersion"}]},"/crm/records/{id}/forms/{form_id}/draft":{"put":{"tags":["CRM"],"summary":"Save a form draft","operationId":"saveCrmFormDraft","description":"**Scope:** `crm:write` and `crm:sensitive:read`.\n\nSaves answers to the record's draft of a form. `version` refuses the save if the draft changed meanwhile.\n\nCall with a member's OAuth token or MCP key (acts as that person, with their contact visibility). Org API keys get 403 `user_token_required`; they use `/contacts`. Beta: switched on per organization (`v1_crm_parity`); while it is off the endpoint answers as an unknown route. Errors use the `V1Error` body. A CRM check that refuses the request keeps its own `error.code` (for example `invalid_spec`, `contact_exists`) and any details it returns (missing stage checks, rejected filters) in `error.details`.\n\n**MCP tool:** `crm_save_form_draft`. **Rate limit:** 30 writes per minute per credential, shared with the same credential's other CRM writes.","parameters":[{"name":"id","in":"path","required":true,"description":"The record id, or its phone number in E.164.","schema":{"type":"string"}},{"name":"form_id","in":"path","required":true,"description":"The form id.","schema":{"type":"string"}},{"$ref":"#/components/parameters/IdempotencyKey"}],"requestBody":{"required":true,"content":{"application/json":{"schema":{"type":"object","additionalProperties":false,"properties":{"answers":{"type":"object","additionalProperties":true},"version":{"type":"integer"}},"required":["answers"]}}}},"responses":{"200":{"description":"The draft.","content":{"application/json":{"schema":{"type":"object","additionalProperties":true}}}},"400":{"description":"The request is not valid (`invalid_request`, `org_conflict`).","content":{"application/json":{"schema":{"allOf":[{"$ref":"#/components/schemas/V1Error"},{"type":"object","properties":{"error":{"type":"object","properties":{"code":{"type":"string","examples":["invalid_request","org_conflict"]}}}}}]}}}},"401":{"$ref":"#/components/responses/Unauthorized"},"403":{"description":"The credential lacks the scope (`insufficient_scope`), is an org API key (`user_token_required`), or the member may not see this (`forbidden`).","content":{"application/json":{"schema":{"allOf":[{"$ref":"#/components/schemas/V1Error"},{"type":"object","properties":{"error":{"type":"object","properties":{"code":{"type":"string","examples":["insufficient_scope","user_token_required","forbidden"]}}}}}]}}}},"404":{"description":"No such record visible to this member (`not_found`).","content":{"application/json":{"schema":{"allOf":[{"$ref":"#/components/schemas/V1Error"},{"type":"object","properties":{"error":{"type":"object","properties":{"code":{"type":"string","examples":["not_found"]}}}}}]}}}},"409":{"description":"The draft changed meanwhile (`conflict`).","content":{"application/json":{"schema":{"allOf":[{"$ref":"#/components/schemas/V1Error"},{"type":"object","properties":{"error":{"type":"object","properties":{"code":{"type":"string","examples":["conflict"]}}}}}]}}}},"429":{"$ref":"#/components/responses/RateLimited"},"500":{"$ref":"#/components/responses/InternalError"},"503":{"description":"The organization settings could not be checked (`unavailable`). Safe to retry.","content":{"application/json":{"schema":{"allOf":[{"$ref":"#/components/schemas/V1Error"},{"type":"object","properties":{"error":{"type":"object","properties":{"code":{"type":"string","examples":["unavailable"]}}}}}]}}}}},"x-required-scope":"crm:write","x-mcp-tool":"crm_save_form_draft"},"parameters":[{"$ref":"#/components/parameters/FinnVersion"}]},"/crm/records/{id}/doc_drafts":{"post":{"tags":["CRM"],"summary":"Draft a document","operationId":"draftCrmDocument","description":"**Scope:** `crm:write`.\n\nDrafts a document for the record from a template.\n\nCall with a member's OAuth token or MCP key (acts as that person, with their contact visibility). Org API keys get 403 `user_token_required`; they use `/contacts`. Beta: switched on per organization (`v1_crm_parity`); while it is off the endpoint answers as an unknown route. Errors use the `V1Error` body. A CRM check that refuses the request keeps its own `error.code` (for example `invalid_spec`, `contact_exists`) and any details it returns (missing stage checks, rejected filters) in `error.details`.\n\n**MCP tool:** `crm_draft_document`. **Rate limit:** 30 writes per minute per credential, shared with the same credential's other CRM writes.","parameters":[{"name":"id","in":"path","required":true,"description":"The record id, or its phone number in E.164.","schema":{"type":"string"}},{"$ref":"#/components/parameters/IdempotencyKey"}],"requestBody":{"required":true,"content":{"application/json":{"schema":{"type":"object","additionalProperties":false,"properties":{"template_id":{"type":"string"}},"required":["template_id"]}}}},"responses":{"202":{"description":"Drafting started; the draft appears on the record when done.","content":{"application/json":{"schema":{"type":"object","additionalProperties":true}}}},"400":{"description":"The request is not valid (`invalid_request`, `org_conflict`).","content":{"application/json":{"schema":{"allOf":[{"$ref":"#/components/schemas/V1Error"},{"type":"object","properties":{"error":{"type":"object","properties":{"code":{"type":"string","examples":["invalid_request","org_conflict"]}}}}}]}}}},"401":{"$ref":"#/components/responses/Unauthorized"},"403":{"description":"The credential lacks the scope (`insufficient_scope`), is an org API key (`user_token_required`), or the member may not see this (`forbidden`).","content":{"application/json":{"schema":{"allOf":[{"$ref":"#/components/schemas/V1Error"},{"type":"object","properties":{"error":{"type":"object","properties":{"code":{"type":"string","examples":["insufficient_scope","user_token_required","forbidden"]}}}}}]}}}},"404":{"description":"No such record visible to this member (`not_found`).","content":{"application/json":{"schema":{"allOf":[{"$ref":"#/components/schemas/V1Error"},{"type":"object","properties":{"error":{"type":"object","properties":{"code":{"type":"string","examples":["not_found"]}}}}}]}}}},"429":{"$ref":"#/components/responses/RateLimited"},"500":{"$ref":"#/components/responses/InternalError"},"503":{"description":"The organization settings could not be checked (`unavailable`). Safe to retry.","content":{"application/json":{"schema":{"allOf":[{"$ref":"#/components/schemas/V1Error"},{"type":"object","properties":{"error":{"type":"object","properties":{"code":{"type":"string","examples":["unavailable"]}}}}}]}}}}},"x-required-scope":"crm:write","x-mcp-tool":"crm_draft_document"},"parameters":[{"$ref":"#/components/parameters/FinnVersion"}]},"/crm/records/{id}/stage_moves":{"post":{"tags":["CRM"],"summary":"Move a record to another stage","operationId":"moveCrmRecordStage","description":"**Scope:** `crm:stage`.\n\nMoves the record to `stage` (with `closeReason` and `substatus` when it closes). Every stage gate still applies. Held for the member to approve: answers 202 `pending_approval` with an `approval_id` (poll `GET /approvals/{id}`); once approved it runs as sent. Needs the organization's API approvals (`v1_hold_for_approval`); without them a member token gets 403 `approval_unavailable`.\n\nCall with a member's OAuth token or MCP key (acts as that person, with their contact visibility). Org API keys get 403 `user_token_required`; they use `/contacts`. Beta: switched on per organization (`v1_crm_parity`); while it is off the endpoint answers as an unknown route. Errors use the `V1Error` body. A CRM check that refuses the request keeps its own `error.code` (for example `invalid_spec`, `contact_exists`) and any details it returns (missing stage checks, rejected filters) in `error.details`.\n\n**MCP tool:** `crm_move_stage`. **Rate limit:** 30 writes per minute per credential, shared with the same credential's other CRM writes.","parameters":[{"name":"id","in":"path","required":true,"description":"The record id, or its phone number in E.164.","schema":{"type":"string"}},{"$ref":"#/components/parameters/IdempotencyKey"}],"requestBody":{"required":true,"content":{"application/json":{"schema":{"type":"object","additionalProperties":false,"properties":{"stage":{"type":"string"},"closeReason":{"type":"string"},"substatus":{"type":"string"}},"required":["stage"]}}}},"responses":{"200":{"description":"The record, moved (an approval the organization relaxed runs at once).","content":{"application/json":{"schema":{"type":"object","additionalProperties":true}}}},"202":{"description":"Held for a person to approve (`pending_approval`). Poll `GET /approvals/{id}`.","content":{"application/json":{"schema":{"$ref":"#/components/schemas/PendingApproval"}}}},"400":{"description":"The request is not valid (`invalid_request`, `org_conflict`).","content":{"application/json":{"schema":{"allOf":[{"$ref":"#/components/schemas/V1Error"},{"type":"object","properties":{"error":{"type":"object","properties":{"code":{"type":"string","examples":["invalid_request","org_conflict"]}}}}}]}}}},"401":{"$ref":"#/components/responses/Unauthorized"},"403":{"description":"The credential lacks the scope (`insufficient_scope`), is an org API key (`user_token_required`), or the member may not see this (`forbidden`).","content":{"application/json":{"schema":{"allOf":[{"$ref":"#/components/schemas/V1Error"},{"type":"object","properties":{"error":{"type":"object","properties":{"code":{"type":"string","examples":["insufficient_scope","user_token_required","forbidden"]}}}}}]}}}},"404":{"description":"No such record visible to this member (`not_found`).","content":{"application/json":{"schema":{"allOf":[{"$ref":"#/components/schemas/V1Error"},{"type":"object","properties":{"error":{"type":"object","properties":{"code":{"type":"string","examples":["not_found"]}}}}}]}}}},"429":{"$ref":"#/components/responses/RateLimited"},"500":{"$ref":"#/components/responses/InternalError"},"503":{"description":"The organization settings could not be checked (`unavailable`). Safe to retry.","content":{"application/json":{"schema":{"allOf":[{"$ref":"#/components/schemas/V1Error"},{"type":"object","properties":{"error":{"type":"object","properties":{"code":{"type":"string","examples":["unavailable"]}}}}}]}}}}},"x-required-scope":"crm:stage","x-mcp-tool":"crm_move_stage","x-finn-hold":{"action":"contact.stage_move","class":"outward"}},"parameters":[{"$ref":"#/components/parameters/FinnVersion"}]},"/crm/records/{id}/forms/{form_id}/submissions":{"post":{"tags":["CRM"],"summary":"Submit a form","operationId":"submitCrmForm","description":"**Scope:** `crm:stage`.\n\nSubmits a form on the record as staff, with JSON answers (no files). Held for the member to approve: answers 202 `pending_approval` with an `approval_id` (poll `GET /approvals/{id}`); once approved it runs as sent. Needs the organization's API approvals (`v1_hold_for_approval`); without them a member token gets 403 `approval_unavailable`.\n\nCall with a member's OAuth token or MCP key (acts as that person, with their contact visibility). Org API keys get 403 `user_token_required`; they use `/contacts`. Beta: switched on per organization (`v1_crm_parity`); while it is off the endpoint answers as an unknown route. Errors use the `V1Error` body. A CRM check that refuses the request keeps its own `error.code` (for example `invalid_spec`, `contact_exists`) and any details it returns (missing stage checks, rejected filters) in `error.details`.\n\n**MCP tool:** `crm_submit_form`. **Rate limit:** 30 writes per minute per credential, shared with the same credential's other CRM writes.","parameters":[{"name":"id","in":"path","required":true,"description":"The record id, or its phone number in E.164.","schema":{"type":"string"}},{"name":"form_id","in":"path","required":true,"description":"The form id.","schema":{"type":"string"}},{"$ref":"#/components/parameters/IdempotencyKey"}],"requestBody":{"required":true,"content":{"application/json":{"schema":{"type":"object","additionalProperties":false,"properties":{"answers":{"type":"object","additionalProperties":true},"links":{"type":"array","items":{"type":"object","additionalProperties":true}}},"required":["answers"]}}}},"responses":{"201":{"description":"The submission.","content":{"application/json":{"schema":{"type":"object","additionalProperties":true}}}},"202":{"description":"Held for a person to approve (`pending_approval`). Poll `GET /approvals/{id}`.","content":{"application/json":{"schema":{"$ref":"#/components/schemas/PendingApproval"}}}},"400":{"description":"The request is not valid (`invalid_request`, `org_conflict`).","content":{"application/json":{"schema":{"allOf":[{"$ref":"#/components/schemas/V1Error"},{"type":"object","properties":{"error":{"type":"object","properties":{"code":{"type":"string","examples":["invalid_request","org_conflict"]}}}}}]}}}},"401":{"$ref":"#/components/responses/Unauthorized"},"403":{"description":"The credential lacks the scope (`insufficient_scope`), is an org API key (`user_token_required`), or the member may not see this (`forbidden`).","content":{"application/json":{"schema":{"allOf":[{"$ref":"#/components/schemas/V1Error"},{"type":"object","properties":{"error":{"type":"object","properties":{"code":{"type":"string","examples":["insufficient_scope","user_token_required","forbidden"]}}}}}]}}}},"404":{"description":"No such record visible to this member (`not_found`).","content":{"application/json":{"schema":{"allOf":[{"$ref":"#/components/schemas/V1Error"},{"type":"object","properties":{"error":{"type":"object","properties":{"code":{"type":"string","examples":["not_found"]}}}}}]}}}},"415":{"description":"Send the answers as JSON (`json_only_for_mcp`).","content":{"application/json":{"schema":{"allOf":[{"$ref":"#/components/schemas/V1Error"},{"type":"object","properties":{"error":{"type":"object","properties":{"code":{"type":"string","examples":["json_only_for_mcp"]}}}}}]}}}},"429":{"$ref":"#/components/responses/RateLimited"},"500":{"$ref":"#/components/responses/InternalError"},"503":{"description":"The organization settings could not be checked (`unavailable`). Safe to retry.","content":{"application/json":{"schema":{"allOf":[{"$ref":"#/components/schemas/V1Error"},{"type":"object","properties":{"error":{"type":"object","properties":{"code":{"type":"string","examples":["unavailable"]}}}}}]}}}}},"x-required-scope":"crm:stage","x-mcp-tool":"crm_submit_form","x-finn-hold":{"action":"contact.form_submit","class":"outward"}},"parameters":[{"$ref":"#/components/parameters/FinnVersion"}]},"/crm/records/{id}/forms/{form_id}/links":{"post":{"tags":["CRM"],"summary":"Share a form link","operationId":"createCrmFormLink","description":"**Scope:** `crm:share`.\n\nA link someone outside the organization can fill the form with, valid for `expires_in_days` (1 to 30). Held for the member to approve: answers 202 `pending_approval` with an `approval_id` (poll `GET /approvals/{id}`); once approved it runs as sent. Needs the organization's API approvals (`v1_hold_for_approval`); without them a member token gets 403 `approval_unavailable`.\n\nCall with a member's OAuth token or MCP key (acts as that person, with their contact visibility). Org API keys get 403 `user_token_required`; they use `/contacts`. Beta: switched on per organization (`v1_crm_parity`); while it is off the endpoint answers as an unknown route. Errors use the `V1Error` body. A CRM check that refuses the request keeps its own `error.code` (for example `invalid_spec`, `contact_exists`) and any details it returns (missing stage checks, rejected filters) in `error.details`.\n\n**MCP tool:** `crm_create_outside_party_link`. **Rate limit:** 30 writes per minute per credential, shared with the same credential's other CRM writes.","parameters":[{"name":"id","in":"path","required":true,"description":"The record id, or its phone number in E.164.","schema":{"type":"string"}},{"name":"form_id","in":"path","required":true,"description":"The form id.","schema":{"type":"string"}},{"$ref":"#/components/parameters/IdempotencyKey"}],"requestBody":{"required":true,"content":{"application/json":{"schema":{"type":"object","additionalProperties":false,"properties":{"expires_in_days":{"type":"integer","minimum":1,"maximum":30},"recipient_label":{"type":"string"},"recipient_phone":{"type":"string"}},"required":["expires_in_days"]}}}},"responses":{"201":{"description":"The link.","content":{"application/json":{"schema":{"type":"object","additionalProperties":true}}}},"202":{"description":"Held for a person to approve (`pending_approval`). Poll `GET /approvals/{id}`.","content":{"application/json":{"schema":{"$ref":"#/components/schemas/PendingApproval"}}}},"400":{"description":"The request is not valid (`invalid_request`, `org_conflict`).","content":{"application/json":{"schema":{"allOf":[{"$ref":"#/components/schemas/V1Error"},{"type":"object","properties":{"error":{"type":"object","properties":{"code":{"type":"string","examples":["invalid_request","org_conflict"]}}}}}]}}}},"401":{"$ref":"#/components/responses/Unauthorized"},"403":{"description":"The credential lacks the scope (`insufficient_scope`), is an org API key (`user_token_required`), or the member may not see this (`forbidden`).","content":{"application/json":{"schema":{"allOf":[{"$ref":"#/components/schemas/V1Error"},{"type":"object","properties":{"error":{"type":"object","properties":{"code":{"type":"string","examples":["insufficient_scope","user_token_required","forbidden"]}}}}}]}}}},"404":{"description":"No such record visible to this member (`not_found`).","content":{"application/json":{"schema":{"allOf":[{"$ref":"#/components/schemas/V1Error"},{"type":"object","properties":{"error":{"type":"object","properties":{"code":{"type":"string","examples":["not_found"]}}}}}]}}}},"429":{"$ref":"#/components/responses/RateLimited"},"500":{"$ref":"#/components/responses/InternalError"},"503":{"description":"The organization settings could not be checked (`unavailable`). Safe to retry.","content":{"application/json":{"schema":{"allOf":[{"$ref":"#/components/schemas/V1Error"},{"type":"object","properties":{"error":{"type":"object","properties":{"code":{"type":"string","examples":["unavailable"]}}}}}]}}}}},"x-required-scope":"crm:share","x-mcp-tool":"crm_create_outside_party_link","x-finn-hold":{"action":"contact.form_link","class":"outward"}},"parameters":[{"$ref":"#/components/parameters/FinnVersion"}]},"/crm/records/{id}/upload_links":{"post":{"tags":["CRM"],"summary":"Share an upload link","operationId":"createCrmUploadLink","description":"**Scope:** `crm:share`.\n\nA link someone outside the organization can upload documents to the record with, valid for `expires_in_days` (1 to 30). Held for the member to approve: answers 202 `pending_approval` with an `approval_id` (poll `GET /approvals/{id}`); once approved it runs as sent. Needs the organization's API approvals (`v1_hold_for_approval`); without them a member token gets 403 `approval_unavailable`.\n\nCall with a member's OAuth token or MCP key (acts as that person, with their contact visibility). Org API keys get 403 `user_token_required`; they use `/contacts`. Beta: switched on per organization (`v1_crm_parity`); while it is off the endpoint answers as an unknown route. Errors use the `V1Error` body. A CRM check that refuses the request keeps its own `error.code` (for example `invalid_spec`, `contact_exists`) and any details it returns (missing stage checks, rejected filters) in `error.details`.\n\n**MCP tool:** `crm_create_outside_party_link`. **Rate limit:** 30 writes per minute per credential, shared with the same credential's other CRM writes.","parameters":[{"name":"id","in":"path","required":true,"description":"The record id, or its phone number in E.164.","schema":{"type":"string"}},{"$ref":"#/components/parameters/IdempotencyKey"}],"requestBody":{"required":true,"content":{"application/json":{"schema":{"type":"object","additionalProperties":false,"properties":{"expires_in_days":{"type":"integer","minimum":1,"maximum":30},"recipient_label":{"type":"string"},"recipient_phone":{"type":"string"}},"required":["expires_in_days"]}}}},"responses":{"201":{"description":"The link.","content":{"application/json":{"schema":{"type":"object","additionalProperties":true}}}},"202":{"description":"Held for a person to approve (`pending_approval`). Poll `GET /approvals/{id}`.","content":{"application/json":{"schema":{"$ref":"#/components/schemas/PendingApproval"}}}},"400":{"description":"The request is not valid (`invalid_request`, `org_conflict`).","content":{"application/json":{"schema":{"allOf":[{"$ref":"#/components/schemas/V1Error"},{"type":"object","properties":{"error":{"type":"object","properties":{"code":{"type":"string","examples":["invalid_request","org_conflict"]}}}}}]}}}},"401":{"$ref":"#/components/responses/Unauthorized"},"403":{"description":"The credential lacks the scope (`insufficient_scope`), is an org API key (`user_token_required`), or the member may not see this (`forbidden`).","content":{"application/json":{"schema":{"allOf":[{"$ref":"#/components/schemas/V1Error"},{"type":"object","properties":{"error":{"type":"object","properties":{"code":{"type":"string","examples":["insufficient_scope","user_token_required","forbidden"]}}}}}]}}}},"404":{"description":"No such record visible to this member (`not_found`).","content":{"application/json":{"schema":{"allOf":[{"$ref":"#/components/schemas/V1Error"},{"type":"object","properties":{"error":{"type":"object","properties":{"code":{"type":"string","examples":["not_found"]}}}}}]}}}},"429":{"$ref":"#/components/responses/RateLimited"},"500":{"$ref":"#/components/responses/InternalError"},"503":{"description":"The organization settings could not be checked (`unavailable`). Safe to retry.","content":{"application/json":{"schema":{"allOf":[{"$ref":"#/components/schemas/V1Error"},{"type":"object","properties":{"error":{"type":"object","properties":{"code":{"type":"string","examples":["unavailable"]}}}}}]}}}}},"x-required-scope":"crm:share","x-mcp-tool":"crm_create_outside_party_link","x-finn-hold":{"action":"contact.upload_link","class":"outward"}},"parameters":[{"$ref":"#/components/parameters/FinnVersion"}]},"/crm/imports/columns":{"post":{"tags":["CRM"],"summary":"Map import columns","operationId":"guessCrmImportColumns","description":"**Scope:** `crm:import`.\n\nSuggests which CRM field each CSV header fills.\n\nCall with a member's OAuth token or MCP key (acts as that person, with their contact visibility). Org API keys get 403 `user_token_required`; they use `/contacts`. Beta: switched on per organization (`v1_crm_parity`); while it is off the endpoint answers as an unknown route. Errors use the `V1Error` body. A CRM check that refuses the request keeps its own `error.code` (for example `invalid_spec`, `contact_exists`) and any details it returns (missing stage checks, rejected filters) in `error.details`.\n\n**MCP tool:** `crm_import_csv`. **Rate limit:** 30 writes per minute per credential, shared with the same credential's other CRM writes.","parameters":[{"$ref":"#/components/parameters/IdempotencyKey"}],"requestBody":{"required":true,"content":{"application/json":{"schema":{"type":"object","additionalProperties":false,"properties":{"headers":{"type":"array","items":{"type":"string"}},"kind":{"type":"string"}},"required":["headers"]}}}},"responses":{"200":{"description":"The suggested mapping.","content":{"application/json":{"schema":{"type":"object","additionalProperties":true}}}},"400":{"description":"The request is not valid (`invalid_request`, `org_conflict`).","content":{"application/json":{"schema":{"allOf":[{"$ref":"#/components/schemas/V1Error"},{"type":"object","properties":{"error":{"type":"object","properties":{"code":{"type":"string","examples":["invalid_request","org_conflict"]}}}}}]}}}},"401":{"$ref":"#/components/responses/Unauthorized"},"403":{"description":"The credential lacks the scope (`insufficient_scope`), is an org API key (`user_token_required`), or the member may not see this (`forbidden`).","content":{"application/json":{"schema":{"allOf":[{"$ref":"#/components/schemas/V1Error"},{"type":"object","properties":{"error":{"type":"object","properties":{"code":{"type":"string","examples":["insufficient_scope","user_token_required","forbidden"]}}}}}]}}}},"429":{"$ref":"#/components/responses/RateLimited"},"500":{"$ref":"#/components/responses/InternalError"},"503":{"description":"The organization settings could not be checked (`unavailable`). Safe to retry.","content":{"application/json":{"schema":{"allOf":[{"$ref":"#/components/schemas/V1Error"},{"type":"object","properties":{"error":{"type":"object","properties":{"code":{"type":"string","examples":["unavailable"]}}}}}]}}}}},"x-required-scope":"crm:import","x-mcp-tool":"crm_import_csv"},"parameters":[{"$ref":"#/components/parameters/FinnVersion"}]},"/crm/imports":{"post":{"tags":["CRM"],"summary":"Import records","operationId":"importCrmRecords","description":"**Scope:** `crm:import`.\n\nImports up to 500 rows. With `dry_run` (the default) nothing is written and the answer previews the result at once; a real run (`dry_run: false`) is held. Held for the member to approve: answers 202 `pending_approval` with an `approval_id` (poll `GET /approvals/{id}`); once approved it runs as sent. Needs the organization's API approvals (`v1_hold_for_approval`); without them a member token gets 403 `approval_unavailable`.\n\nCall with a member's OAuth token or MCP key (acts as that person, with their contact visibility). Org API keys get 403 `user_token_required`; they use `/contacts`. Beta: switched on per organization (`v1_crm_parity`); while it is off the endpoint answers as an unknown route. Errors use the `V1Error` body. A CRM check that refuses the request keeps its own `error.code` (for example `invalid_spec`, `contact_exists`) and any details it returns (missing stage checks, rejected filters) in `error.details`.\n\n**MCP tool:** `crm_import_csv`. **Rate limit:** 30 writes per minute per credential, shared with the same credential's other CRM writes.","parameters":[{"$ref":"#/components/parameters/IdempotencyKey"}],"requestBody":{"required":true,"content":{"application/json":{"schema":{"type":"object","additionalProperties":false,"properties":{"rows":{"type":"array","maxItems":500,"items":{"type":"object","additionalProperties":true}},"mapping":{"type":"object","additionalProperties":true},"dry_run":{"type":"boolean"},"first_row":{"type":"integer"}},"required":["rows","mapping"]}}}},"responses":{"200":{"description":"The preview or the import result.","content":{"application/json":{"schema":{"type":"object","additionalProperties":true}}}},"202":{"description":"Held for a person to approve (`pending_approval`). Poll `GET /approvals/{id}`.","content":{"application/json":{"schema":{"$ref":"#/components/schemas/PendingApproval"}}}},"400":{"description":"The request is not valid (`invalid_request`, `org_conflict`).","content":{"application/json":{"schema":{"allOf":[{"$ref":"#/components/schemas/V1Error"},{"type":"object","properties":{"error":{"type":"object","properties":{"code":{"type":"string","examples":["invalid_request","org_conflict"]}}}}}]}}}},"401":{"$ref":"#/components/responses/Unauthorized"},"403":{"description":"The credential lacks the scope (`insufficient_scope`), is an org API key (`user_token_required`), or the member may not see this (`forbidden`).","content":{"application/json":{"schema":{"allOf":[{"$ref":"#/components/schemas/V1Error"},{"type":"object","properties":{"error":{"type":"object","properties":{"code":{"type":"string","examples":["insufficient_scope","user_token_required","forbidden"]}}}}}]}}}},"429":{"$ref":"#/components/responses/RateLimited"},"500":{"$ref":"#/components/responses/InternalError"},"503":{"description":"The organization settings could not be checked (`unavailable`). Safe to retry.","content":{"application/json":{"schema":{"allOf":[{"$ref":"#/components/schemas/V1Error"},{"type":"object","properties":{"error":{"type":"object","properties":{"code":{"type":"string","examples":["unavailable"]}}}}}]}}}}},"x-required-scope":"crm:import","x-mcp-tool":"crm_import_csv","x-finn-hold":{"action":"contact.import","class":"outward"}},"parameters":[{"$ref":"#/components/parameters/FinnVersion"}]},"/webhook_endpoints/{id}/test":{"post":{"tags":["Webhooks"],"operationId":"testWebhookEndpoint","summary":"Send a test event","description":"**Scope:** `admin:webhooks`. **Needs** webhook endpoints by API and webhook test, redeliver and rotation turned on for your organization (ask Finn support).\n\nSends one signed `webhook.test` event to the endpoint now, one attempt, and answers what happened. Use it to check your URL and your signature check. Nothing is recorded: no delivery is listed, and the endpoint's `failure_count` and `last_success_at` stay as they are, so a failed test never switches an endpoint off. Sent to a switched-off endpoint too.\n\nThe body is `{ event: \"webhook.test\", event_id, created_at, org_id, test: true, data: { endpoint_id, message } }`, signed like every event (`X-Finn-Signature`; both secrets during a rotation). A failed send is still `200`, with `delivered: false`.\n\n**Rate limit:** 30 test sends, redeliveries and rotations per minute per API key.","x-required-scope":"admin:webhooks","parameters":[{"name":"id","in":"path","required":true,"description":"The webhook endpoint's id.","schema":{"type":"string","format":"uuid"}}],"responses":{"200":{"description":"What happened.","headers":{"RateLimit-Policy":{"$ref":"#/components/headers/RateLimitPolicy"},"RateLimit-Limit":{"$ref":"#/components/headers/RateLimitLimit"},"RateLimit-Remaining":{"$ref":"#/components/headers/RateLimitRemaining"},"RateLimit-Reset":{"$ref":"#/components/headers/RateLimitReset"}},"content":{"application/json":{"schema":{"$ref":"#/components/schemas/WebhookTestResult"}}}},"401":{"description":"Missing or invalid API key (`api_key_required`, `invalid_api_key`).","content":{"application/json":{"schema":{"$ref":"#/components/schemas/V1Error"}}}},"403":{"description":"`insufficient_scope` (the key lacks `admin:webhooks`), or `webhooks_api_not_enabled` when webhook endpoints by API are not turned on for your organization.","content":{"application/json":{"schema":{"$ref":"#/components/schemas/V1Error"}}}},"404":{"description":"No such webhook endpoint in your organization (`webhook_endpoint_not_found`), or `not_found` when webhook test, redeliver and rotation are not turned on for your organization.","content":{"application/json":{"schema":{"$ref":"#/components/schemas/V1Error"}}}},"429":{"description":"Too many requests (`rate_limited`). Retry after the `Retry-After` header.","content":{"application/json":{"schema":{"$ref":"#/components/schemas/V1Error"}}}},"503":{"description":"Settings could not be read (`unavailable`). Retry shortly.","content":{"application/json":{"schema":{"$ref":"#/components/schemas/V1Error"}}}}}},"parameters":[{"$ref":"#/components/parameters/FinnOrg"},{"$ref":"#/components/parameters/FinnVersion"}]},"/webhook_endpoints/{id}/deliveries/{delivery_id}/redeliver":{"post":{"tags":["Webhooks"],"operationId":"redeliverWebhookDelivery","summary":"Redeliver an event","description":"**Scope:** `admin:webhooks`. **Needs** webhook endpoints by API and webhook test, redeliver and rotation turned on for your organization (ask Finn support).\n\nSends a delivered or failed event to this endpoint again. A new delivery is queued (`202`, `status: pending`) and sent, usually within 15 seconds, signed and retried like any event; read its outcome with `GET /webhook_endpoints/{id}/deliveries`. The body carries the same `event_id` as the first time, so if you de-duplicate on `event_id`, remove the event from your store first. A record event carries the record as it is when sent; a `call.completed` gets a fresh `recording_url`.\n\nNot possible for a `call.completed` sent the in-process way (`event_id` is `null`): its body was not kept.\n\n**Rate limit:** 30 test sends, redeliveries and rotations per minute per API key.","x-required-scope":"admin:webhooks","parameters":[{"name":"id","in":"path","required":true,"description":"The webhook endpoint's id.","schema":{"type":"string","format":"uuid"}},{"name":"delivery_id","in":"path","required":true,"description":"The delivery's id, from `GET /webhook_endpoints/{id}/deliveries`.","schema":{"type":"string","format":"uuid"}}],"responses":{"202":{"description":"The new delivery, queued.","headers":{"RateLimit-Policy":{"$ref":"#/components/headers/RateLimitPolicy"},"RateLimit-Limit":{"$ref":"#/components/headers/RateLimitLimit"},"RateLimit-Remaining":{"$ref":"#/components/headers/RateLimitRemaining"},"RateLimit-Reset":{"$ref":"#/components/headers/RateLimitReset"}},"content":{"application/json":{"schema":{"$ref":"#/components/schemas/WebhookDelivery"}}}},"401":{"description":"Missing or invalid API key (`api_key_required`, `invalid_api_key`).","content":{"application/json":{"schema":{"$ref":"#/components/schemas/V1Error"}}}},"403":{"description":"`insufficient_scope` (the key lacks `admin:webhooks`), or `webhooks_api_not_enabled` when webhook endpoints by API are not turned on for your organization.","content":{"application/json":{"schema":{"$ref":"#/components/schemas/V1Error"}}}},"404":{"description":"No such webhook endpoint (`webhook_endpoint_not_found`) or delivery (`webhook_delivery_not_found`) in your organization, or `not_found` when webhook test, redeliver and rotation are not turned on for your organization.","content":{"application/json":{"schema":{"$ref":"#/components/schemas/V1Error"}}}},"409":{"description":"`delivery_not_redeliverable` (a `call.completed` sent the in-process way), `delivery_pending` (still waiting to be sent or retried), or `webhook_endpoint_disabled` (switch the endpoint on first).","content":{"application/json":{"schema":{"$ref":"#/components/schemas/V1Error"}}}},"429":{"description":"Too many requests (`rate_limited`). Retry after the `Retry-After` header.","content":{"application/json":{"schema":{"$ref":"#/components/schemas/V1Error"}}}},"503":{"description":"Settings could not be read (`unavailable`). Retry shortly.","content":{"application/json":{"schema":{"$ref":"#/components/schemas/V1Error"}}}}}},"parameters":[{"$ref":"#/components/parameters/FinnOrg"},{"$ref":"#/components/parameters/FinnVersion"}]},"/webhook_endpoints/{id}/rotate_secret":{"post":{"tags":["Webhooks"],"operationId":"rotateWebhookEndpointSecret","summary":"Rotate the signing secret","description":"**Scope:** `admin:webhooks`. **Needs** webhook endpoints by API and webhook test, redeliver and rotation turned on for your organization (ask Finn support).\n\nMakes a new signing secret and returns it. It is shown only in this response: store it now. For 24 hours (`previous_secret_expires_at`) every event is signed with both secrets: `X-Finn-Signature` is `t=<unix>,v1=<new>,v1=<old>`. Accept a request when any `v1` entry matches, switch your receiver to the new secret, and the old one stops signing by itself. Rotating again within the 24 hours keeps only the secret just replaced signing; the one before it stops at once.\n\nNot idempotent: a retry makes another secret. If a response was lost, rotate again and use that secret.\n\n**Rate limit:** 30 test sends, redeliveries and rotations per minute per API key.","x-required-scope":"admin:webhooks","parameters":[{"name":"id","in":"path","required":true,"description":"The webhook endpoint's id.","schema":{"type":"string","format":"uuid"}}],"responses":{"200":{"description":"The endpoint, with its new secret.","headers":{"RateLimit-Policy":{"$ref":"#/components/headers/RateLimitPolicy"},"RateLimit-Limit":{"$ref":"#/components/headers/RateLimitLimit"},"RateLimit-Remaining":{"$ref":"#/components/headers/RateLimitRemaining"},"RateLimit-Reset":{"$ref":"#/components/headers/RateLimitReset"}},"content":{"application/json":{"schema":{"$ref":"#/components/schemas/WebhookEndpointRotated"}}}},"401":{"description":"Missing or invalid API key (`api_key_required`, `invalid_api_key`).","content":{"application/json":{"schema":{"$ref":"#/components/schemas/V1Error"}}}},"403":{"description":"`insufficient_scope` (the key lacks `admin:webhooks`), or `webhooks_api_not_enabled` when webhook endpoints by API are not turned on for your organization.","content":{"application/json":{"schema":{"$ref":"#/components/schemas/V1Error"}}}},"404":{"description":"No such webhook endpoint in your organization (`webhook_endpoint_not_found`), or `not_found` when webhook test, redeliver and rotation are not turned on for your organization.","content":{"application/json":{"schema":{"$ref":"#/components/schemas/V1Error"}}}},"429":{"description":"Too many requests (`rate_limited`). Retry after the `Retry-After` header.","content":{"application/json":{"schema":{"$ref":"#/components/schemas/V1Error"}}}},"503":{"description":"Settings could not be read (`unavailable`). Retry shortly.","content":{"application/json":{"schema":{"$ref":"#/components/schemas/V1Error"}}}}}},"parameters":[{"$ref":"#/components/parameters/FinnOrg"},{"$ref":"#/components/parameters/FinnVersion"}]},"/dialer/follow-ups/{call_id}/done":{"parameters":[{"$ref":"#/components/parameters/FinnVersion"}],"post":{"tags":["Dialer"],"operationId":"markDialerFollowUpDone","summary":"Mark a dialer follow-up done","description":"**Scope:** `dialer:write`.\n\nMarks the call's follow-up done; it leaves the follow-up list. Answers `{callId, state: \"done\"}`.\n\n**Off unless** your organization has dialer writes switched on (registry flag `v1_dialer_writes`, off by default; ask Finn support): until then this answers `404 not_found`. Only calls that have ended: a live call's wrap-up stays with the rep (`409 illegal_state`). An org key reaches the whole team's calls; a member's OAuth token or MCP key reaches the member's own calls, or the team's for an admin or supervisor. Not held for approval: these are internal records, like CRM notes. Send an `Idempotency-Key` so a retry does not write twice.\n\n**Rate limit:** 60 writes per minute per API key across the Dialer API.","parameters":[{"name":"call_id","in":"path","required":true,"description":"The call's id (a line id from GET /dialer/sessions/{id}).","schema":{"type":"string","pattern":"^[0-9a-fA-F]{8}-[0-9a-fA-F]{4}-[0-9a-fA-F]{4}-[0-9a-fA-F]{4}-[0-9a-fA-F]{12}$"}},{"$ref":"#/components/parameters/IdempotencyKey"}],"responses":{"200":{"description":"OK.","content":{"application/json":{"schema":{"type":"object","additionalProperties":true}}}},"400":{"description":"The request is not valid (`invalid_request` / `validation_failed`, with `fields`).","content":{"application/json":{"schema":{"allOf":[{"$ref":"#/components/schemas/V1Error"},{"type":"object","properties":{"error":{"type":"object","properties":{"code":{"type":"string","enum":["invalid_request","validation_failed"]}}}}}]}}}},"401":{"description":"Missing, unknown or revoked credentials.","content":{"application/json":{"schema":{"allOf":[{"$ref":"#/components/schemas/V1Error"},{"type":"object","properties":{"error":{"type":"object","properties":{"code":{"type":"string","enum":["api_key_required","invalid_api_key","unauthorized","key_expired"]}}}}}]}}}},"403":{"description":"The key lacks the scope (`insufficient_scope`), the credential cannot use this endpoint (`credential_not_supported`), or the dialer refused (`forbidden`).","content":{"application/json":{"schema":{"allOf":[{"$ref":"#/components/schemas/V1Error"},{"type":"object","properties":{"error":{"type":"object","properties":{"code":{"type":"string","enum":["insufficient_scope","credential_not_supported","forbidden"]}}}}}]}}}},"404":{"description":"Not found, or dialer writes are not switched on for this organization (registry flags `v1_dialer`, `v1_dialer_writes`): `not_found`, `follow_up_not_found`.","content":{"application/json":{"schema":{"allOf":[{"$ref":"#/components/schemas/V1Error"},{"type":"object","properties":{"error":{"type":"object","properties":{"code":{"type":"string","enum":["not_found","follow_up_not_found"]}}}}}]}}}},"409":{"description":"The call has not ended, or (a note) it has no disposition yet (`illegal_state`).","content":{"application/json":{"schema":{"allOf":[{"$ref":"#/components/schemas/V1Error"},{"type":"object","properties":{"error":{"type":"object","properties":{"code":{"type":"string","enum":["illegal_state"]}}}}}]}}}},"429":{"description":"Too many requests (`rate_limited`). Retry after the `Retry-After` header.","content":{"application/json":{"schema":{"allOf":[{"$ref":"#/components/schemas/V1Error"},{"type":"object","properties":{"error":{"type":"object","properties":{"code":{"type":"string","enum":["rate_limited"]}}}}}]}}}},"503":{"description":"The dialer is briefly unavailable (`unavailable`). Safe to retry.","content":{"application/json":{"schema":{"allOf":[{"$ref":"#/components/schemas/V1Error"},{"type":"object","properties":{"error":{"type":"object","properties":{"code":{"type":"string","enum":["unavailable"]}}}}}]}}}}},"x-required-scope":"dialer:write","x-mcp-tool":"dialer_mark_follow_up_done"}},"/dialer/calls/{call_id}/disposition":{"parameters":[{"$ref":"#/components/parameters/FinnVersion"}],"put":{"tags":["Dialer"],"operationId":"setDialerCallDisposition","summary":"Set a finished call's disposition","description":"**Scope:** `dialer:write`.\n\nSets the call's wrap-up outcome, as if the rep had saved it: analytics, follow-ups, the do-not-call list and Contacts read it. The call's notes are kept. A call already sent to Contacts is sent again with the new outcome. `callback_at` (unix milliseconds, within the next 365 days) goes with `callback` only. Answers the call's disposition (rep-dialer-service's object, camelCase).\n\n**Off unless** your organization has dialer writes switched on (registry flag `v1_dialer_writes`, off by default; ask Finn support): until then this answers `404 not_found`. Only calls that have ended: a live call's wrap-up stays with the rep (`409 illegal_state`). An org key reaches the whole team's calls; a member's OAuth token or MCP key reaches the member's own calls, or the team's for an admin or supervisor. Not held for approval: these are internal records, like CRM notes. Send an `Idempotency-Key` so a retry does not write twice.\n\n**Rate limit:** 60 writes per minute per API key across the Dialer API.","parameters":[{"name":"call_id","in":"path","required":true,"description":"The call's id (a line id from GET /dialer/sessions/{id}).","schema":{"type":"string","pattern":"^[0-9a-fA-F]{8}-[0-9a-fA-F]{4}-[0-9a-fA-F]{4}-[0-9a-fA-F]{4}-[0-9a-fA-F]{12}$"}},{"$ref":"#/components/parameters/IdempotencyKey"}],"requestBody":{"required":true,"content":{"application/json":{"schema":{"type":"object","required":["outcome"],"properties":{"outcome":{"type":"string","enum":["interested","callback","send_info","left_voicemail","wrong_person","not_interested","do_not_call"]},"callback_at":{"type":"integer","description":"Unix milliseconds; required with `callback`, refused otherwise."}}}}}},"responses":{"200":{"description":"OK.","content":{"application/json":{"schema":{"type":"object","additionalProperties":true}}}},"400":{"description":"The request is not valid (`invalid_request` / `validation_failed`, with `fields`).","content":{"application/json":{"schema":{"allOf":[{"$ref":"#/components/schemas/V1Error"},{"type":"object","properties":{"error":{"type":"object","properties":{"code":{"type":"string","enum":["invalid_request","validation_failed"]}}}}}]}}}},"401":{"description":"Missing, unknown or revoked credentials.","content":{"application/json":{"schema":{"allOf":[{"$ref":"#/components/schemas/V1Error"},{"type":"object","properties":{"error":{"type":"object","properties":{"code":{"type":"string","enum":["api_key_required","invalid_api_key","unauthorized","key_expired"]}}}}}]}}}},"403":{"description":"The key lacks the scope (`insufficient_scope`), the credential cannot use this endpoint (`credential_not_supported`), or the dialer refused (`forbidden`).","content":{"application/json":{"schema":{"allOf":[{"$ref":"#/components/schemas/V1Error"},{"type":"object","properties":{"error":{"type":"object","properties":{"code":{"type":"string","enum":["insufficient_scope","credential_not_supported","forbidden"]}}}}}]}}}},"404":{"description":"Not found, or dialer writes are not switched on for this organization (registry flags `v1_dialer`, `v1_dialer_writes`): `not_found`, `call_not_found`.","content":{"application/json":{"schema":{"allOf":[{"$ref":"#/components/schemas/V1Error"},{"type":"object","properties":{"error":{"type":"object","properties":{"code":{"type":"string","enum":["not_found","call_not_found"]}}}}}]}}}},"409":{"description":"The call has not ended, or (a note) it has no disposition yet (`illegal_state`).","content":{"application/json":{"schema":{"allOf":[{"$ref":"#/components/schemas/V1Error"},{"type":"object","properties":{"error":{"type":"object","properties":{"code":{"type":"string","enum":["illegal_state"]}}}}}]}}}},"429":{"description":"Too many requests (`rate_limited`). Retry after the `Retry-After` header.","content":{"application/json":{"schema":{"allOf":[{"$ref":"#/components/schemas/V1Error"},{"type":"object","properties":{"error":{"type":"object","properties":{"code":{"type":"string","enum":["rate_limited"]}}}}}]}}}},"503":{"description":"The dialer is briefly unavailable (`unavailable`). Safe to retry.","content":{"application/json":{"schema":{"allOf":[{"$ref":"#/components/schemas/V1Error"},{"type":"object","properties":{"error":{"type":"object","properties":{"code":{"type":"string","enum":["unavailable"]}}}}}]}}}}},"x-required-scope":"dialer:write","x-mcp-tool":"dialer_set_disposition"}},"/dialer/calls/{call_id}/notes":{"parameters":[{"$ref":"#/components/parameters/FinnVersion"}],"post":{"tags":["Dialer"],"operationId":"addDialerCallNote","summary":"Add a note to a finished call","description":"**Scope:** `dialer:write`.\n\nAppends `note` (1 to 4000 characters) to the call's notes as a new paragraph (10,000 characters in all). The call needs a disposition first. Answers the call's disposition with its notes.\n\n**Off unless** your organization has dialer writes switched on (registry flag `v1_dialer_writes`, off by default; ask Finn support): until then this answers `404 not_found`. Only calls that have ended: a live call's wrap-up stays with the rep (`409 illegal_state`). An org key reaches the whole team's calls; a member's OAuth token or MCP key reaches the member's own calls, or the team's for an admin or supervisor. Not held for approval: these are internal records, like CRM notes. Send an `Idempotency-Key` so a retry does not write twice.\n\n**Rate limit:** 60 writes per minute per API key across the Dialer API.","parameters":[{"name":"call_id","in":"path","required":true,"description":"The call's id (a line id from GET /dialer/sessions/{id}).","schema":{"type":"string","pattern":"^[0-9a-fA-F]{8}-[0-9a-fA-F]{4}-[0-9a-fA-F]{4}-[0-9a-fA-F]{4}-[0-9a-fA-F]{12}$"}},{"$ref":"#/components/parameters/IdempotencyKey"}],"requestBody":{"required":true,"content":{"application/json":{"schema":{"type":"object","required":["note"],"properties":{"note":{"type":"string","minLength":1,"maxLength":4000}}}}}},"responses":{"200":{"description":"OK.","content":{"application/json":{"schema":{"type":"object","additionalProperties":true}}}},"400":{"description":"The request is not valid (`invalid_request` / `validation_failed`, with `fields`).","content":{"application/json":{"schema":{"allOf":[{"$ref":"#/components/schemas/V1Error"},{"type":"object","properties":{"error":{"type":"object","properties":{"code":{"type":"string","enum":["invalid_request","validation_failed"]}}}}}]}}}},"401":{"description":"Missing, unknown or revoked credentials.","content":{"application/json":{"schema":{"allOf":[{"$ref":"#/components/schemas/V1Error"},{"type":"object","properties":{"error":{"type":"object","properties":{"code":{"type":"string","enum":["api_key_required","invalid_api_key","unauthorized","key_expired"]}}}}}]}}}},"403":{"description":"The key lacks the scope (`insufficient_scope`), the credential cannot use this endpoint (`credential_not_supported`), or the dialer refused (`forbidden`).","content":{"application/json":{"schema":{"allOf":[{"$ref":"#/components/schemas/V1Error"},{"type":"object","properties":{"error":{"type":"object","properties":{"code":{"type":"string","enum":["insufficient_scope","credential_not_supported","forbidden"]}}}}}]}}}},"404":{"description":"Not found, or dialer writes are not switched on for this organization (registry flags `v1_dialer`, `v1_dialer_writes`): `not_found`, `call_not_found`.","content":{"application/json":{"schema":{"allOf":[{"$ref":"#/components/schemas/V1Error"},{"type":"object","properties":{"error":{"type":"object","properties":{"code":{"type":"string","enum":["not_found","call_not_found"]}}}}}]}}}},"409":{"description":"The call has not ended, or (a note) it has no disposition yet (`illegal_state`).","content":{"application/json":{"schema":{"allOf":[{"$ref":"#/components/schemas/V1Error"},{"type":"object","properties":{"error":{"type":"object","properties":{"code":{"type":"string","enum":["illegal_state"]}}}}}]}}}},"429":{"description":"Too many requests (`rate_limited`). Retry after the `Retry-After` header.","content":{"application/json":{"schema":{"allOf":[{"$ref":"#/components/schemas/V1Error"},{"type":"object","properties":{"error":{"type":"object","properties":{"code":{"type":"string","enum":["rate_limited"]}}}}}]}}}},"503":{"description":"The dialer is briefly unavailable (`unavailable`). Safe to retry.","content":{"application/json":{"schema":{"allOf":[{"$ref":"#/components/schemas/V1Error"},{"type":"object","properties":{"error":{"type":"object","properties":{"code":{"type":"string","enum":["unavailable"]}}}}}]}}}}},"x-required-scope":"dialer:write","x-mcp-tool":"dialer_add_call_note"}},"/chat/agents/{agent_id}":{"parameters":[{"$ref":"#/components/parameters/FinnOrg"},{"$ref":"#/components/parameters/FinnVersion"}],"get":{"tags":["Chat"],"operationId":"getChatAgent","summary":"Get a chat agent's settings","description":"**Scope:** `chat:read`.\n\nThe agent's own settings (`overrides`: Setup → Agents in Finn), what the agent uses (`effective`: the workspace's Setup with the overrides on top, `timezone` resolved) and the settings `version`. An OAuth token or MCP key may call this.\n\n**Off unless** your organization has chat agent settings switched on (registry flag `v1_chat_agent_writes`, off by default; ask Finn support): until then this answers `404 not_found`. The agent's instructions and first message are the Finn agent's: change them with `PATCH /finns/{finn_id}/draft`.\n\n**Rate limit:** 300 requests per minute per API key across the Chat API.","parameters":[{"name":"agent_id","in":"path","required":true,"description":"The chat agent's Finn id (a UUID).","schema":{"type":"string","pattern":"^[0-9a-fA-F]{8}-[0-9a-fA-F]{4}-[0-9a-fA-F]{4}-[0-9a-fA-F]{4}-[0-9a-fA-F]{12}$"}}],"responses":{"200":{"description":"OK.","content":{"application/json":{"schema":{"type":"object","additionalProperties":true}}}},"400":{"description":"The request is not valid (`invalid_request`, with `fields`), or `cursor` came from another list or other filters (`invalid_cursor`).","content":{"application/json":{"schema":{"allOf":[{"$ref":"#/components/schemas/V1Error"},{"type":"object","properties":{"error":{"type":"object","properties":{"code":{"type":"string","enum":["invalid_request","invalid_cursor"]}}}}}]}}}},"401":{"description":"Missing, unknown or revoked credentials.","content":{"application/json":{"schema":{"allOf":[{"$ref":"#/components/schemas/V1Error"},{"type":"object","properties":{"error":{"type":"object","properties":{"code":{"type":"string","enum":["api_key_required","invalid_api_key","key_expired"]}}}}}]}}}},"403":{"description":"The key lacks the scope (`insufficient_scope`), or the Chat API is not switched on for this organization (`chat_api_not_enabled`).","content":{"application/json":{"schema":{"allOf":[{"$ref":"#/components/schemas/V1Error"},{"type":"object","properties":{"error":{"type":"object","properties":{"code":{"type":"string","enum":["insufficient_scope","chat_api_not_enabled"]}}}}}]}}}},"404":{"description":"No such chat agent in your organization (`agent_not_found`), or chat agent settings are not switched on for it (`not_found`).","content":{"application/json":{"schema":{"allOf":[{"$ref":"#/components/schemas/V1Error"},{"type":"object","properties":{"error":{"type":"object","properties":{"code":{"type":"string","enum":["agent_not_found","not_found"]}}}}}]}}}},"429":{"$ref":"#/components/responses/RateLimited"},"503":{"description":"Chat is briefly unavailable (`unavailable`). Safe to retry; a send retried with the same `Idempotency-Key` is not sent twice.","content":{"application/json":{"schema":{"allOf":[{"$ref":"#/components/schemas/V1Error"},{"type":"object","properties":{"error":{"type":"object","properties":{"code":{"type":"string","enum":["unavailable"]}}}}}]}}}}},"x-required-scope":"chat:read","x-mcp-tool":"chat_get_agent"},"patch":{"tags":["Chat"],"operationId":"updateChatAgent","summary":"Change a chat agent's settings","description":"**Scope:** `chat:write`.\n\nChanges the settings sent: any of `hours`, `handoff`, `quick_replies`, `resolution`, `booking`. A setting sent replaces the agent's own; `null` drops it, so the agent follows the workspace's (for `booking`, no booking). The change is live: the agent uses it on its next reply. Answers the agent as `GET` does.\n\n**Approval:** an org key changes the agent at once unless your organization holds org keys for `chat.agent_update`. A member's OAuth token or MCP key needs an org admin role (`403 forbidden` otherwise), and the change waits for that member to approve it in Finn: the answer is `202` with a `PendingApproval` and nothing has changed yet. Held because the settings change what the agent tells customers, unlike a Finn draft, which waits for someone to publish it. Send an `Idempotency-Key` so a retry returns the same approval instead of asking again.\n\n**Off unless** your organization has chat agent settings switched on (registry flag `v1_chat_agent_writes`, off by default; ask Finn support): until then this answers `404 not_found`. The agent's instructions and first message are the Finn agent's: change them with `PATCH /finns/{finn_id}/draft`.\n\n**Rate limit:** 60 writes per minute per API key, shared by every Chat write.","parameters":[{"name":"agent_id","in":"path","required":true,"description":"The chat agent's Finn id (a UUID).","schema":{"type":"string","pattern":"^[0-9a-fA-F]{8}-[0-9a-fA-F]{4}-[0-9a-fA-F]{4}-[0-9a-fA-F]{4}-[0-9a-fA-F]{12}$"}},{"$ref":"#/components/parameters/IdempotencyKey"}],"requestBody":{"required":true,"content":{"application/json":{"schema":{"type":"object","properties":{"hours":{"type":["object","null"],"description":"Opening hours (time zone and days); null follows the workspace's."},"handoff":{"type":["object","null"],"description":"When the agent hands over to a person (keywords and rules)."},"quick_replies":{"type":["array","object","null"],"description":"Quick replies the agent offers."},"resolution":{"type":["object","null"],"description":"When a conversation counts as resolved."},"booking":{"type":["object","null"],"description":"The booking event type; null means none."}},"additionalProperties":false}}}},"responses":{"200":{"description":"OK.","content":{"application/json":{"schema":{"type":"object","additionalProperties":true}}}},"202":{"$ref":"#/components/responses/PendingApproval"},"400":{"description":"The request is not valid (`invalid_request`, with `fields`), or `cursor` came from another list or other filters (`invalid_cursor`).","content":{"application/json":{"schema":{"allOf":[{"$ref":"#/components/schemas/V1Error"},{"type":"object","properties":{"error":{"type":"object","properties":{"code":{"type":"string","enum":["invalid_request","invalid_cursor"]}}}}}]}}}},"401":{"description":"Missing, unknown or revoked credentials.","content":{"application/json":{"schema":{"allOf":[{"$ref":"#/components/schemas/V1Error"},{"type":"object","properties":{"error":{"type":"object","properties":{"code":{"type":"string","enum":["api_key_required","invalid_api_key","key_expired"]}}}}}]}}}},"403":{"description":"The key lacks the scope (`insufficient_scope`), the Chat API is not switched on (`chat_api_not_enabled`), a member is not an org admin (`forbidden`), or approval holds are off for this organization (`approval_unavailable`).","content":{"application/json":{"schema":{"allOf":[{"$ref":"#/components/schemas/V1Error"},{"type":"object","properties":{"error":{"type":"object","properties":{"code":{"type":"string","enum":["insufficient_scope","chat_api_not_enabled","forbidden","approval_unavailable"]}}}}}]}}}},"404":{"description":"No such chat agent in your organization (`agent_not_found`), or chat agent settings are not switched on for it (`not_found`).","content":{"application/json":{"schema":{"allOf":[{"$ref":"#/components/schemas/V1Error"},{"type":"object","properties":{"error":{"type":"object","properties":{"code":{"type":"string","enum":["agent_not_found","not_found"]}}}}}]}}}},"409":{"description":"The settings changed at the same time (`setup_stale`): read the agent again and retry.","content":{"application/json":{"schema":{"allOf":[{"$ref":"#/components/schemas/V1Error"},{"type":"object","properties":{"error":{"type":"object","properties":{"code":{"type":"string","enum":["setup_stale"]}}}}}]}}}},"423":{"$ref":"#/components/responses/AiAppsPaused"},"429":{"$ref":"#/components/responses/RateLimited"},"503":{"description":"Chat is briefly unavailable (`unavailable`). Safe to retry; a send retried with the same `Idempotency-Key` is not sent twice.","content":{"application/json":{"schema":{"allOf":[{"$ref":"#/components/schemas/V1Error"},{"type":"object","properties":{"error":{"type":"object","properties":{"code":{"type":"string","enum":["unavailable"]}}}}}]}}}}},"x-required-scope":"chat:write","x-mcp-tool":"chat_update_agent","x-finn-hold":{"action":"chat.agent_update","class":"outward"}}},"/chat/agents/{agent_id}/test_turn":{"parameters":[{"$ref":"#/components/parameters/FinnOrg"},{"$ref":"#/components/parameters/FinnVersion"}],"post":{"tags":["Chat"],"operationId":"testChatAgent","summary":"Test a chat agent","description":"**Scope:** `chat:write`.\n\nAsks the agent for its next reply as it would answer a customer, but as a test: nothing is sent to anyone, a booking is simulated, the agent's own HTTP tools are not called and nothing is billed. Send the conversation so far (`history`, at most 50 messages) and the next message (`latest`, at most 4000 characters). Answers `{text, options, handoff, end, booking, tools, turn_id, latency_ms, simulated: true}`. Not held for approval: nothing leaves Finn. At most 30 test turns per organization a minute (`429 rate_limited`).\n\n**Off unless** your organization has chat agent settings switched on (registry flag `v1_chat_agent_writes`, off by default; ask Finn support): until then this answers `404 not_found`. The agent's instructions and first message are the Finn agent's: change them with `PATCH /finns/{finn_id}/draft`.\n\n**Rate limit:** 60 writes per minute per API key, shared by every Chat write.","parameters":[{"name":"agent_id","in":"path","required":true,"description":"The chat agent's Finn id (a UUID).","schema":{"type":"string","pattern":"^[0-9a-fA-F]{8}-[0-9a-fA-F]{4}-[0-9a-fA-F]{4}-[0-9a-fA-F]{4}-[0-9a-fA-F]{12}$"}},{"$ref":"#/components/parameters/IdempotencyKey"}],"requestBody":{"required":true,"content":{"application/json":{"schema":{"type":"object","required":["latest"],"properties":{"latest":{"type":"string","maxLength":4000},"history":{"type":"array","maxItems":50,"items":{"type":"object","required":["role","content"],"properties":{"role":{"type":"string","enum":["user","assistant"]},"content":{"type":"string","maxLength":8000}}}},"channel":{"type":"string","description":"Default `widget`."},"session":{"type":"string"}}}}}},"responses":{"200":{"description":"OK.","content":{"application/json":{"schema":{"type":"object","additionalProperties":true}}}},"400":{"description":"The request is not valid (`invalid_request`, with `fields`), or `cursor` came from another list or other filters (`invalid_cursor`).","content":{"application/json":{"schema":{"allOf":[{"$ref":"#/components/schemas/V1Error"},{"type":"object","properties":{"error":{"type":"object","properties":{"code":{"type":"string","enum":["invalid_request","invalid_cursor"]}}}}}]}}}},"401":{"description":"Missing, unknown or revoked credentials.","content":{"application/json":{"schema":{"allOf":[{"$ref":"#/components/schemas/V1Error"},{"type":"object","properties":{"error":{"type":"object","properties":{"code":{"type":"string","enum":["api_key_required","invalid_api_key","key_expired"]}}}}}]}}}},"403":{"description":"The key lacks the scope (`insufficient_scope`), or the Chat API is not switched on for this organization (`chat_api_not_enabled`).","content":{"application/json":{"schema":{"allOf":[{"$ref":"#/components/schemas/V1Error"},{"type":"object","properties":{"error":{"type":"object","properties":{"code":{"type":"string","enum":["insufficient_scope","chat_api_not_enabled"]}}}}}]}}}},"404":{"description":"No such chat agent in your organization (`agent_not_found`), or chat agent settings are not switched on for it (`not_found`).","content":{"application/json":{"schema":{"allOf":[{"$ref":"#/components/schemas/V1Error"},{"type":"object","properties":{"error":{"type":"object","properties":{"code":{"type":"string","enum":["agent_not_found","not_found"]}}}}}]}}}},"422":{"description":"The runtime refused the turn (`content_filtered`, `validation_failed`).","content":{"application/json":{"schema":{"allOf":[{"$ref":"#/components/schemas/V1Error"},{"type":"object","properties":{"error":{"type":"object","properties":{"code":{"type":"string","enum":["content_filtered","validation_failed"]}}}}}]}}}},"429":{"$ref":"#/components/responses/RateLimited"},"503":{"description":"Chat is briefly unavailable (`unavailable`). Safe to retry; a send retried with the same `Idempotency-Key` is not sent twice.","content":{"application/json":{"schema":{"allOf":[{"$ref":"#/components/schemas/V1Error"},{"type":"object","properties":{"error":{"type":"object","properties":{"code":{"type":"string","enum":["unavailable"]}}}}}]}}}}},"x-required-scope":"chat:write","x-mcp-tool":"chat_test_agent"}}},"webhooks":{"call.completed":{"post":{"operationId":"callCompleted","summary":"call.completed","description":"Sent once per call to each webhook endpoint after the call ends, answered or not: calls from outbound and inbound deployments and calls placed with `POST /calls`. If the Finn has a **Data webhook URL** (set in the dashboard under the Finn's **Deployment settings**), that URL also receives the event, signed with the Finn's own secret.\n\nUnanswered calls, and calls on a Finn without analysis questions, are usually sent within about 5 minutes. For an answered call on a Finn with analysis questions, the event waits for the analysis, so `analysis` is normally filled in; if analysis has not arrived after about an hour, the event is sent with what exists. New endpoints are not backfilled with older calls.\n\n**Verify the signature.** `X-Finn-Signature` is `t=<unix seconds>,v1=<hex>`, where `v1` is the HMAC-SHA256 of `<t>.<raw request body>` keyed with the whole signing secret, including its `whsec_` prefix, as UTF-8. Compute it over the raw body bytes before parsing JSON, compare in constant time, and reject timestamps older than a few minutes. For 24 hours after a secret rotation (`POST /webhook_endpoints/{id}/rotate_secret`) the header carries two `v1` entries, one per secret: accept the request when any of them matches.\n\n**Delivery and retries.** Endpoints must use `https` and resolve to a public address. Each attempt times out after 10 seconds, and redirects are not followed. Any `2xx` response marks the event delivered. Timeouts, connection errors, `429`, `5xx`, `3xx` and responses larger than 64 KB are retried, up to 3 attempts in total (waiting 1 second, then 2 seconds). Other `4xx` responses are not retried. Each endpoint gets at most one `call.completed` per call; after 3 failed attempts the event is not sent again, so reconcile with `GET /calls/{call_uuid}`. De-duplicate on `call_uuid`.\n\n**Durable delivery.** When your organization has durable webhooks switched on (ask Finn support), `call.completed` goes through the same queue as record events: it survives a Finn restart, carries `event_id` (`call.completed:<call_uuid>`), `occurred_at` and `org_id`, goes out in order with the endpoint's other events, and a send that failed with a timeout, connection error, `429` or `5xx` is tried again after 1 min, 5 min, 30 min, 2 h and 6 h. `recording_url` is signed when the event is sent and is valid for one hour. An endpoint with 30 failed sends in a row and no success for a day is switched off (`enabled: false`); `GET /webhook_endpoints/{id}/deliveries` shows what happened.","security":[],"parameters":[{"name":"X-Finn-Signature","in":"header","required":true,"description":"`t=<unix seconds>,v1=<hex HMAC-SHA256 of \"<t>.<raw body>\">`.","schema":{"type":"string"},"example":"t=1759403049,v1=5f8a3c0e9d2b7a41c6e8f0d3b9a2c7e1f4d6b8a0c2e4f6a8b0d2c4e6f8a0b2c4"},{"name":"X-Finn-Event","in":"header","required":true,"description":"The event name.","schema":{"type":"string","const":"call.completed"}},{"name":"User-Agent","in":"header","required":true,"schema":{"type":"string","const":"Finn-Webhooks/1"}}],"requestBody":{"required":true,"content":{"application/json":{"schema":{"$ref":"#/components/schemas/CallCompletedEvent"},"example":{"event":"call.completed","created_at":"2026-10-02T10:44:09.000Z","data":{"call_uuid":"b7e3c9a2-4d1f-4e8b-9a6c-2f5d8e1b3c70","finn_id":"8f14e45f-ceea-467a-9f6a-1c0e5b2a77d1","deployment_id":"0e8a4c71-9b2d-4f36-a5e1-7c3d9b2f6a10","status":"completed","disconnect_reason":"hangup","direction":"outbound","from_number":"+14155550123","to_number":"+14155550142","duration_seconds":97,"user_sentiment":"positive","call_successful":true,"created_at":"2026-10-02T10:42:02.000Z","updated_at":"2026-10-02T10:43:41.000Z","recording_url":"https://storage.example.com/recordings/b7e3c9a2-4d1f-4e8b-9a6c-2f5d8e1b3c70.wav?sig=...","analysis":[{"question_name":"Issue resolved","question_type":"Yes/No","answer":"Yes","needs_review":false,"reasoning":"Caller confirmed the replacement order went through.","extracted_at":"2026-10-02T10:44:10.000Z"}]}}}}},"responses":{"2XX":{"description":"Acknowledge receipt. Respond as soon as the event is stored; work done before responding counts toward the 10-second timeout."}}}},"contact.updated":{"post":{"operationId":"contactUpdated","summary":"contact.updated","description":"A record's name, tags, email, stage, owner, lead status, substatus, close reason or custom fields changed, in Finn or through `PATCH /contacts/{id}`. `data.changed_fields` lists what changed, never the values; `data.contact` is the record when the event is sent. Not sent for an edit that changed nothing, or for a record deleted before sending.\n\nSent to each webhook endpoint that lists this event, usually within a minute, signed and retried like `call.completed` (`X-Finn-Signature`, up to 3 attempts). Events of one endpoint go out in the order they happened. Delivery is at least once: de-duplicate on `event_id`. An event not sent within 24 hours is dropped.","security":[],"parameters":[{"name":"X-Finn-Signature","in":"header","required":true,"description":"`t=<unix seconds>,v1=<hex HMAC-SHA256 of \"<t>.<raw body>\">`.","schema":{"type":"string"},"example":"t=1759403049,v1=5f8a3c0e9d2b7a41c6e8f0d3b9a2c7e1f4d6b8a0c2e4f6a8b0d2c4e6f8a0b2c4"},{"name":"X-Finn-Event","in":"header","required":true,"description":"The event name.","schema":{"type":"string","const":"contact.updated"}},{"name":"User-Agent","in":"header","required":true,"schema":{"type":"string","const":"Finn-Webhooks/1"}}],"requestBody":{"required":true,"content":{"application/json":{"schema":{"$ref":"#/components/schemas/ContactUpdatedEvent"},"example":{"event":"contact.updated","event_id":"crm:updated:6f1c2a0e-8d1b-4b7a-9f3e-2c4d5e6f7a8b:1762164902000","created_at":"2026-11-03T10:15:04.000Z","occurred_at":"2026-11-03T10:15:02.000Z","org_id":"0b7c6a1e-2f3d-4e5a-8b9c-1d2e3f4a5b6c","data":{"contact":{"id":"6f1c2a0e-8d1b-4b7a-9f3e-2c4d5e6f7a8b","name":"Ravi Rao","phones":["+919800000091"],"emails":[],"owner":null,"stage":"qualified","stage_kind":"open","lead_status":"qualified","lead_source":null,"sources":["api"],"tags":["expo","hot"],"fields":{},"custom_fields":{"budget":15000},"created_at":"2026-11-03T09:02:44.000Z","updated_at":"2026-11-03T10:15:02.000Z"},"changed_fields":["custom_fields.budget","stage","tags"]}}}}},"responses":{"2XX":{"description":"Acknowledge receipt. Respond as soon as the event is stored; work done before responding counts toward the 10-second timeout."}}}},"case.created":{"post":{"operationId":"caseCreated","summary":"case.created","description":"A Finn Operator case opened. `data.contact` is the case's record, or `null` when it has none.\n\nSent to each webhook endpoint that lists this event, usually within a minute, signed and retried like `call.completed` (`X-Finn-Signature`, up to 3 attempts). Events of one endpoint go out in the order they happened. Delivery is at least once: de-duplicate on `event_id`. An event not sent within 24 hours is dropped.","security":[],"parameters":[{"name":"X-Finn-Signature","in":"header","required":true,"description":"`t=<unix seconds>,v1=<hex HMAC-SHA256 of \"<t>.<raw body>\">`.","schema":{"type":"string"},"example":"t=1759403049,v1=5f8a3c0e9d2b7a41c6e8f0d3b9a2c7e1f4d6b8a0c2e4f6a8b0d2c4e6f8a0b2c4"},{"name":"X-Finn-Event","in":"header","required":true,"description":"The event name.","schema":{"type":"string","const":"case.created"}},{"name":"User-Agent","in":"header","required":true,"schema":{"type":"string","const":"Finn-Webhooks/1"}}],"requestBody":{"required":true,"content":{"application/json":{"schema":{"$ref":"#/components/schemas/CaseCreatedEvent"},"example":{"event":"case.created","event_id":"case_created:3c9b1f7e-2a4d-4e6b-8c1d-5f7a9b0c2d3e","created_at":"2026-11-03T10:15:04.000Z","occurred_at":"2026-11-03T10:15:02.000Z","org_id":"0b7c6a1e-2f3d-4e5a-8b9c-1d2e3f4a5b6c","data":{"contact":null,"case":{"id":"3c9b1f7e-2a4d-4e6b-8c1d-5f7a9b0c2d3e","agent":{"id":"1b2c3d4e-5f6a-4b7c-8d9e-0f1a2b3c4d5e","name":"Renewals"},"status":"running","outcome_key":null,"title":"Renewal follow-up"}}}}}},"responses":{"2XX":{"description":"Acknowledge receipt. Respond as soon as the event is stored; work done before responding counts toward the 10-second timeout."}}}},"case.completed":{"post":{"operationId":"caseCompleted","summary":"case.completed","description":"A Finn Operator case reached a final status (such as `resolved`, `ended`, `failed` or `cancelled`). `data.contact` may be `null`.\n\nSent to each webhook endpoint that lists this event, usually within a minute, signed and retried like `call.completed` (`X-Finn-Signature`, up to 3 attempts). Events of one endpoint go out in the order they happened. Delivery is at least once: de-duplicate on `event_id`. An event not sent within 24 hours is dropped.","security":[],"parameters":[{"name":"X-Finn-Signature","in":"header","required":true,"description":"`t=<unix seconds>,v1=<hex HMAC-SHA256 of \"<t>.<raw body>\">`.","schema":{"type":"string"},"example":"t=1759403049,v1=5f8a3c0e9d2b7a41c6e8f0d3b9a2c7e1f4d6b8a0c2e4f6a8b0d2c4e6f8a0b2c4"},{"name":"X-Finn-Event","in":"header","required":true,"description":"The event name.","schema":{"type":"string","const":"case.completed"}},{"name":"User-Agent","in":"header","required":true,"schema":{"type":"string","const":"Finn-Webhooks/1"}}],"requestBody":{"required":true,"content":{"application/json":{"schema":{"$ref":"#/components/schemas/CaseCompletedEvent"},"example":{"event":"case.completed","event_id":"case_completed:3c9b1f7e-2a4d-4e6b-8c1d-5f7a9b0c2d3e","created_at":"2026-11-03T10:15:04.000Z","occurred_at":"2026-11-03T10:15:02.000Z","org_id":"0b7c6a1e-2f3d-4e5a-8b9c-1d2e3f4a5b6c","data":{"contact":{"id":"6f1c2a0e-8d1b-4b7a-9f3e-2c4d5e6f7a8b","name":"Ravi Rao","phones":["+919800000091"],"emails":[],"owner":null,"stage":"qualified","stage_kind":"open","lead_status":"qualified","lead_source":null,"sources":["api"],"tags":["expo","hot"],"fields":{},"custom_fields":{"budget":15000},"created_at":"2026-11-03T09:02:44.000Z","updated_at":"2026-11-03T10:15:02.000Z"},"case":{"id":"3c9b1f7e-2a4d-4e6b-8c1d-5f7a9b0c2d3e","agent":{"id":"1b2c3d4e-5f6a-4b7c-8d9e-0f1a2b3c4d5e","name":"Renewals"},"status":"resolved","outcome_key":"renewed","title":"Renewal follow-up"}}}}}},"responses":{"2XX":{"description":"Acknowledge receipt. Respond as soon as the event is stored; work done before responding counts toward the 10-second timeout."}}}},"approval.requested":{"post":{"operationId":"approvalRequested","summary":"approval.requested","description":"A Finn Operator case is waiting for a person's approval. Sent once per approval. Read it with `GET /approvals/{id}` and decide it with the approval endpoints, within the key's limits. `data.contact` may be `null`.\n\nSent to each webhook endpoint that lists this event, usually within a minute, signed and retried like `call.completed` (`X-Finn-Signature`, up to 3 attempts). Events of one endpoint go out in the order they happened. Delivery is at least once: de-duplicate on `event_id`. An event not sent within 24 hours is dropped.","security":[],"parameters":[{"name":"X-Finn-Signature","in":"header","required":true,"description":"`t=<unix seconds>,v1=<hex HMAC-SHA256 of \"<t>.<raw body>\">`.","schema":{"type":"string"},"example":"t=1759403049,v1=5f8a3c0e9d2b7a41c6e8f0d3b9a2c7e1f4d6b8a0c2e4f6a8b0d2c4e6f8a0b2c4"},{"name":"X-Finn-Event","in":"header","required":true,"description":"The event name.","schema":{"type":"string","const":"approval.requested"}},{"name":"User-Agent","in":"header","required":true,"schema":{"type":"string","const":"Finn-Webhooks/1"}}],"requestBody":{"required":true,"content":{"application/json":{"schema":{"$ref":"#/components/schemas/ApprovalRequestedEvent"},"example":{"event":"approval.requested","event_id":"approval_requested:5d2e8f1a-3b4c-4d6e-9f0a-1b2c3d4e5f6a","created_at":"2026-11-03T10:15:04.000Z","occurred_at":"2026-11-03T10:15:02.000Z","org_id":"0b7c6a1e-2f3d-4e5a-8b9c-1d2e3f4a5b6c","data":{"contact":{"id":"6f1c2a0e-8d1b-4b7a-9f3e-2c4d5e6f7a8b","name":"Ravi Rao","phones":["+919800000091"],"emails":[],"owner":null,"stage":"qualified","stage_kind":"open","lead_status":"qualified","lead_source":null,"sources":["api"],"tags":["expo","hot"],"fields":{},"custom_fields":{"budget":15000},"created_at":"2026-11-03T09:02:44.000Z","updated_at":"2026-11-03T10:15:02.000Z"},"approval":{"id":"5d2e8f1a-3b4c-4d6e-9f0a-1b2c3d4e5f6a","case_id":"3c9b1f7e-2a4d-4e6b-8c1d-5f7a9b0c2d3e","agent":{"id":"1b2c3d4e-5f6a-4b7c-8d9e-0f1a2b3c4d5e","name":"Renewals"},"proposal_summary":"Email the renewal quote","due_at":"2026-11-04T09:00:00.000Z","link":"https://www.hirefinn.ai/operator/approvals/5d2e8f1a-3b4c-4d6e-9f0a-1b2c3d4e5f6a"}}}}}},"responses":{"2XX":{"description":"Acknowledge receipt. Respond as soon as the event is stored; work done before responding counts toward the 10-second timeout."}}}},"contact.created":{"post":{"operationId":"contactCreated","summary":"contact.created","description":"A record was created: in Finn, from a lead source, by an import through the API or by a call. `data.contact` is the record when the event is sent (with the owner the assignment rules gave it).\n\nSent to each webhook endpoint that lists this event, usually within a minute, signed and retried like `call.completed` (`X-Finn-Signature`, up to 3 attempts; with durable webhooks, again after 1 min, 5 min, 30 min, 2 h and 6 h). Events of one endpoint go out in the order they happened. Delivery is at least once: de-duplicate on `event_id`. An event not sent within 24 hours is dropped.","security":[],"parameters":[{"name":"X-Finn-Signature","in":"header","required":true,"description":"`t=<unix seconds>,v1=<hex HMAC-SHA256 of \"<t>.<raw body>\">`.","schema":{"type":"string"},"example":"t=1759403049,v1=5f8a3c0e9d2b7a41c6e8f0d3b9a2c7e1f4d6b8a0c2e4f6a8b0d2c4e6f8a0b2c4"},{"name":"X-Finn-Event","in":"header","required":true,"description":"The event name.","schema":{"type":"string","const":"contact.created"}},{"name":"User-Agent","in":"header","required":true,"schema":{"type":"string","const":"Finn-Webhooks/1"}}],"requestBody":{"required":true,"content":{"application/json":{"schema":{"$ref":"#/components/schemas/ContactCreatedEvent"},"example":{"event":"contact.created","event_id":"crm:created:6f1c2a0e-8d1b-4b7a-9f3e-2c4d5e6f7a8b:1762164902000","created_at":"2026-11-03T10:15:04.000Z","occurred_at":"2026-11-03T10:15:02.000Z","org_id":"0b7c6a1e-2f3d-4e5a-8b9c-1d2e3f4a5b6c","data":{"contact":{"id":"6f1c2a0e-8d1b-4b7a-9f3e-2c4d5e6f7a8b","name":"Ravi Rao","phones":["+919800000091"],"emails":[],"owner":null,"stage":"qualified","stage_kind":"open","lead_status":"qualified","lead_source":null,"sources":["api"],"tags":["expo","hot"],"fields":{},"custom_fields":{"budget":15000},"created_at":"2026-11-03T09:02:44.000Z","updated_at":"2026-11-03T10:15:02.000Z"}}}}}},"responses":{"2XX":{"description":"Acknowledge receipt. Respond as soon as the event is stored; work done before responding counts toward the 10-second timeout."}}}},"contact.assigned":{"post":{"operationId":"contactAssigned","summary":"contact.assigned","description":"A record got a new owner, or lost its owner. `data.change.from` / `to` are `{ id, name }` or `null`.\n\nSent to each webhook endpoint that lists this event, usually within a minute, signed and retried like `call.completed` (`X-Finn-Signature`, up to 3 attempts; with durable webhooks, again after 1 min, 5 min, 30 min, 2 h and 6 h). Events of one endpoint go out in the order they happened. Delivery is at least once: de-duplicate on `event_id`. An event not sent within 24 hours is dropped.","security":[],"parameters":[{"name":"X-Finn-Signature","in":"header","required":true,"description":"`t=<unix seconds>,v1=<hex HMAC-SHA256 of \"<t>.<raw body>\">`.","schema":{"type":"string"},"example":"t=1759403049,v1=5f8a3c0e9d2b7a41c6e8f0d3b9a2c7e1f4d6b8a0c2e4f6a8b0d2c4e6f8a0b2c4"},{"name":"X-Finn-Event","in":"header","required":true,"description":"The event name.","schema":{"type":"string","const":"contact.assigned"}},{"name":"User-Agent","in":"header","required":true,"schema":{"type":"string","const":"Finn-Webhooks/1"}}],"requestBody":{"required":true,"content":{"application/json":{"schema":{"$ref":"#/components/schemas/ContactAssignedEvent"},"example":{"event":"contact.assigned","event_id":"crm:assigned:6f1c2a0e-8d1b-4b7a-9f3e-2c4d5e6f7a8b:1762164902000","created_at":"2026-11-03T10:15:04.000Z","occurred_at":"2026-11-03T10:15:02.000Z","org_id":"0b7c6a1e-2f3d-4e5a-8b9c-1d2e3f4a5b6c","data":{"contact":{"id":"6f1c2a0e-8d1b-4b7a-9f3e-2c4d5e6f7a8b","name":"Ravi Rao","phones":["+919800000091"],"emails":[],"owner":null,"stage":"qualified","stage_kind":"open","lead_status":"qualified","lead_source":null,"sources":["api"],"tags":["expo","hot"],"fields":{},"custom_fields":{"budget":15000},"created_at":"2026-11-03T09:02:44.000Z","updated_at":"2026-11-03T10:15:02.000Z"},"change":{"from":null,"to":{"id":"4a1b2c3d-5e6f-4a7b-8c9d-0e1f2a3b4c5d","name":"Asha"},"by":"auto","user":null}}}}}},"responses":{"2XX":{"description":"Acknowledge receipt. Respond as soon as the event is stored; work done before responding counts toward the 10-second timeout."}}}},"contact.stage_changed":{"post":{"operationId":"contactStageChanged","summary":"contact.stage_changed","description":"A record moved to another stage. `data.change.from` / `to` are stage keys; `data.contact.stage_kind` says whether the new stage is open, won or lost.\n\nSent to each webhook endpoint that lists this event, usually within a minute, signed and retried like `call.completed` (`X-Finn-Signature`, up to 3 attempts; with durable webhooks, again after 1 min, 5 min, 30 min, 2 h and 6 h). Events of one endpoint go out in the order they happened. Delivery is at least once: de-duplicate on `event_id`. An event not sent within 24 hours is dropped.","security":[],"parameters":[{"name":"X-Finn-Signature","in":"header","required":true,"description":"`t=<unix seconds>,v1=<hex HMAC-SHA256 of \"<t>.<raw body>\">`.","schema":{"type":"string"},"example":"t=1759403049,v1=5f8a3c0e9d2b7a41c6e8f0d3b9a2c7e1f4d6b8a0c2e4f6a8b0d2c4e6f8a0b2c4"},{"name":"X-Finn-Event","in":"header","required":true,"description":"The event name.","schema":{"type":"string","const":"contact.stage_changed"}},{"name":"User-Agent","in":"header","required":true,"schema":{"type":"string","const":"Finn-Webhooks/1"}}],"requestBody":{"required":true,"content":{"application/json":{"schema":{"$ref":"#/components/schemas/ContactStageChangedEvent"},"example":{"event":"contact.stage_changed","event_id":"crm:stage_changed:6f1c2a0e-8d1b-4b7a-9f3e-2c4d5e6f7a8b:1762164902000","created_at":"2026-11-03T10:15:04.000Z","occurred_at":"2026-11-03T10:15:02.000Z","org_id":"0b7c6a1e-2f3d-4e5a-8b9c-1d2e3f4a5b6c","data":{"contact":{"id":"6f1c2a0e-8d1b-4b7a-9f3e-2c4d5e6f7a8b","name":"Ravi Rao","phones":["+919800000091"],"emails":[],"owner":null,"stage":"qualified","stage_kind":"open","lead_status":"qualified","lead_source":null,"sources":["api"],"tags":["expo","hot"],"fields":{},"custom_fields":{"budget":15000},"created_at":"2026-11-03T09:02:44.000Z","updated_at":"2026-11-03T10:15:02.000Z"},"change":{"from":"new","to":"qualified","by":"person","user":{"id":"4a1b2c3d-5e6f-4a7b-8c9d-0e1f2a3b4c5d","name":"Asha"}}}}}}},"responses":{"2XX":{"description":"Acknowledge receipt. Respond as soon as the event is stored; work done before responding counts toward the 10-second timeout."}}}},"contact.substatus_changed":{"post":{"operationId":"contactSubstatusChanged","summary":"contact.substatus_changed","description":"A record moved to another substatus within the same stage (organizations with substatuses, lead records only).\n\nSent to each webhook endpoint that lists this event, usually within a minute, signed and retried like `call.completed` (`X-Finn-Signature`, up to 3 attempts; with durable webhooks, again after 1 min, 5 min, 30 min, 2 h and 6 h). Events of one endpoint go out in the order they happened. Delivery is at least once: de-duplicate on `event_id`. An event not sent within 24 hours is dropped.","security":[],"parameters":[{"name":"X-Finn-Signature","in":"header","required":true,"description":"`t=<unix seconds>,v1=<hex HMAC-SHA256 of \"<t>.<raw body>\">`.","schema":{"type":"string"},"example":"t=1759403049,v1=5f8a3c0e9d2b7a41c6e8f0d3b9a2c7e1f4d6b8a0c2e4f6a8b0d2c4e6f8a0b2c4"},{"name":"X-Finn-Event","in":"header","required":true,"description":"The event name.","schema":{"type":"string","const":"contact.substatus_changed"}},{"name":"User-Agent","in":"header","required":true,"schema":{"type":"string","const":"Finn-Webhooks/1"}}],"requestBody":{"required":true,"content":{"application/json":{"schema":{"$ref":"#/components/schemas/ContactSubstatusChangedEvent"},"example":{"event":"contact.substatus_changed","event_id":"crm:substatus_changed:6f1c2a0e-8d1b-4b7a-9f3e-2c4d5e6f7a8b:1762164902000","created_at":"2026-11-03T10:15:04.000Z","occurred_at":"2026-11-03T10:15:02.000Z","org_id":"0b7c6a1e-2f3d-4e5a-8b9c-1d2e3f4a5b6c","data":{"contact":{"id":"6f1c2a0e-8d1b-4b7a-9f3e-2c4d5e6f7a8b","name":"Ravi Rao","phones":["+919800000091"],"emails":[],"owner":null,"stage":"qualified","stage_kind":"open","lead_status":"qualified","lead_source":null,"sources":["api"],"tags":["expo","hot"],"fields":{},"custom_fields":{"budget":15000},"created_at":"2026-11-03T09:02:44.000Z","updated_at":"2026-11-03T10:15:02.000Z"},"substatus":{"stage":"qualified","from":null,"to":"docs_pending","by":"user","via":"patch"}}}}}},"responses":{"2XX":{"description":"Acknowledge receipt. Respond as soon as the event is stored; work done before responding counts toward the 10-second timeout."}}}},"contact.section_row_added":{"post":{"operationId":"contactSectionRowAdded","summary":"contact.section_row_added","description":"A row was added to a section (a repeating group of fields) of a record. Rows imported in bulk send no event.\n\nSent to each webhook endpoint that lists this event, usually within a minute, signed and retried like `call.completed` (`X-Finn-Signature`, up to 3 attempts; with durable webhooks, again after 1 min, 5 min, 30 min, 2 h and 6 h). Events of one endpoint go out in the order they happened. Delivery is at least once: de-duplicate on `event_id`. An event not sent within 24 hours is dropped.","security":[],"parameters":[{"name":"X-Finn-Signature","in":"header","required":true,"description":"`t=<unix seconds>,v1=<hex HMAC-SHA256 of \"<t>.<raw body>\">`.","schema":{"type":"string"},"example":"t=1759403049,v1=5f8a3c0e9d2b7a41c6e8f0d3b9a2c7e1f4d6b8a0c2e4f6a8b0d2c4e6f8a0b2c4"},{"name":"X-Finn-Event","in":"header","required":true,"description":"The event name.","schema":{"type":"string","const":"contact.section_row_added"}},{"name":"User-Agent","in":"header","required":true,"schema":{"type":"string","const":"Finn-Webhooks/1"}}],"requestBody":{"required":true,"content":{"application/json":{"schema":{"$ref":"#/components/schemas/ContactSectionRowAddedEvent"},"example":{"event":"contact.section_row_added","event_id":"crm:section_row_added:6f1c2a0e-8d1b-4b7a-9f3e-2c4d5e6f7a8b:1762164902000","created_at":"2026-11-03T10:15:04.000Z","occurred_at":"2026-11-03T10:15:02.000Z","org_id":"0b7c6a1e-2f3d-4e5a-8b9c-1d2e3f4a5b6c","data":{"contact":{"id":"6f1c2a0e-8d1b-4b7a-9f3e-2c4d5e6f7a8b","name":"Ravi Rao","phones":["+919800000091"],"emails":[],"owner":null,"stage":"qualified","stage_kind":"open","lead_status":"qualified","lead_source":null,"sources":["api"],"tags":["expo","hot"],"fields":{},"custom_fields":{"budget":15000},"created_at":"2026-11-03T09:02:44.000Z","updated_at":"2026-11-03T10:15:02.000Z"},"section_row":{"field_key":"visits","row_id":"9c8b7a6d-5e4f-4a3b-8c2d-1e0f9a8b7c6d","data":{"date":"2026-11-03","notes":"Site visit"},"supersedes_id":null,"meta":null}}}}}},"responses":{"2XX":{"description":"Acknowledge receipt. Respond as soon as the event is stored; work done before responding counts toward the 10-second timeout."}}}},"contact.link_changed":{"post":{"operationId":"contactLinkChanged","summary":"contact.link_changed","description":"A link field of a record (a pointer to another record) was set, changed or ended. Links imported in bulk send no event.\n\nSent to each webhook endpoint that lists this event, usually within a minute, signed and retried like `call.completed` (`X-Finn-Signature`, up to 3 attempts; with durable webhooks, again after 1 min, 5 min, 30 min, 2 h and 6 h). Events of one endpoint go out in the order they happened. Delivery is at least once: de-duplicate on `event_id`. An event not sent within 24 hours is dropped.","security":[],"parameters":[{"name":"X-Finn-Signature","in":"header","required":true,"description":"`t=<unix seconds>,v1=<hex HMAC-SHA256 of \"<t>.<raw body>\">`.","schema":{"type":"string"},"example":"t=1759403049,v1=5f8a3c0e9d2b7a41c6e8f0d3b9a2c7e1f4d6b8a0c2e4f6a8b0d2c4e6f8a0b2c4"},{"name":"X-Finn-Event","in":"header","required":true,"description":"The event name.","schema":{"type":"string","const":"contact.link_changed"}},{"name":"User-Agent","in":"header","required":true,"schema":{"type":"string","const":"Finn-Webhooks/1"}}],"requestBody":{"required":true,"content":{"application/json":{"schema":{"$ref":"#/components/schemas/ContactLinkChangedEvent"},"example":{"event":"contact.link_changed","event_id":"crm:link_changed:6f1c2a0e-8d1b-4b7a-9f3e-2c4d5e6f7a8b:1762164902000","created_at":"2026-11-03T10:15:04.000Z","occurred_at":"2026-11-03T10:15:02.000Z","org_id":"0b7c6a1e-2f3d-4e5a-8b9c-1d2e3f4a5b6c","data":{"contact":{"id":"6f1c2a0e-8d1b-4b7a-9f3e-2c4d5e6f7a8b","name":"Ravi Rao","phones":["+919800000091"],"emails":[],"owner":null,"stage":"qualified","stage_kind":"open","lead_status":"qualified","lead_source":null,"sources":["api"],"tags":["expo","hot"],"fields":{},"custom_fields":{"budget":15000},"created_at":"2026-11-03T09:02:44.000Z","updated_at":"2026-11-03T10:15:02.000Z"},"link":{"field_key":"company","from_contact_id":"6f1c2a0e-8d1b-4b7a-9f3e-2c4d5e6f7a8b","old_to":null,"new_to":"7a2d3b1f-9e2c-4c8b-8a4f-3d5e6f7a8b9c"}}}}}},"responses":{"2XX":{"description":"Acknowledge receipt. Respond as soon as the event is stored; work done before responding counts toward the 10-second timeout."}}}},"lead.created":{"post":{"operationId":"leadCreated","summary":"lead.created","description":"A lead arrived: a new lead record, or a lead merged into an existing record. `data.lead` says how it came in.\n\nSent to each webhook endpoint that lists this event, usually within a minute, signed and retried like `call.completed` (`X-Finn-Signature`, up to 3 attempts; with durable webhooks, again after 1 min, 5 min, 30 min, 2 h and 6 h). Events of one endpoint go out in the order they happened. Delivery is at least once: de-duplicate on `event_id`. An event not sent within 24 hours is dropped.","security":[],"parameters":[{"name":"X-Finn-Signature","in":"header","required":true,"description":"`t=<unix seconds>,v1=<hex HMAC-SHA256 of \"<t>.<raw body>\">`.","schema":{"type":"string"},"example":"t=1759403049,v1=5f8a3c0e9d2b7a41c6e8f0d3b9a2c7e1f4d6b8a0c2e4f6a8b0d2c4e6f8a0b2c4"},{"name":"X-Finn-Event","in":"header","required":true,"description":"The event name.","schema":{"type":"string","const":"lead.created"}},{"name":"User-Agent","in":"header","required":true,"schema":{"type":"string","const":"Finn-Webhooks/1"}}],"requestBody":{"required":true,"content":{"application/json":{"schema":{"$ref":"#/components/schemas/LeadCreatedEvent"},"example":{"event":"lead.created","event_id":"crm:created:6f1c2a0e-8d1b-4b7a-9f3e-2c4d5e6f7a8b:1762164902000","created_at":"2026-11-03T10:15:04.000Z","occurred_at":"2026-11-03T10:15:02.000Z","org_id":"0b7c6a1e-2f3d-4e5a-8b9c-1d2e3f4a5b6c","data":{"contact":{"id":"6f1c2a0e-8d1b-4b7a-9f3e-2c4d5e6f7a8b","name":"Ravi Rao","phones":["+919800000091"],"emails":[],"owner":null,"stage":"qualified","stage_kind":"open","lead_status":"qualified","lead_source":null,"sources":["api"],"tags":["expo","hot"],"fields":{},"custom_fields":{"budget":15000},"created_at":"2026-11-03T09:02:44.000Z","updated_at":"2026-11-03T10:15:02.000Z"},"lead":{"via":"form","source":"website"}}}}}},"responses":{"2XX":{"description":"Acknowledge receipt. Respond as soon as the event is stored; work done before responding counts toward the 10-second timeout."}}}},"lead.status_changed":{"post":{"operationId":"leadStatusChanged","summary":"lead.status_changed","description":"A record's lead status changed. `data.change.from` / `to` are lead statuses.\n\nSent to each webhook endpoint that lists this event, usually within a minute, signed and retried like `call.completed` (`X-Finn-Signature`, up to 3 attempts; with durable webhooks, again after 1 min, 5 min, 30 min, 2 h and 6 h). Events of one endpoint go out in the order they happened. Delivery is at least once: de-duplicate on `event_id`. An event not sent within 24 hours is dropped.","security":[],"parameters":[{"name":"X-Finn-Signature","in":"header","required":true,"description":"`t=<unix seconds>,v1=<hex HMAC-SHA256 of \"<t>.<raw body>\">`.","schema":{"type":"string"},"example":"t=1759403049,v1=5f8a3c0e9d2b7a41c6e8f0d3b9a2c7e1f4d6b8a0c2e4f6a8b0d2c4e6f8a0b2c4"},{"name":"X-Finn-Event","in":"header","required":true,"description":"The event name.","schema":{"type":"string","const":"lead.status_changed"}},{"name":"User-Agent","in":"header","required":true,"schema":{"type":"string","const":"Finn-Webhooks/1"}}],"requestBody":{"required":true,"content":{"application/json":{"schema":{"$ref":"#/components/schemas/LeadStatusChangedEvent"},"example":{"event":"lead.status_changed","event_id":"crm:status_changed:6f1c2a0e-8d1b-4b7a-9f3e-2c4d5e6f7a8b:1762164902000","created_at":"2026-11-03T10:15:04.000Z","occurred_at":"2026-11-03T10:15:02.000Z","org_id":"0b7c6a1e-2f3d-4e5a-8b9c-1d2e3f4a5b6c","data":{"contact":{"id":"6f1c2a0e-8d1b-4b7a-9f3e-2c4d5e6f7a8b","name":"Ravi Rao","phones":["+919800000091"],"emails":[],"owner":null,"stage":"qualified","stage_kind":"open","lead_status":"qualified","lead_source":null,"sources":["api"],"tags":["expo","hot"],"fields":{},"custom_fields":{"budget":15000},"created_at":"2026-11-03T09:02:44.000Z","updated_at":"2026-11-03T10:15:02.000Z"},"change":{"from":"new","to":"qualified","by":"auto","user":null}}}}}},"responses":{"2XX":{"description":"Acknowledge receipt. Respond as soon as the event is stored; work done before responding counts toward the 10-second timeout."}}}},"document.status_changed":{"post":{"operationId":"documentStatusChanged","summary":"document.status_changed","description":"A document item on a record changed status (`missing`, `received`, `verified`, `rejected`). Never carries a file name or anything read from a file.\n\nSent to each webhook endpoint that lists this event, usually within a minute, signed and retried like `call.completed` (`X-Finn-Signature`, up to 3 attempts; with durable webhooks, again after 1 min, 5 min, 30 min, 2 h and 6 h). Events of one endpoint go out in the order they happened. Delivery is at least once: de-duplicate on `event_id`. An event not sent within 24 hours is dropped.","security":[],"parameters":[{"name":"X-Finn-Signature","in":"header","required":true,"description":"`t=<unix seconds>,v1=<hex HMAC-SHA256 of \"<t>.<raw body>\">`.","schema":{"type":"string"},"example":"t=1759403049,v1=5f8a3c0e9d2b7a41c6e8f0d3b9a2c7e1f4d6b8a0c2e4f6a8b0d2c4e6f8a0b2c4"},{"name":"X-Finn-Event","in":"header","required":true,"description":"The event name.","schema":{"type":"string","const":"document.status_changed"}},{"name":"User-Agent","in":"header","required":true,"schema":{"type":"string","const":"Finn-Webhooks/1"}}],"requestBody":{"required":true,"content":{"application/json":{"schema":{"$ref":"#/components/schemas/DocumentStatusChangedEvent"},"example":{"event":"document.status_changed","event_id":"crm:status_changed:6f1c2a0e-8d1b-4b7a-9f3e-2c4d5e6f7a8b:1762164902000","created_at":"2026-11-03T10:15:04.000Z","occurred_at":"2026-11-03T10:15:02.000Z","org_id":"0b7c6a1e-2f3d-4e5a-8b9c-1d2e3f4a5b6c","data":{"contact":{"id":"6f1c2a0e-8d1b-4b7a-9f3e-2c4d5e6f7a8b","name":"Ravi Rao","phones":["+919800000091"],"emails":[],"owner":null,"stage":"qualified","stage_kind":"open","lead_status":"qualified","lead_source":null,"sources":["api"],"tags":["expo","hot"],"fields":{},"custom_fields":{"budget":15000},"created_at":"2026-11-03T09:02:44.000Z","updated_at":"2026-11-03T10:15:02.000Z"},"document":{"type":{"id":"d1","key":"id_proof","name":"ID proof"},"from":"missing","to":"received","via":"upload_link","requested_count":1}}}}}},"responses":{"2XX":{"description":"Acknowledge receipt. Respond as soon as the event is stored; work done before responding counts toward the 10-second timeout."}}}}},"components":{"securitySchemes":{"TestApiKey":{"type":"http","scheme":"bearer","bearerFormat":"finn_test_…","description":"A **test key**, starting with `finn_test_`, made in a practice organization (Settings → Integrations → API keys → Test key). It carries scopes like a live key and is sent the same way (`Authorization: Bearer finn_test_…`). Reads, writes and agent runs act on the practice organization for real, but nothing reaches a customer or spends credit: placing a call, renting a number, starting a deployment and sending a chat message return `403 test_mode`, and anything else outbound is blocked by practice mode.\n\nTest keys exist only where Finn has turned them on for your organization. A test key whose organization is no longer a practice organization returns `401 test_key_not_allowed`; one whose organization has test keys turned off returns `401 test_keys_not_enabled`."},"ApiKey":{"type":"http","scheme":"bearer","bearerFormat":"finn_live_…","description":"An API key starting with `finn_live_`, created in the dashboard under **Settings → Integrations → API keys**. A missing key, or a token without the `finn_live_` prefix, returns `401 api_key_required`; an unknown or revoked key returns `401 invalid_api_key`.\n\nEach key carries **scopes** that decide which endpoints it may call. A scope is `product:access`; the products are `voice`, `chat`, `crm`, `operator`, `approvals`, `sales`, `dialer` and `admin`.\n\n| Scope | Allows |\n|---|---|\n| `voice:read` | Read calls, Finns, voices, phone numbers, audiences, deployments, wallet and the do-not-call list: every `GET` tagged Calls, Finns, Voices, Phone numbers, Audiences, Deployments, Wallet or Do not call. |\n| `voice:write` | Every other method on those endpoints: place calls, create and change Finns, rent numbers, upload audiences, deploy, add do-not-call numbers. |\n| `voice:dnc:write` | Remove a number from the do-not-call list. |\n| `crm:read` | Read records, record kinds, fields and a record's timeline. |\n| `crm:sensitive:read` | See the values of fields marked sensitive. Without it they come back as `{ \"redacted\": true, \"set\": true|false, \"hint\"? }`. |\n| `crm:write` | Create and update records, add notes. |\n| `operator:read` | Read Finn Operator cases and their events. |\n| `operator:write` | Start a case. |\n| `approvals:read` | Read pending approvals. |\n| `approvals:write` | Approve or reject a pending approval. |\n| `chat:read` | Read chat channels, conversations, messages and chat contacts. |\n| `chat:write` | Start conversations, send messages, update chat contacts. |\n| `admin:webhooks` | Create, read, change and delete webhook endpoints, read their deliveries and the event list (`/webhook_endpoints`, `/events`). Never given to a key unless picked, nor to a key made before scopes. |\n\n`operator:build`, `sales:read`, `sales:write`, `dialer:read`, `dialer:write`, `admin:read` and `admin:write` are reserved: a key may carry them, and no endpoint checks them yet.\n\nA write scope includes the read it builds on: `voice:write` includes `voice:read`; `voice:dnc:write` includes `voice:write`; `crm:write` and `crm:sensitive:read` include `crm:read`; `operator:write` includes `operator:read`; `approvals:write` includes `approvals:read` and `operator:read`; `chat:write` includes `chat:read`.\n\n**Older scope names** still work and mean exactly what they did:\n\n| Older name | Same as |\n|---|---|\n| `voice` | `voice:read` + `voice:write` |\n| `dnc:write` | `voice:dnc:write` |\n| `cases:read` | `operator:read` + `approvals:read` |\n| `cases:write` | `operator:write` |\n\nPick scopes when you create the key; a new key with none picked gets `voice`. A key created before scopes existed keeps the access it had: every scope above except `voice:dnc:write`, `approvals:write` and the reserved ones. A request without the scope it needs returns `403 insufficient_scope` with `required_scope`. Each operation names its scope in its description and in `x-required-scope` (and the older name in `x-legacy-scope`).\n\nAI agents should use a key with only the scopes they need."}},"headers":{"RateLimitPolicy":{"description":"The limit and window, for example `300;w=60`.","schema":{"type":"string"}},"RateLimitLimit":{"description":"Requests allowed in the current window for this endpoint group.","schema":{"type":"integer"}},"RateLimitRemaining":{"description":"Requests left in the current window.","schema":{"type":"integer"}},"RateLimitReset":{"description":"Seconds until the window resets.","schema":{"type":"integer"}},"RetryAfter":{"description":"Seconds to wait before retrying.","schema":{"type":"integer"}},"IdempotentReplayed":{"description":"`true` when this response is the saved answer to an earlier request with the same `Idempotency-Key`; nothing was done again.","schema":{"type":"string","enum":["true"]}},"FinnVersion":{"description":"The API version this response was served with: the request's `Finn-Version` header, else the version pinned on the API key, else the current version. Sent only while version pinning is switched on (beta).","schema":{"type":"string","example":"2026-11-01"}}},"responses":{"AiAppsPaused":{"description":"`ai_apps_paused`: an admin of the organization has paused all AI apps (Settings > Integrations in Finn). Every write by an API key, OAuth app or MCP key is refused until they turn AI apps back on; reads still work. An approved held action is refused too while paused.","content":{"application/json":{"schema":{"$ref":"#/components/schemas/V1Error"},"examples":{"ai_apps_paused":{"summary":"ai_apps_paused","value":{"error":{"code":"ai_apps_paused","message":"An admin of this organization has paused all AI apps. Reads still work; writes are refused until an admin turns AI apps back on in Finn: Settings > Integrations.","fields":[],"request_id":"req_0123456789abcdef","doc_url":"https://api.hirefinn.ai/docs/errors#ai_apps_paused"}}}}}}},"Unauthorized":{"description":"The API key is missing, malformed, unknown or revoked.","content":{"application/json":{"schema":{"allOf":[{"$ref":"#/components/schemas/Error"},{"type":"object","properties":{"error":{"type":"string","enum":["api_key_required","invalid_api_key","key_expired"]}}}]},"examples":{"api_key_required":{"summary":"api_key_required","value":{"error":"api_key_required","message":"Provide a Bearer finn_live_... key in the Authorization header"}},"invalid_api_key":{"summary":"invalid_api_key","value":{"error":"invalid_api_key","message":"The key is unknown or has been revoked."}}}}}},"RateLimited":{"description":"Too many requests for this key and endpoint group. Wait `Retry-After` seconds, then retry. For an organization with launch-safety limits on, also `rate_limited` for the per-credential ceiling across the API and `daily_cap_reached` when the organization has sent its daily number of calls, messages or emails through AI apps (API keys, OAuth apps, MCP); then `Retry-After` is the time to midnight UTC. Those organizations get the unified error body (`V1Error`).","content":{"application/json":{"schema":{"anyOf":[{"allOf":[{"$ref":"#/components/schemas/Error"},{"type":"object","properties":{"error":{"type":"string","enum":["rate_limited"]}}}]},{"$ref":"#/components/schemas/V1Error"}]},"examples":{"daily_cap_reached":{"summary":"daily_cap_reached (launch-safety limits on)","value":{"error":{"code":"daily_cap_reached","message":"This organization has reached its daily limit of 500 calls sent by AI apps (500 used today, UTC). It resets at midnight UTC; ask Finn support to change the limit.","fields":[],"request_id":"req_0123456789abcdef","doc_url":"https://api.hirefinn.ai/docs/errors#daily_cap_reached"}}},"rate_limited":{"summary":"rate_limited","value":{"error":"rate_limited","message":"Too many requests. Limit is 300 per minute per API key."}}}}},"headers":{"RateLimit-Policy":{"$ref":"#/components/headers/RateLimitPolicy"},"RateLimit-Limit":{"$ref":"#/components/headers/RateLimitLimit"},"RateLimit-Remaining":{"$ref":"#/components/headers/RateLimitRemaining"},"RateLimit-Reset":{"$ref":"#/components/headers/RateLimitReset"},"Retry-After":{"$ref":"#/components/headers/RetryAfter"}}},"InternalError":{"description":"Unexpected error. Retry reads with backoff; check state before retrying writes.","content":{"application/json":{"schema":{"allOf":[{"$ref":"#/components/schemas/Error"},{"type":"object","properties":{"error":{"type":"string","enum":["internal_error"]}}}]},"examples":{"internal_error":{"summary":"internal_error","value":{"error":"internal_error"}}}}}},"PendingApproval":{"description":"Held for approval: nothing has run yet. A person in Finn approves or declines it at `approve_url` before `expires_at` (24 hours); once approved, Finn runs the request exactly as sent. Read the outcome with `GET /approvals/{approval_id}`. A retry with the same `Idempotency-Key` never runs it a second time. Who is held: members' OAuth tokens and MCP keys while the organization has holds on (without them these writes answer 403 `approval_unavailable`), and org API keys only where the organization's hold policy says so. Spending (`money`) is always held for those credentials; an `outward` action may be relaxed by the policy.","headers":{"Idempotent-Replayed":{"$ref":"#/components/headers/IdempotentReplayed"}},"content":{"application/json":{"schema":{"$ref":"#/components/schemas/PendingApproval"},"example":{"status":"pending_approval","approval_id":"3f0c6a52-8d1e-4c7a-9b0e-2d4f6a8c1e30","approve_url":"https://app.hirefinn.ai/approvals/holds/3f0c6a52-8d1e-4c7a-9b0e-2d4f6a8c1e30","expires_at":"2026-12-02T10:15:00.000Z","action":"call.create","summary":"Call +14155550123 with Finn \"Front desk\""}}}},"InsufficientScope":{"description":"The API key does not have the scope this operation needs.","content":{"application/json":{"schema":{"allOf":[{"$ref":"#/components/schemas/Error"},{"type":"object","properties":{"error":{"type":"string","enum":["insufficient_scope"]}}}]},"examples":{"insufficient_scope":{"summary":"insufficient_scope","value":{"error":"insufficient_scope","message":"This API key does not have the crm:write scope. Create a key with it in Finn: Settings > Integrations > API keys.","required_scope":"crm:write"}}}}}}},"schemas":{"Error":{"type":"object","description":"Every error has a stable `error` code and, except for `internal_error`, a human-readable `message`. Check `error`, not `message`. Some errors add the optional fields below.","required":["error"],"properties":{"error":{"type":"string","description":"Stable error code.","examples":["invalid_request"]},"message":{"type":"string","description":"Human-readable explanation. Can change; do not parse it."},"action_url":{"type":"string","format":"uri","description":"Dashboard page where the problem is fixed, on errors that need setup in the dashboard: `compliance_required`, `insufficient_credits`, `feature_not_allowed`, `api_key_owner_required`."},"details":{"type":"array","description":"On `400 invalid_request` from audience contact endpoints: up to 50 contacts that failed validation.","items":{"type":"object","required":["index","message"],"properties":{"index":{"type":"integer","description":"Position of the contact in the request's `contacts` array."},"message":{"type":"string"}}}},"deployment_id":{"type":["string","null"],"format":"uuid","description":"On `409 deployment_already_active` and `409 audience_in_use`: the live deployment that caused the conflict."},"errors":{"type":"array","items":{},"description":"On `422 deployment_rejected`: the reasons the deployment was rejected, when available."},"required_credits":{"type":"number","description":"On `402 insufficient_credits` from `POST /phone_numbers`: credits the rental needs."},"available_credits":{"type":"number","description":"On `402 insufficient_credits` from `POST /phone_numbers`: credits in the wallet."},"transaction_id":{"type":"string","description":"On `500 rent_failed`: the refunded wallet transaction."},"required_scope":{"type":"string","description":"On `403 insufficient_scope`: the scope the key is missing."},"contact_id":{"type":"string","format":"uuid","description":"On `409 contact_exists` from `POST /contacts`: the record that already has this number."},"fields":{"type":"array","items":{"type":"string"},"description":"On `400 unsupported_field` from `PATCH /contacts/{id}`: the body keys an API key may not set."},"allowed_answers":{"type":"array","items":{"type":"string"},"description":"On `422 no_such_answer` from the approve and reject endpoints: the answers the approval takes."}}},"CreateCallRequest":{"type":"object","required":["finn_id","to_number","country_code"],"properties":{"finn_id":{"type":"string","format":"uuid","description":"The Finn to run. Must belong to your organization."},"to_number":{"type":"string","description":"Destination number: digits with an optional leading `+`; spaces, dashes and parentheses are allowed. With a leading `+` it is used as a full international number.","pattern":"^\\+?[0-9][0-9\\s()\\-]{5,20}$"},"country_code":{"type":"string","description":"Calling code such as `\"1\"` or `\"+91\"` (1–4 digits, optional `+`), prepended when `to_number` has no leading `+`. Required: omitting it returns `400 invalid_request`.","pattern":"^\\+?[0-9]{1,4}$"}}},"CallCreated":{"type":"object","required":["call_uuid","finn_id","to_number","status"],"properties":{"call_uuid":{"type":"string","format":"uuid","description":"The call ID. Use it with `GET /calls/{call_uuid}`."},"finn_id":{"type":"string","format":"uuid","description":"The Finn placing the call."},"to_number":{"type":"string","description":"The `to_number` you sent, trimmed."},"status":{"type":"string","const":"initiated"}}},"CallAnalysisAnswer":{"type":"object","description":"One answer from post-call analysis. The questions are the Finn's `post_call_analysis` configuration.","required":["question_name","question_type","answer","needs_review","reasoning","extracted_at"],"properties":{"question_name":{"type":"string","description":"The question's `name`."},"question_type":{"type":"string","description":"The question's type, as configured on the Finn."},"answer":{"type":["string","null"],"description":"The extracted answer. Always a string, even for Yes/No and Number questions."},"needs_review":{"type":"boolean","description":"The extraction was uncertain and a person should check it."},"reasoning":{"type":["string","null"],"description":"Why this answer was chosen."},"extracted_at":{"type":"string","format":"date-time","description":"When the answer was extracted."}}},"CallResult":{"type":"object","description":"The record of a call.","required":["call_uuid","finn_id","deployment_id","status","disconnect_reason","direction","from_number","to_number","duration_seconds","user_sentiment","call_successful","created_at","updated_at","recording_url","analysis"],"properties":{"call_uuid":{"type":"string","format":"uuid","description":"The call ID."},"finn_id":{"type":["string","null"],"format":"uuid","description":"The Finn that handled the call."},"deployment_id":{"type":["string","null"],"format":"uuid","description":"The deployment the call belongs to, the same `id` that `GET /deployments` returns. `null` for calls that do not belong to a deployment, such as calls placed with `POST /calls`."},"status":{"type":["string","null"],"description":"Call status, for example `completed`, `no_answer`, `busy`, `voicemail`, `failed` or `cancelled`. Not a closed set: compare case-insensitively and handle unknown values."},"disconnect_reason":{"type":["string","null"],"description":"Why the call ended. Free-form."},"direction":{"type":["string","null"],"description":"`outbound` or `inbound`."},"from_number":{"type":["string","null"],"description":"The calling number."},"to_number":{"type":["string","null"],"description":"The called number, in E.164 format."},"duration_seconds":{"type":["integer","null"],"description":"Talk time in whole seconds. `null` when no duration was recorded."},"user_sentiment":{"type":["string","null"],"description":"Set by post-call analysis."},"call_successful":{"type":["boolean","null"],"description":"Whether the call met its goal, according to post-call analysis."},"created_at":{"type":["string","null"],"format":"date-time"},"updated_at":{"type":["string","null"],"format":"date-time"},"recording_url":{"type":["string","null"],"format":"uri","description":"Signed link to the call recording, valid for 1 hour. Download it within that hour, or call `GET /calls/{call_uuid}` for a fresh link. `null` when the call has no recording: recording was off, the call was not answered, or the recording is past your retention window."},"analysis":{"type":"array","items":{"$ref":"#/components/schemas/CallAnalysisAnswer"},"description":"Post-call analysis answers, newest first. Empty until analysis has run."}}},"CallBilling":{"type":"object","description":"Estimated charge, computed when you read the call. Does not apply plan multipliers or unanswered-call charges; the wallet ledger is the record.","required":["credits_charged","price_per_credit","currency","estimated_cost"],"properties":{"credits_charged":{"type":"integer","description":"`ceil(duration_seconds / 60)`, or `0` with no duration."},"price_per_credit":{"type":"number","description":"Your effective rate."},"currency":{"type":"string","description":"Currency of `price_per_credit`."},"estimated_cost":{"type":"number","description":"`credits_charged × price_per_credit`."}}},"Call":{"allOf":[{"$ref":"#/components/schemas/CallResult"},{"type":"object","required":["billing"],"properties":{"billing":{"anyOf":[{"$ref":"#/components/schemas/CallBilling"},{"type":"null"}],"description":"Estimated charge. `null` if your rate could not be looked up."}}}],"properties":{"transcript":{"type":["array","null"],"items":{"$ref":"#/components/schemas/TranscriptTurn"},"description":"Turn-by-turn transcript, or null when none was stored for the call."},"ai_analysis":{"oneOf":[{"$ref":"#/components/schemas/CallAiAnalysis"},{"type":"null"}],"description":"Summary, insights and success scores. Null until the analyzer has run."}}},"Finn":{"type":"object","description":"A voice agent. List, retrieve, create and update all return this full object.","required":["id","name","status","voice","language","language_group","time_zone","use_case","identity_text","style_guardrails","response_guidelines","begin_message","system_prompt","max_call_duration_minutes","idle_reminder_message","idle_reminder_after_seconds","end_call_on_silence_seconds","handoff_number","post_call_analysis","call_flow","created_at","updated_at"],"properties":{"id":{"type":"string","format":"uuid","description":"The Finn ID."},"name":{"type":"string","description":"Display name."},"status":{"type":"string","description":"`draft` or `live`. Set by deploying and stopping; not writable."},"voice":{"type":["string","null"],"description":"Voice ID from `GET /voices`."},"language":{"type":["string","null"],"description":"Language label, for example `English`."},"language_group":{"type":["string","null"],"description":"Set from the voice's provider. Not writable."},"time_zone":{"type":["string","null"],"description":"IANA time zone."},"use_case":{"type":["string","null"],"description":"Short description of what the Finn is for."},"identity_text":{"type":["string","null"],"description":"Who the Finn is."},"style_guardrails":{"type":["string","null"],"description":"Tone and boundaries."},"response_guidelines":{"type":["string","null"],"description":"How to phrase answers."},"begin_message":{"type":["string","null"],"description":"First line the Finn speaks on every call."},"system_prompt":{"type":["string","null"],"description":"The Finn's main instructions: objective, flow and rules. The same text as **System prompt** in the dashboard editor. Calls from deployments use it unless the Finn has a call flow turned on in the dashboard; calls placed with `POST /calls` get it added after `identity_text`."},"max_call_duration_minutes":{"type":["integer","null"],"description":"Hard limit on call length, in minutes. `null` means Finn's default limit of about 8 minutes. Applies to calls from deployments, not to calls placed with `POST /calls`."},"idle_reminder_message":{"type":["string","null"],"description":"What the Finn says when the caller goes quiet. Applies to calls from deployments, not to calls placed with `POST /calls`."},"idle_reminder_after_seconds":{"type":["integer","null"],"description":"Seconds of silence before the Finn says the idle reminder. `null` uses Finn's default. Applies to calls from deployments, not to calls placed with `POST /calls`."},"end_call_on_silence_seconds":{"type":["integer","null"],"description":"Seconds of silence after which the Finn hangs up. `null` uses Finn's default. Applies to calls from deployments, not to calls placed with `POST /calls`."},"handoff_number":{"anyOf":[{"$ref":"#/components/schemas/HandoffNumber"},{"type":"null"}],"description":"Transfer number."},"post_call_analysis":{"type":"array","items":{"$ref":"#/components/schemas/PostCallAnalysisQuestion"},"description":"Questions answered after each call. Empty array when there are none."},"call_flow":{"anyOf":[{"$ref":"#/components/schemas/CallFlow"},{"type":"null"}],"description":"The Finn's call flow, or `null` when it has none. Tool `api_key` values are returned as `\"[redacted]\"`."},"created_at":{"type":"string","format":"date-time","description":"When the Finn was created."},"updated_at":{"type":"string","format":"date-time","description":"When the Finn was last updated."}}},"HandoffNumber":{"type":"object","required":["country_code","phone_number"],"properties":{"country_code":{"type":["string","null"],"examples":["+1"]},"phone_number":{"type":"string","description":"Digits without the country code.","examples":["4155550100"]}}},"HandoffNumberInput":{"type":"object","description":"Transfer number. `null` clears it.","required":["country_code","phone_number"],"properties":{"country_code":{"type":"string","pattern":"^\\+?\\d{1,4}$","description":"1 to 4 digits with an optional `+`.","examples":["+1"]},"phone_number":{"type":"string","description":"4 to 15 digits without the country code. Spaces, dashes and parentheses are stripped.","examples":["4155550100"]}}},"PostCallAnalysisQuestionInput":{"type":"object","required":["name"],"properties":{"name":{"type":"string","minLength":1,"description":"The question. Also returned as `question_name` in call analysis."},"type":{"type":"string","description":"`Yes/No`, `Selector`, `Number` or `Text`. Common aliases are accepted (`boolean` becomes `Yes/No`, `select` and `choice` become `Selector`, `integer` becomes `Number`); anything else becomes `Text`."},"description":{"type":"string","description":"Guidance for the analyzer."},"choices":{"type":"array","items":{"type":"string"},"description":"Options for `Selector` questions."},"formatExamples":{"oneOf":[{"type":"array","items":{"type":"string"}},{"type":"string"}],"description":"Example answers, as an array or one comma-separated string."},"id":{"type":"number","description":"Generated when missing."}}},"PostCallAnalysisQuestion":{"type":"object","required":["id","name","type","description"],"properties":{"id":{"type":"number"},"name":{"type":"string"},"type":{"type":"string","enum":["Yes/No","Selector","Number","Text"]},"description":{"type":"string"},"choices":{"type":"array","items":{"type":"string"},"description":"Present on `Selector` questions that have options."},"formatExamples":{"type":"array","items":{"type":"string"}}}},"CreateFinnRequest":{"type":"object","description":"Only these fields are accepted; any other field returns `400 invalid_request`. Strings are trimmed.","additionalProperties":false,"required":["name","voice"],"properties":{"name":{"type":"string","minLength":1,"maxLength":100,"description":"Display name. Need not be unique."},"voice":{"type":"string","minLength":1,"maxLength":200,"description":"A `voice_id` from `GET /voices`.\n\nThe TTS provider is derived from this id and is not a field you send: a hyphenated UUID\nis Cartesia (the default), an alphanumeric id without hyphens is ElevenLabs. Sending a\nvoice that does not belong to a provider your plan includes returns `403`.","example":"f786b574-daa5-4673-aa0c-cbe3e8534c02"},"language":{"type":["string","null"],"maxLength":50,"description":"Language label. Defaults to `English` on create."},"time_zone":{"type":["string","null"],"description":"IANA time zone.","examples":["Asia/Kolkata"]},"use_case":{"type":["string","null"],"maxLength":500,"description":"Short description of what the Finn is for."},"identity_text":{"type":["string","null"],"maxLength":20000,"description":"Who the Finn is and what it does."},"style_guardrails":{"type":["string","null"],"maxLength":20000,"description":"Tone and boundaries."},"response_guidelines":{"type":["string","null"],"maxLength":20000,"description":"How to phrase answers."},"begin_message":{"type":["string","null"],"maxLength":1000,"description":"First line the Finn speaks. Defaults to `Hello, this is a call from Finn.` on create."},"system_prompt":{"type":["string","null"],"maxLength":100000,"description":"The Finn's main instructions: objective, flow and rules. `null` or an empty string clears it."},"max_call_duration_minutes":{"type":["integer","null"],"enum":[1,3,5,10,15,30,60,null],"description":"Hard limit on call length, in minutes. `null` clears it, which means Finn's default limit of about 8 minutes. Not used by `POST /calls`."},"idle_reminder_message":{"type":["string","null"],"maxLength":1000,"description":"What the Finn says when the caller goes quiet. `null` or an empty string clears it. Not used by `POST /calls`."},"idle_reminder_after_seconds":{"type":["integer","null"],"minimum":5,"maximum":600,"description":"Seconds of silence before the idle reminder, 5 to 600. `null` clears it. Not used by `POST /calls`."},"end_call_on_silence_seconds":{"type":["integer","null"],"enum":[15,20,30,45,60,90,120,null],"description":"Seconds of silence after which the Finn hangs up. `null` clears it. Not used by `POST /calls`."},"handoff_number":{"anyOf":[{"$ref":"#/components/schemas/HandoffNumberInput"},{"type":"null"}]},"post_call_analysis":{"type":["array","null"],"maxItems":50,"items":{"$ref":"#/components/schemas/PostCallAnalysisQuestionInput"},"description":"Questions answered automatically after each call. Up to 50.\n\nFour question types, all shown in the example:\n\n- `Yes/No` — a boolean judgement.\n- `Selector` — one of `choices`. Supply `choices`, or the analyzer has nothing to pick from.\n- `Number` — a numeric answer.\n- `Text` — free text; use `formatExamples` to steer the wording.\n\nRead the answers per call with `GET /calls/{call_uuid}` (`analysis`), or aggregated\nacross the Finn with `GET /finns/{finn_id}/post_call_analysis`.","example":[{"name":"Wants to renew","type":"Yes/No","description":"Did the customer confirm they want to renew the plan?"},{"name":"Reason for declining","type":"Selector","description":"If they declined, which reason did they give?","choices":["Too expensive","Switching provider","No longer needed","Wants to think about it"]},{"name":"Renewal date","type":"Text","description":"The date the customer agreed to renew, exactly as they said it.","formatExamples":["12 March","next Tuesday","end of the month"]},{"name":"Seats requested","type":"Number","description":"How many seats did the customer ask for? Leave empty if not discussed.","formatExamples":["5","12"]}]},"call_flow":{"anyOf":[{"$ref":"#/components/schemas/CallFlowInput"},{"type":"null"}],"description":"The call flow. `null` removes it."}}},"UpdateFinnRequest":{"type":"object","description":"Send only the fields to change, at least one. Fields you don't send keep their values, including dashboard settings the API doesn't expose. `null` clears a field, except `name` and `voice`. On the text fields an empty string also clears the value. Any other field returns `400 invalid_request`.","additionalProperties":false,"minProperties":1,"properties":{"name":{"type":"string","minLength":1,"maxLength":100,"description":"Display name. Need not be unique."},"voice":{"type":"string","minLength":1,"maxLength":200,"description":"A `voice_id` from `GET /voices`.\n\nThe TTS provider is derived from this id and is not a field you send: a hyphenated UUID\nis Cartesia (the default), an alphanumeric id without hyphens is ElevenLabs. Sending a\nvoice that does not belong to a provider your plan includes returns `403`.","example":"f786b574-daa5-4673-aa0c-cbe3e8534c02"},"language":{"type":["string","null"],"maxLength":50,"description":"Language label."},"time_zone":{"type":["string","null"],"description":"IANA time zone.","examples":["Asia/Kolkata"]},"use_case":{"type":["string","null"],"maxLength":500,"description":"Short description of what the Finn is for."},"identity_text":{"type":["string","null"],"maxLength":20000,"description":"Who the Finn is and what it does."},"style_guardrails":{"type":["string","null"],"maxLength":20000,"description":"Tone and boundaries."},"response_guidelines":{"type":["string","null"],"maxLength":20000,"description":"How to phrase answers."},"begin_message":{"type":["string","null"],"maxLength":1000,"description":"First line the Finn speaks."},"system_prompt":{"type":["string","null"],"maxLength":100000,"description":"The Finn's main instructions: objective, flow and rules. `null` or an empty string clears it."},"max_call_duration_minutes":{"type":["integer","null"],"enum":[1,3,5,10,15,30,60,null],"description":"Hard limit on call length, in minutes. `null` clears it, which means Finn's default limit of about 8 minutes. Not used by `POST /calls`."},"idle_reminder_message":{"type":["string","null"],"maxLength":1000,"description":"What the Finn says when the caller goes quiet. `null` or an empty string clears it. Not used by `POST /calls`."},"idle_reminder_after_seconds":{"type":["integer","null"],"minimum":5,"maximum":600,"description":"Seconds of silence before the idle reminder, 5 to 600. `null` clears it. Not used by `POST /calls`."},"end_call_on_silence_seconds":{"type":["integer","null"],"enum":[15,20,30,45,60,90,120,null],"description":"Seconds of silence after which the Finn hangs up. `null` clears it. Not used by `POST /calls`."},"handoff_number":{"anyOf":[{"$ref":"#/components/schemas/HandoffNumberInput"},{"type":"null"}]},"post_call_analysis":{"type":["array","null"],"maxItems":50,"items":{"$ref":"#/components/schemas/PostCallAnalysisQuestionInput"},"description":"Questions answered automatically after each call. Up to 50.\n\nFour question types, all shown in the example:\n\n- `Yes/No` — a boolean judgement.\n- `Selector` — one of `choices`. Supply `choices`, or the analyzer has nothing to pick from.\n- `Number` — a numeric answer.\n- `Text` — free text; use `formatExamples` to steer the wording.\n\nRead the answers per call with `GET /calls/{call_uuid}` (`analysis`), or aggregated\nacross the Finn with `GET /finns/{finn_id}/post_call_analysis`.","example":[{"name":"Wants to renew","type":"Yes/No","description":"Did the customer confirm they want to renew the plan?"},{"name":"Reason for declining","type":"Selector","description":"If they declined, which reason did they give?","choices":["Too expensive","Switching provider","No longer needed","Wants to think about it"]},{"name":"Renewal date","type":"Text","description":"The date the customer agreed to renew, exactly as they said it.","formatExamples":["12 March","next Tuesday","end of the month"]},{"name":"Seats requested","type":"Number","description":"How many seats did the customer ask for? Leave empty if not discussed.","formatExamples":["5","12"]}]},"call_flow":{"anyOf":[{"$ref":"#/components/schemas/CallFlowInput"},{"type":"null"}],"description":"The call flow. `null` removes it."}}},"DeletedFinn":{"type":"object","required":["id","deleted"],"properties":{"id":{"type":"string","format":"uuid","description":"The deleted Finn."},"deleted":{"type":"boolean","const":true}}},"Voice":{"type":"object","required":["voice_id","name","description","preview_url","category","labels","languages"],"properties":{"voice_id":{"type":"string","description":"Pass this as `voice` on a Finn. A hyphenated UUID is a Cartesia voice; an alphanumeric id without hyphens is an ElevenLabs voice. You never send the provider itself."},"name":{"type":["string","null"],"description":"Display name."},"description":{"type":"string","description":"Empty string when the provider has none."},"preview_url":{"type":["string","null"],"format":"uri","description":"Audio sample, when the provider offers one."},"category":{"type":["string","null"],"description":"Provider category, for example `premade`."},"labels":{"type":"object","additionalProperties":true,"description":"Provider tags such as accent, gender or age. Empty object when there are none."},"languages":{"type":"array","items":{"type":"string"},"description":"Language codes the voice speaks, for example `[\"hi\", \"en-IN\"]`. Filter on these with `?language=`."}},"description":"A voice your organization can give a Finn."},"PhoneNumber":{"type":"object","required":["id","phone_number","status","provider","country","capabilities","allocation","created_at","updated_at"],"properties":{"id":{"type":"string","format":"uuid","description":"Use it as `phone_number_id` when deploying."},"phone_number":{"type":"string","description":"The number, normally E.164."},"status":{"type":"string","description":"Rented numbers are `Live` once provisioned. Compare case-insensitively; a `pending` or `cancelled` number cannot be deployed."},"provider":{"type":["string","null"],"description":"Carrier: `plivo`, `twilio` or `exotel`."},"country":{"type":["string","null"],"description":"ISO 3166-1 alpha-2 code."},"capabilities":{"type":"object","description":"Each capability is `true`, `false` or `null` when unknown.","required":["voice","sms","mms"],"properties":{"voice":{"type":["boolean","null"]},"sms":{"type":["boolean","null"]},"mms":{"type":["boolean","null"]}}},"allocation":{"anyOf":[{"$ref":"#/components/schemas/PhoneNumberAllocation"},{"type":"null"}],"description":"The Finn the number is assigned to through a deployment. `null` when unassigned. Can still name a Finn whose deployment has ended; that number can be deployed again."},"created_at":{"type":"string","format":"date-time","description":"When the number was added."},"updated_at":{"type":"string","format":"date-time","description":"When the number was last updated."}}},"PhoneNumberAllocation":{"type":"object","required":["finn_id","call_type"],"properties":{"finn_id":{"type":"string","format":"uuid","description":"The assigned Finn."},"call_type":{"type":["string","null"],"description":"`inbound` or `outbound`."}}},"AvailablePhoneNumber":{"type":"object","required":["phone_number","country","region","locality","number_type","capabilities","price","currency"],"properties":{"phone_number":{"type":"string","description":"Pass this unchanged to `POST /phone_numbers`."},"country":{"type":["string","null"],"description":"ISO country code."},"region":{"type":["string","null"],"description":"Region or state."},"locality":{"type":["string","null"],"description":"City."},"number_type":{"type":["string","null"],"description":"Carrier number type, for example `local`."},"capabilities":{"type":"object","description":"Each capability is `true`, `false` or `null` when unknown.","required":["voice","sms","mms"],"properties":{"voice":{"type":["boolean","null"]},"sms":{"type":["boolean","null"]},"mms":{"type":["boolean","null"]}}},"price":{"type":["number","null"],"description":"Rental price in `currency`, for per-number-priced (Exotel) numbers only."},"currency":{"type":["string","null"],"description":"`INR` when `price` is set."}}},"RentPhoneNumberRequest":{"type":"object","required":["phone_number"],"properties":{"phone_number":{"type":"string","description":"A number from `GET /phone_numbers/available`: 6 to 15 digits with an optional `+`. Spaces, dashes and parentheses are stripped."},"provider":{"type":"string","enum":["plivo","twilio","exotel"],"description":"Pass `exotel` to rent an Exotel number. Otherwise the carrier is your organization's configured carrier, or Plivo for `+91` and Twilio for other numbers."},"capabilities":{"type":"object","description":"Capabilities to request. Each value is a boolean; anything else is treated as unspecified.","properties":{"voice":{"type":"boolean"},"sms":{"type":"boolean"},"mms":{"type":"boolean"}}}}},"PhoneNumberRental":{"type":"object","required":["status","phone_number","transaction_id","price","currency","credits_debited","remaining_credits"],"properties":{"status":{"type":"string","const":"provisioning"},"phone_number":{"type":"string","description":"The number being provisioned."},"transaction_id":{"type":"string","description":"Reference for this purchase. Quote it to support."},"price":{"type":"number","description":"Rental price charged."},"currency":{"type":"string","description":"Currency of `price`."},"credits_debited":{"type":"number","description":"Credits taken from the wallet (ledger type `debit_phone_number`)."},"remaining_credits":{"type":"number","description":"Wallet balance after the debit."}}},"ReleasedPhoneNumber":{"type":"object","required":["id","released"],"properties":{"id":{"type":"string","format":"uuid","description":"The released number."},"released":{"type":"boolean","const":true}}},"Audience":{"type":"object","required":["id","name","contact_count","source","is_dynamic","created_at","updated_at"],"properties":{"id":{"type":"string","format":"uuid","description":"Pass it as `audience_id` when deploying."},"name":{"type":"string","description":"Display name."},"contact_count":{"type":"integer","description":"Number of contacts."},"source":{"type":"string","description":"`api` for audiences created through this API; dashboard audiences show their origin, for example `manual_upload`."},"is_dynamic":{"type":"boolean","description":"`true` for audiences kept in sync from an external source. These cannot be deployed through the API."},"created_at":{"type":"string","format":"date-time","description":"When the audience was created."},"updated_at":{"type":["string","null"],"format":"date-time","description":"`null` until the audience is first edited."}}},"ArchivedAudience":{"type":"object","required":["id","archived"],"properties":{"id":{"type":"string","format":"uuid","description":"The archived audience."},"archived":{"type":"boolean","const":true}}},"ContactInput":{"type":"object","description":"A contact to add. Every key other than `name`, `phone_number` and `country_code` becomes a custom field passed with the contact when it is dialed: up to 50 per contact, key 1 to 64 characters without commas, double quotes or line breaks, value up to 500 characters without line breaks. `null` values are skipped. Two contacts are duplicates when `country_code` plus `phone_number` is the same number.","required":["country_code","phone_number"],"properties":{"country_code":{"type":["string","number"],"description":"1 to 4 digits, optionally prefixed with `+`.","examples":["+91"]},"phone_number":{"type":["string","number"],"description":"The number without its country code: 4 to 14 digits, at most 15 together with the country code. Spaces, dashes, dots and parentheses are stripped. A number starting with `+` must begin with its `country_code`, which is then removed.","examples":["9876543210"]},"name":{"type":"string","maxLength":255,"description":"No line breaks."}},"additionalProperties":{"type":["string","number","boolean","null"]}},"Contact":{"type":"object","description":"A stored contact: every value is a string. Contacts added through the API have `phone_number` as digits only and `country_code` with a leading `+`.","properties":{"name":{"type":"string"},"phone_number":{"type":"string"},"country_code":{"type":"string"}},"additionalProperties":{"type":"string"}},"CreateAudienceRequest":{"type":"object","required":["name"],"properties":{"name":{"type":"string","minLength":1,"maxLength":255,"description":"Display name, 1 to 255 characters after trimming."},"contacts":{"type":"array","maxItems":5000,"items":{"$ref":"#/components/schemas/ContactInput"},"description":"Up to 5,000 contacts."}}},"AddContactsRequest":{"type":"object","required":["contacts"],"properties":{"contacts":{"type":"array","minItems":1,"maxItems":5000,"items":{"$ref":"#/components/schemas/ContactInput"},"description":"1 to 5,000 contacts."}}},"Deployment":{"type":"object","description":"A Finn put to work on a phone number: outbound (dials an audience) or inbound (answers calls).","required":["id"],"properties":{"id":{"type":"string","format":"uuid","description":"The deployment ID."},"finn_id":{"type":["string","null"],"format":"uuid","description":"The Finn."},"audience_id":{"type":["string","null"],"format":"uuid","description":"The audience. `null` for inbound."},"phone_number_id":{"type":["string","null"],"format":"uuid","description":"The number. `null` when `max_parallel_calls` is above 1; the number is in `number_pool` instead."},"call_type":{"type":["string","null"],"description":"`outbound` or `inbound`."},"status":{"type":["string","null"],"description":"`pending` (starting), `in_progress` / `active` / `running` (live), `completed` (an outbound deployment that dialed its whole audience), `stopped` or `error`. Deployments managed in the dashboard can report other values; handle unknown values."},"from_number":{"type":["string","null"],"description":"The number dialed from (outbound) or answered on (inbound). `null` when `max_parallel_calls` is above 1."},"number_pool":{"type":["array","null"],"items":{"type":"string"},"description":"Numbers used for parallel calling, set when `max_parallel_calls` is above 1. A deployment created through the API has one number in the pool."},"max_parallel_calls":{"type":["integer","null"],"description":"Concurrent calls. `1`, or `null` right after creation, for one call at a time."},"schedule":{"anyOf":[{"$ref":"#/components/schemas/DeploymentSchedule"},{"type":"null"}],"description":"Start time of a deployment scheduled in the dashboard. Always `null` for deployments created through the API."},"total_calls":{"type":["integer","null"],"description":"Calls the deployment plans to make, normally the audience size. `0` for inbound."},"completed_calls":{"type":["integer","null"],"description":"Calls finished so far."},"recording_enabled":{"type":"boolean","description":"Whether calls are recorded (a billable add-on)."},"created_at":{"type":["string","null"],"format":"date-time"},"updated_at":{"type":["string","null"],"format":"date-time"}}},"DeploymentSchedule":{"type":"object","properties":{"date":{"type":["string","null"],"format":"date"},"start_time":{"type":["string","null"]},"timezone":{"type":["string","null"]}}},"CreateOutboundDeploymentRequest":{"type":"object","required":["finn_id","phone_number_id","audience_id"],"properties":{"finn_id":{"type":"string","format":"uuid","description":"A Finn in your organization."},"phone_number_id":{"type":"string","format":"uuid","description":"A number in your organization to dial from."},"audience_id":{"type":"string","format":"uuid","description":"An audience in your organization."},"max_parallel_calls":{"type":"integer","minimum":1,"maximum":5,"default":1,"description":"Calls the number places at once."},"recording_enabled":{"type":"boolean","default":false,"description":"Turns on the call recording add-on. Each answered call is charged once for recording (ledger type `debit_call_recording`). Recordings are available in the dashboard, not through the API. Cannot be changed after creation."},"schedule":{"type":["object","null"],"description":"Run the deployment later instead of immediately. Omit it, or send null, to start now.\n\n`max_parallel_calls` must be 1 (or omitted) on a scheduled deployment.","required":["date","start_time"],"properties":{"date":{"type":"string","format":"date","description":"`YYYY-MM-DD`.","example":"2026-11-03"},"start_time":{"type":"string","description":"`HH:MM`, 24-hour.","example":"10:00"},"timezone":{"type":"string","default":"UTC","description":"IANA time zone the date and time are read in.","example":"Asia/Kolkata"}}}}},"CreateInboundDeploymentRequest":{"type":"object","required":["finn_id","phone_number_id"],"properties":{"finn_id":{"type":"string","format":"uuid","description":"A Finn in your organization."},"phone_number_id":{"type":"string","format":"uuid","description":"A number in your organization."},"recording_enabled":{"type":"boolean","default":false,"description":"Turns on the call recording add-on (billable per answered call). Cannot be changed after creation."}}},"StoppedDeployment":{"type":"object","required":["id","status"],"properties":{"id":{"type":"string","format":"uuid","description":"The deployment."},"status":{"type":"string","const":"stopped"}}},"StoppedInboundDeployment":{"type":"object","required":["id","finn_id","status"],"properties":{"id":{"type":"string","format":"uuid","description":"The stopped deployment."},"finn_id":{"type":"string","format":"uuid","description":"The Finn."},"status":{"type":"string","const":"stopped"}}},"Wallet":{"type":"object","required":["credits","currency","price_per_credit","monetary_value","low_balance"],"properties":{"credits":{"type":"number","description":"Balance in credits. Can be fractional."},"currency":{"type":["string","null"],"description":"Wallet currency."},"price_per_credit":{"type":["number","null"],"description":"Your effective rate per credit, in `currency`."},"monetary_value":{"type":"number","description":"`credits × price_per_credit`, rounded to 2 decimals."},"low_balance":{"type":"boolean","description":"`true` when the balance is below about 20 calls' worth of credits for your plan."}}},"WalletTransaction":{"type":"object","required":["id","type","credits_delta","new_balance","call_uuid","created_at"],"properties":{"id":{"type":"string","description":"Ledger entry ID: numeric, returned as a string."},"type":{"type":"string","description":"What the entry is for, for example `debit_call_answered`, `debit_call_unanswered`, `credit_refund_call_unanswered`, `debit_call_recording`, `debit_phone_number`, `credit_refund_phone_number`, `debit_sms`, `debit_whatsapp`, `credit_topup`, `credit_signup_grant`, `credit_plan_grant`, `credit_renewal`, `credit_adjustment`, `credit_migration` or `currency_realign`. New types can be added."},"credits_delta":{"type":"number","description":"Negative for debits, positive for credits."},"new_balance":{"type":"number","description":"Balance after this entry."},"call_uuid":{"type":["string","null"],"description":"Reference for the entry: the call's UUID for call charges, `phone:<number>` or `phone:<number>:refund` for number rentals and refunds."},"created_at":{"type":"string","format":"date-time","description":"When the entry was recorded."}}},"CallCompletedEvent":{"type":"object","required":["event","created_at","data"],"properties":{"event":{"type":"string","const":"call.completed"},"created_at":{"type":"string","format":"date-time","description":"When the event was sent."},"data":{"$ref":"#/components/schemas/CallResult"},"event_id":{"type":"string","description":"`call.completed:<call_uuid>`. Sent when your organization has durable webhooks on (the event then goes through the same queue as record events and is retried for hours). De-duplicate on it, or on `data.call_uuid`."},"occurred_at":{"type":"string","format":"date-time","description":"With durable webhooks: when the event was queued."},"org_id":{"type":"string","format":"uuid","description":"With durable webhooks."}}},"CallSummary":{"type":"object","description":"A call as it appears in `GET /calls`. The same fields `GET /calls/{call_uuid}` returns, without the per-call extras.","required":["call_uuid"],"properties":{"call_uuid":{"type":"string","format":"uuid"},"finn_id":{"type":["string","null"],"format":"uuid"},"deployment_id":{"type":["string","null"],"format":"uuid","description":"Null for a single call placed with `POST /calls`."},"status":{"type":["string","null"],"description":"`completed`, `failed`, `busy`, `no-answer`, `canceled`, `voicemail`, `in-progress` or `ringing`."},"disconnect_reason":{"type":["string","null"]},"direction":{"type":["string","null"],"enum":["inbound","outbound",null]},"from_number":{"type":["string","null"]},"to_number":{"type":["string","null"]},"duration_seconds":{"type":["integer","null"],"description":"Null when the call was never answered — not 0, which would mean answered and hung up immediately."},"user_sentiment":{"type":["string","null"]},"call_successful":{"type":["boolean","null"]},"created_at":{"type":["string","null"],"format":"date-time"},"updated_at":{"type":["string","null"],"format":"date-time"}}},"TranscriptTurn":{"type":"object","description":"One turn of a call transcript.","properties":{"role":{"type":["string","null"],"description":"`agent` or `user`."},"text":{"type":"string"},"seconds":{"type":["number","null"],"description":"Seconds into the call."}}},"CallAiAnalysis":{"type":"object","description":"The analyzer's read of the call. Null on a call it has not processed.","properties":{"summary":{"type":["string","null"]},"key_insights":{"type":"array","items":{"type":"string"}},"recommendations":{"type":"array","items":{"type":"string"}},"business_outcome":{"type":["string","null"]},"success":{"type":"object","properties":{"final":{"type":["boolean","string","null"]},"technical_score":{"type":["number","null"]},"conversation_score":{"type":["number","null"]},"business_score":{"type":["number","null"]}}}}},"DeploymentAnalytics":{"type":"object","description":"Aggregate results for one deployment.","properties":{"deployment_id":{"type":"string","format":"uuid"},"calls":{"type":"object","properties":{"total":{"type":"integer"},"answered":{"type":"integer","description":"Calls with a duration above zero."},"completed":{"type":"integer"},"unanswered":{"type":"integer","description":"`failed`, `busy`, `no-answer` or `canceled`."},"voicemail":{"type":"integer"},"successful":{"type":"integer"}}},"rates":{"type":"object","description":"Percentages of `calls.total`. Null when no calls have been placed.","properties":{"answer_rate_percent":{"type":["number","null"]},"success_rate_percent":{"type":["number","null"]},"voicemail_rate_percent":{"type":["number","null"]}}},"duration":{"type":"object","properties":{"total_seconds":{"type":"integer"},"average_seconds":{"type":["integer","null"],"description":"Across answered calls only."}}},"status_breakdown":{"type":"array","items":{"type":"object","properties":{"status":{"type":"string"},"count":{"type":"integer"}}}},"sentiment_breakdown":{"type":"array","items":{"type":"object","properties":{"sentiment":{"type":"string"},"count":{"type":"integer"}}}},"first_call_at":{"type":["string","null"],"format":"date-time"},"last_call_at":{"type":["string","null"],"format":"date-time"}}},"FinnPostCallAnalysis":{"type":"object","description":"A Finn's post-call answers aggregated across its calls.","properties":{"finn_id":{"type":"string","format":"uuid"},"calls_analyzed":{"type":"integer"},"questions":{"type":"array","items":{"type":"object","properties":{"question_name":{"type":"string"},"question_type":{"type":["string","null"]},"total_answers":{"type":"integer"},"needs_review":{"type":"integer","description":"Answers the analyzer was unsure about."},"answers":{"type":"array","description":"Each distinct answer and how often it was given.","items":{"type":"object","properties":{"answer":{"type":["string","null"]},"count":{"type":"integer"}}}}}}}}},"CanvasWorkflow":{"type":"object","description":"The call flow in the shape the dashboard canvas saves. Every tool a step uses must be defined in `tools`. Built-in tools use their canonical name with `builtin: true`: `end_call`, `call_transfer_to_human` (`config.call_transfer_number`, E.164, required), `check_calendar_slot_availability` and `book_calendar_slot` (`config.api_key` and numeric `config.event_type_id` required, optional `config.timezone`), `press_digit`.","required":["nodes"],"properties":{"entry_node":{"type":"string","description":"ID of the first step. Required when `nodes` is not empty."},"nodes":{"type":"array","items":{"type":"object","required":["id","type"],"properties":{"id":{"type":"string","minLength":1},"type":{"type":"string","enum":["llm","tool"],"description":"`llm` is a Conversation step; `tool` is a terminal step that runs one tool."},"prompt":{"type":"string","description":"`llm` only. What the Finn says and does at this step."},"tools":{"type":"array","items":{"type":"string"},"description":"`llm` only. Names from `tools` the step can call."},"tool":{"type":"string","description":"`tool` only. Name from `tools`."},"steer_back":{"type":["string","null"]},"transitions":{"type":"array","items":{"type":"object","required":["to"],"properties":{"to":{"type":"string","description":"A step ID."},"when":{"type":["string","null"],"description":"Condition. `null` means always."}}}}},"additionalProperties":true}},"tools":{"type":"object","additionalProperties":{"type":"object","required":["builtin"],"properties":{"builtin":{"type":"boolean"},"ack":{"type":["string","null"],"description":"Line spoken by terminal tools."},"filler":{"type":["string","null"],"description":"What the Finn says while the tool runs."},"config":{"type":"object","additionalProperties":true,"description":"Tool settings. In responses, `api_key` is `\"[redacted]\"`; sending `\"[redacted]\"` back in a PATCH keeps the stored key."}},"additionalProperties":true}},"settings":{"type":"object","additionalProperties":true,"description":"For example `{ \"endCallReasons\": [\"caller is not interested\"] }`."},"version":{"type":"string","default":"1.0"},"max_repeats":{"type":"integer","default":10}},"additionalProperties":true},"CallFlow":{"type":"object","required":["enabled","canvas_workflow"],"properties":{"enabled":{"type":"boolean","description":"When true, the flow runs on every call instead of `system_prompt`."},"canvas_workflow":{"$ref":"#/components/schemas/CanvasWorkflow"}}},"CallFlowInput":{"type":"object","required":["canvas_workflow"],"additionalProperties":false,"description":"A `canvas_workflow` with no `nodes` removes the call flow, as `null` does.","properties":{"enabled":{"type":"boolean","default":true},"canvas_workflow":{"$ref":"#/components/schemas/CanvasWorkflow"}}},"KnowledgeBaseDocument":{"type":"object","required":["id","name","type","created_at"],"properties":{"id":{"type":"string","format":"uuid"},"name":{"type":"string","description":"Document file name."},"type":{"type":"string","description":"`pdf`, `docx` or `txt`. Text and TXT uploads are stored as `docx`."},"created_at":{"type":"string","format":"date-time"}}},"DoNotCallEntry":{"type":"object","required":["phone_key","phone_number","sources","reason","created_at"],"properties":{"phone_key":{"type":"string","description":"The normalized key Finn matches numbers on. Use it, or the number as you sent it, to remove the entry."},"phone_number":{"type":"string","description":"The number as it was first added."},"sources":{"type":"array","items":{"type":"string","enum":["manual","crm_stage","rep_dialer","call_opt_out","chat_opt_out","import","api","operator","sales_ai"]},"description":"Every reason the number is listed, oldest first. `api` is this API. `call_opt_out`, `chat_opt_out`, `rep_dialer`, `operator` and `sales_ai` record the person asking not to be contacted; those can't be removed through the API."},"reason":{"type":["string","null"],"description":"The first reason given, if any."},"created_at":{"type":"string","format":"date-time","description":"When the number was first listed."}}},"OperatorError":{"type":"object","description":"Errors from Finn Operator (cases and approvals) are passed through as Operator returns them: `code` instead of `error`. Errors raised before the request reaches Operator (key, scope, rate limit, a path ID that isn't a UUID, a missing `answer`) use the usual `Error` shape. Read `error ?? code`.","required":["code","message"],"properties":{"code":{"type":"string","description":"Stable error code: `not_found`, `invalid`, `conflict`, `forbidden`, `cap_reached`, `unauthenticated`, `org_not_enabled`.","examples":["conflict"]},"message":{"type":"string"},"details":{"description":"Extra detail, usually `null`. On `429 cap_reached`: `{ metric, used, limit, day, resets_at, timezone }`."}}},"RedactedValue":{"type":"object","description":"Stands in for a sensitive field's value when the key lacks `crm:sensitive:read`. `hint` (last 4 characters, such as `••••4821`) only for short ID-like values.","required":["redacted","set"],"properties":{"redacted":{"type":"boolean","const":true},"set":{"type":"boolean"},"hint":{"type":"string"}}},"Record":{"type":"object","description":"A CRM record (a contact), in the shape the dashboard uses: camelCase keys. Values of sensitive custom fields are replaced by a `RedactedValue` unless the key has `crm:sensitive:read`. Records in a list carry a few more display fields (such as `pendingSuggestions` and `aiScore`); treat unknown keys as optional.","required":["id","phone","name","stage"],"additionalProperties":true,"properties":{"id":{"type":"string","format":"uuid"},"phone":{"type":"string","description":"E.164, or empty for a record without a number."},"phoneKey":{"type":"string","description":"Normalised number Finn matches on."},"name":{"type":"string","description":"Empty string when unknown."},"email":{"type":"string"},"kind":{"type":"string","description":"The record kind's key. Only when the workspace has kinds turned on (see `GET /record_kinds`)."},"ref_no":{"type":"string","description":"Case ID, when the workspace numbers records and this one has a number."},"stage":{"type":"string","description":"Stage key, such as `new`."},"stageManual":{"type":"boolean"},"substatus":{"type":["string","null"],"description":"Only in workspaces with substatuses."},"substatusLabel":{"type":["string","null"]},"substatusAt":{"type":["string","null"],"format":"date-time"},"leadStatus":{"type":"string","enum":["new","contacted","qualified","converted","lost"],"description":"Absent for a record that isn't a lead."},"leadStatusAt":{"type":"string","format":"date-time"},"leadStatusBy":{"type":"string","description":"`user` or `auto`."},"assigneeId":{"type":["string","null"],"format":"uuid","description":"Owner."},"assigneeName":{"type":["string","null"]},"tags":{"type":"array","items":{"type":"string"}},"sources":{"type":"array","items":{"type":"string"},"description":"How the record reached Finn, such as `api`, `lead` or `csv`."},"segments":{"type":"array","items":{}},"fields":{"type":"object","description":"Built-in fields, such as `company`."},"customFields":{"type":"object","description":"Your workspace's custom fields by key. A sensitive one is a `RedactedValue` without `crm:sensitive:read`.","additionalProperties":{"oneOf":[{},{"$ref":"#/components/schemas/RedactedValue"}]}},"closeReason":{"type":"string"},"aiTemperature":{"type":"string"},"aiTemperatureReason":{"type":"string"},"stats":{"type":"object","properties":{"count":{"type":"integer","description":"Calls."},"connected":{"type":"integer"},"lastContacted":{"type":"string","format":"date-time"},"lastOutcome":{"type":"string"},"lastSentiment":{"type":"string"}}},"createdAt":{"type":"string","format":"date-time"},"updatedAt":{"type":"string","format":"date-time"}}},"RecordKind":{"type":"object","additionalProperties":true,"required":["key","label"],"properties":{"key":{"type":"string","examples":["client"]},"label":{"type":"string"},"label_plural":{"type":"string"},"lead_centric":{"type":"boolean","description":"Records of this kind have lead status and stages."},"is_primary":{"type":"boolean"},"icon":{"type":"string"},"archived":{"type":"boolean"}}},"FieldDefinition":{"type":"object","required":["key","label","type","options","position","archived"],"properties":{"key":{"type":"string","description":"The key used in `custom_fields`."},"label":{"type":"string"},"type":{"type":"string","description":"Field type, such as `text`, `number`, `select` or `datetime`."},"options":{"type":"array","items":{},"description":"Choices for a select field; empty otherwise."},"position":{"type":"integer"},"archived":{"type":"boolean"},"default_timezone":{"type":["string","null"],"description":"`datetime` fields only."},"config":{"type":"object"},"kinds":{"type":"array","items":{"type":"string"},"description":"Record kinds the field applies to, when limited."}}},"CreateRecordRequest":{"type":"object","required":["phone"],"properties":{"phone":{"type":"string","description":"At least 6 digits. A number another record already has returns `409 contact_exists`."},"name":{"type":"string","maxLength":200},"kind":{"type":"string","description":"Record kind key, in workspaces with kinds."},"custom_fields":{"type":"object","description":"Values by field key, checked against `GET /fields`."}}},"UpdateRecordRequest":{"type":"object","description":"Only these keys. Any other key (owner, links, notes...) returns `400 unsupported_field`: those are edits made in Finn.","additionalProperties":false,"properties":{"name":{"type":"string","maxLength":200},"tags":{"type":"array","items":{"type":"string"},"description":"Replaces the tags."},"custom_fields":{"type":"object","description":"`{ key: value }` sets a field, `{ key: null }` clears it. Fields not named are left alone."},"stage":{"type":"string","description":"A stage key of your workspace. A stage check that a person must clear returns `409`; an API key can't override it."},"substatus":{"type":["string","null"],"description":"Workspaces with substatuses only."},"lead_status":{"type":"string","enum":["new","contacted","qualified","converted","lost"]}}},"TimelineItem":{"type":"object","description":"One entry on a record's timeline, newest first. Fields beyond these depend on `kind`.","additionalProperties":true,"required":["id","kind","at"],"properties":{"id":{"type":"string"},"kind":{"type":"string","enum":["call","chat","message","operator","note","form","change","file","view"]},"type":{"type":"string"},"source":{"type":["string","null"]},"channel":{"type":["string","null"]},"direction":{"type":["string","null"]},"at":{"type":"string","format":"date-time"},"text":{"type":["string","null"]},"author":{"type":["string","null"]}}},"Note":{"type":"object","additionalProperties":true,"required":["id","type","text","at"],"properties":{"id":{"type":"string"},"type":{"type":"string","const":"comment"},"text":{"type":"string"},"at":{"type":"string","format":"date-time"},"author":{"type":"string","description":"`API key: <key name>`."}}},"CaseParticipant":{"type":"object","additionalProperties":true,"required":["role"],"properties":{"id":{"type":"string"},"contact_id":{"type":"string"},"role":{"type":"string"},"name":{"type":["string","null"]},"phone":{"type":"string"},"email":{"type":"string"}}},"CaseSummary":{"type":"object","additionalProperties":true,"required":["id","agent_id","agent_name","status","opened_at"],"properties":{"id":{"type":"string","format":"uuid"},"agent_id":{"type":"string","format":"uuid"},"agent_name":{"type":"string"},"version_no":{"type":"integer"},"status":{"type":"string","description":"`open`, `running`, `waiting`, `resolved`, `ended`, `failed` or `cancelled`. Treat as a string: values can be added."},"title":{"type":["string","null"]},"subject_type":{"type":["string","null"]},"subject_ref":{"type":["string","null"]},"stage":{"type":["string","null"]},"owner_id":{"type":["string","null"]},"outcome_key":{"type":["string","null"]},"cost_minor":{"type":"integer","description":"Cost so far in minor currency units."},"participants":{"type":"array","items":{"type":"object","properties":{"role":{"type":"string"},"name":{"type":["string","null"]}}}},"waiting":{"type":["object","null"],"description":"What the case is waiting for: `{ node_id, kind (contact|wait_for|action), role?, event_kinds, since, timeout_at }`."},"opened_at":{"type":"string","format":"date-time"},"closed_at":{"type":["string","null"],"format":"date-time"}}},"Case":{"type":"object","additionalProperties":true,"description":"A case with its threads, steps, waits and resolutions.","required":["id","agent_id","status","opened_at"],"properties":{"id":{"type":"string","format":"uuid"},"org_id":{"type":"string","format":"uuid"},"agent_id":{"type":"string","format":"uuid"},"version_id":{"type":"string"},"version_no":{"type":"integer"},"status":{"type":"string","description":"`open`, `running`, `waiting`, `resolved`, `ended`, `failed` or `cancelled`. Treat as a string: values can be added."},"test":{"type":"boolean"},"title":{"type":["string","null"]},"subject_type":{"type":["string","null"]},"subject_ref":{"type":["string","null"]},"subject":{"description":"What the case is about, as started."},"memory":{"description":"The case's memory."},"stage":{"type":["string","null"]},"owner_id":{"type":["string","null"]},"outcome_key":{"type":["string","null"]},"outcome_reason":{"type":["string","null"]},"error_code":{"type":["string","null"]},"error_message":{"type":["string","null"]},"cost_minor":{"type":"integer"},"currency":{"type":"string"},"event_seq":{"type":"integer","description":"The last event's `seq`."},"next_wake_at":{"type":["string","null"],"format":"date-time"},"opened_at":{"type":"string","format":"date-time"},"closed_at":{"type":["string","null"],"format":"date-time"},"participants":{"type":"array","items":{"$ref":"#/components/schemas/CaseParticipant"}},"threads":{"type":"array","items":{"type":"object"}},"open_waits":{"type":"array","items":{"type":"object"}},"steps":{"type":"array","items":{"type":"object"}},"resolutions":{"type":"array","items":{"type":"object"}}}},"CaseEvent":{"type":"object","additionalProperties":true,"required":["seq","type","actor_type","at"],"properties":{"seq":{"type":"integer"},"type":{"type":"string"},"actor_type":{"type":"string","description":"`system`, `ai`, `user` or `participant`. A case started by an API key records `user` with the key's ID as `actor_id`."},"actor_id":{"type":"string"},"step_id":{"type":"string"},"thread_id":{"type":"string"},"data":{"description":"Event details, or `null`."},"at":{"type":"string","format":"date-time"}}},"StartCaseRequest":{"type":"object","description":"Every field is optional. Other fields are dropped.","properties":{"subject":{"type":"object","description":"What the case is about. One open case per subject: starting again for the same subject delivers into the open case (`result: matched`).","required":["type","ref"],"properties":{"type":{"type":"string","examples":["contact"]},"ref":{"type":"string"},"data":{"type":"object"}}},"start":{"type":"string","description":"The Start step to begin at. Required when the agent's live version has more than one Start."},"title":{"type":"string"},"participants":{"type":"array","items":{"type":"object","required":["role"],"properties":{"role":{"type":"string"},"contact_id":{"type":"string"},"name":{"type":"string"},"phone":{"type":"string","description":"E.164."},"email":{"type":"string"}}}},"memory":{"type":"object","description":"Initial memory, checked against the agent's memory schema."},"data":{"type":"object","description":"Readable in the flow as `trigger.data`."},"owner_id":{"type":"string","description":"Case owner, used when the Start's owner rule takes it from the trigger."},"trigger":{"type":"string","default":"api","description":"Leave it out: it defaults to `api`, which starts the case at one of the agent's API Starts."}}},"CaseStarted":{"type":"object","required":["case_id","trigger_event_id","result","replayed"],"properties":{"case_id":{"type":"string","format":"uuid"},"trigger_event_id":{"type":"string"},"result":{"type":"string","enum":["opened","matched","reopened"],"description":"`opened`: a new case. `matched`: delivered into the open case for the same subject. `reopened`: a recently closed case for the subject was reopened."},"replayed":{"type":"boolean","description":"`true` when this is the stored answer to an earlier request with the same `Idempotency-Key`."}}},"Approval":{"type":"object","additionalProperties":true,"required":["id","case_id","question","allowed_answers","status","created_at"],"properties":{"id":{"type":"string","format":"uuid"},"case_id":{"type":"string","format":"uuid"},"case_title":{"type":["string","null"]},"agent_id":{"type":["string","null"]},"agent_name":{"type":["string","null"]},"node_id":{"type":["string","null"]},"contact_id":{"type":["string","null"]},"question":{"type":"string"},"allowed_answers":{"type":"array","items":{"type":"string"},"description":"The answers `POST /approvals/{id}/decide` accepts, such as `approve` and `reject`."},"status":{"type":"string","description":"`open` while waiting; decided, expired or cancelled after."},"assignee_role":{"type":["string","null"],"description":"The role asked to decide, such as `approver`."},"assignee_user_id":{"type":["string","null"]},"assignee_user_ids":{"type":"array","items":{"type":"string"}},"proposal":{"description":"What the agent proposes to do, or `null`."},"payment":{"description":"Payment details when the approval is for a payment, else `null`. An API key can't decide these."},"payment_hash":{"type":["string","null"]},"why":{"description":"The agent's reason, or `null`."},"final":{"description":"What was finally approved (an edited proposal), or `null`."},"escalated_to":{},"deadline_at":{"type":["string","null"],"format":"date-time"},"reminders":{"type":"integer"},"answer":{"type":["string","null"]},"comment":{"type":["string","null"]},"decided_by":{"type":["string","null"],"description":"Who decided. `api_key:<key_id>` for an API key."},"decided_via":{"type":["string","null"],"description":"`api` for an API key."},"decided_at":{"type":["string","null"],"format":"date-time"},"created_at":{"type":"string","format":"date-time"}}},"WebhookContact":{"type":"object","description":"A record as webhooks send it (snake_case), read when the event is sent. Custom field values are not redacted.","required":["id","phones","emails","stage","stage_kind","tags","fields","custom_fields"],"additionalProperties":true,"properties":{"id":{"type":"string","format":"uuid"},"name":{"type":["string","null"]},"phones":{"type":"array","items":{"type":"string"},"description":"E.164, main number first."},"emails":{"type":"array","items":{"type":"string"}},"owner":{"type":["object","null"],"properties":{"id":{"type":"string"},"name":{"type":["string","null"]}}},"stage":{"type":"string"},"stage_kind":{"type":"string","description":"`open`, `won` or `lost`."},"lead_status":{"type":["string","null"]},"lead_source":{"type":["string","null"]},"sources":{"type":"array","items":{"type":"string"}},"tags":{"type":"array","items":{"type":"string"}},"fields":{"type":"object"},"custom_fields":{"type":"object"},"kind":{"type":"string","description":"Workspaces with record kinds only."},"ref_no":{"type":"string"},"substatus":{"type":["string","null"]},"substatus_label":{"type":["string","null"]},"created_at":{"type":["string","null"],"format":"date-time"},"updated_at":{"type":["string","null"],"format":"date-time"}}},"ContactUpdatedEvent":{"type":"object","description":"A record was edited. Sent once per edit that changed something.","required":["event","event_id","created_at","occurred_at","org_id","data"],"properties":{"event":{"type":"string","const":"contact.updated"},"event_id":{"type":"string","description":"The same for every delivery of one event. De-duplicate on it: delivery is at least once."},"created_at":{"type":"string","format":"date-time","description":"When the body was built."},"occurred_at":{"type":"string","format":"date-time","description":"When it happened."},"org_id":{"type":"string","format":"uuid"},"data":{"type":"object","required":["contact","changed_fields"],"properties":{"contact":{"$ref":"#/components/schemas/WebhookContact"},"changed_fields":{"type":"array","items":{"type":"string"},"description":"What changed, sorted, never the values: `name`, `tags`, `email`, `stage`, `owner`, `lead_status`, `substatus`, `close_reason`, `custom_fields.<key>`."}}}}},"CaseCreatedEvent":{"type":"object","description":"A Finn Operator case opened.","required":["event","event_id","created_at","occurred_at","org_id","data"],"properties":{"event":{"type":"string","const":"case.created"},"event_id":{"type":"string","description":"The same for every delivery of one event. De-duplicate on it: delivery is at least once."},"created_at":{"type":"string","format":"date-time","description":"When the body was built."},"occurred_at":{"type":"string","format":"date-time","description":"When it happened."},"org_id":{"type":"string","format":"uuid"},"data":{"type":"object","required":["contact","case"],"properties":{"contact":{"oneOf":[{"$ref":"#/components/schemas/WebhookContact"},{"type":"null"}],"description":"The case's record, or `null` when it has none (or it was deleted)."},"case":{"type":"object","required":["id"],"properties":{"id":{"type":"string","format":"uuid"},"agent":{"type":["object","null"],"properties":{"id":{"type":"string"},"name":{"type":["string","null"]}}},"status":{"type":["string","null"]},"outcome_key":{"type":["string","null"]},"title":{"type":["string","null"]}}}}}}},"CaseCompletedEvent":{"type":"object","description":"A Finn Operator case reached a final status.","required":["event","event_id","created_at","occurred_at","org_id","data"],"properties":{"event":{"type":"string","const":"case.completed"},"event_id":{"type":"string","description":"The same for every delivery of one event. De-duplicate on it: delivery is at least once."},"created_at":{"type":"string","format":"date-time","description":"When the body was built."},"occurred_at":{"type":"string","format":"date-time","description":"When it happened."},"org_id":{"type":"string","format":"uuid"},"data":{"type":"object","required":["contact","case"],"properties":{"contact":{"oneOf":[{"$ref":"#/components/schemas/WebhookContact"},{"type":"null"}],"description":"The case's record, or `null` when it has none (or it was deleted)."},"case":{"type":"object","required":["id"],"properties":{"id":{"type":"string","format":"uuid"},"agent":{"type":["object","null"],"properties":{"id":{"type":"string"},"name":{"type":["string","null"]}}},"status":{"type":["string","null"]},"outcome_key":{"type":["string","null"]},"title":{"type":["string","null"]}}}}}}},"ApprovalRequestedEvent":{"type":"object","description":"A Finn Operator case asked for an approval. Sent once per approval.","required":["event","event_id","created_at","occurred_at","org_id","data"],"properties":{"event":{"type":"string","const":"approval.requested"},"event_id":{"type":"string","description":"The same for every delivery of one event. De-duplicate on it: delivery is at least once."},"created_at":{"type":"string","format":"date-time","description":"When the body was built."},"occurred_at":{"type":"string","format":"date-time","description":"When it happened."},"org_id":{"type":"string","format":"uuid"},"data":{"type":"object","required":["contact","approval"],"properties":{"contact":{"oneOf":[{"$ref":"#/components/schemas/WebhookContact"},{"type":"null"}],"description":"The case's record, or `null` when it has none (or it was deleted)."},"approval":{"type":"object","required":["id"],"properties":{"id":{"type":"string","format":"uuid"},"case_id":{"type":["string","null"]},"agent":{"type":["object","null"],"properties":{"id":{"type":"string"},"name":{"type":["string","null"]}}},"proposal_summary":{"type":["string","null"]},"due_at":{"type":["string","null"],"format":"date-time"},"link":{"type":["string","null"],"description":"Where to decide it in Finn."}}}}}}},"V1Error":{"type":"object","description":"The unified error body used by newer endpoints (Agents, Chat). Check `error.code`, not `message`. `fields` lists each invalid input; `request_id` matches the `X-Request-Id` header.","required":["error"],"properties":{"error":{"type":"object","required":["code","message","fields","request_id","doc_url"],"properties":{"code":{"type":"string","examples":["invalid_request"]},"message":{"type":"string"},"fields":{"type":"array","description":"Each invalid input, plus context entries (`code: \"context\"`) for extra facts an error carries: `action_url`, `deployment_id`, `contact_id`, `required_credits`, `available_credits`, `transaction_id`, `allowed_answers`, Operator's `details.resets_at`, and so on. A context entry's `field` names the fact and `value` holds it.","items":{"type":"object","properties":{"field":{"type":"string"},"code":{"type":"string","description":"Why the field is not valid, or `context` for a context entry."},"message":{"type":"string"},"value":{"description":"Context entries only: the value itself (a URL, id, number, list or object)."}},"required":["field","code","message"]}},"required_scope":{"type":"string","description":"Only on `insufficient_scope`: the scope (or space-separated scopes) the credential is missing.","examples":["voice:read"]},"request_id":{"type":["string","null"],"description":"Same as the `X-Request-Id` header."},"doc_url":{"type":"string","format":"uri"}}}}},"AgentSummary":{"type":"object","additionalProperties":true,"required":["id","name","status","updated_at"],"properties":{"id":{"type":"string","format":"uuid"},"name":{"type":"string"},"status":{"type":"string","enum":["draft","live","paused","archived"]},"halted":{"type":"boolean"},"live_version_no":{"type":["integer","null"]},"last_published_at":{"type":["string","null"],"format":"date-time"},"open_cases":{"type":"integer","description":"Open live cases; test cases are not counted."},"updated_at":{"type":"string","format":"date-time"},"start_trigger":{"type":"object","description":"How the agent starts: the first Start's trigger `{kind, config}`."}}},"Agent":{"type":"object","additionalProperties":true,"required":["id","name","status","draft_rev"],"properties":{"id":{"type":"string","format":"uuid"},"name":{"type":"string"},"status":{"type":"string","enum":["draft","live","paused","archived"]},"draft":{"type":"object","description":"The draft graph (nodes and edges). Edit it with PUT /agents/{id}/draft."},"draft_rev":{"type":"integer","description":"Draft revision, also the `ETag`. Send it as `If-Match` to avoid overwriting someone else's edit."}}},"AgentList":{"type":"object","required":["data","next_cursor","has_more"],"properties":{"data":{"type":"array","items":{"$ref":"#/components/schemas/AgentSummary"}},"next_cursor":{"type":["string","null"]},"has_more":{"type":"boolean"}}},"AgentDraftResult":{"type":"object","required":["name","graph"],"properties":{"name":{"type":"string"},"graph":{"type":"object","description":"A draft graph. Pass it as `draft` to POST /agents to keep it."},"notes":{"type":"array","items":{"type":"string"}},"problems":{"type":"array","items":{"type":"object","additionalProperties":true}}}},"ChatList":{"type":"object","required":["data","next_cursor","has_more"],"properties":{"data":{"type":"array","items":{"type":"object","additionalProperties":true}},"next_cursor":{"type":["string","null"]},"has_more":{"type":"boolean"}}},"WebhookEndpoint":{"type":"object","description":"Where Finn sends the events it lists. The signing secret is never returned here: only `POST /webhook_endpoints` returns it, once.","required":["id","object","url","events","enabled","created_at","updated_at","last_success_at","last_failure_at","failure_count"],"properties":{"id":{"type":"string","format":"uuid"},"object":{"type":"string","const":"webhook_endpoint"},"url":{"type":"string","format":"uri","description":"`https`, resolving to a public address."},"events":{"type":"array","items":{"type":"string","enum":["call.completed","contact.created","contact.assigned","contact.stage_changed","contact.substatus_changed","contact.section_row_added","contact.link_changed","lead.created","lead.status_changed","document.status_changed","contact.updated","case.created","case.completed","approval.requested"]}},"enabled":{"type":"boolean","description":"`false`: nothing is sent. Finn switches an endpoint off itself after many failed sends with no success for a day (organizations with durable webhooks); set it back to `true` to resume."},"created_at":{"type":"string","format":"date-time"},"updated_at":{"type":"string","format":"date-time"},"last_success_at":{"type":["string","null"],"format":"date-time","description":"Last delivered event."},"last_failure_at":{"type":["string","null"],"format":"date-time","description":"Last failed send."},"failure_count":{"type":"integer","description":"Failed sends since the last success."}}},"WebhookEndpointWithSecret":{"allOf":[{"$ref":"#/components/schemas/WebhookEndpoint"},{"type":"object","required":["secret"],"properties":{"secret":{"type":"string","pattern":"^whsec_","description":"The signing secret. Shown only in this response: store it now. Verify `X-Finn-Signature` with it (see `call.completed`)."}}}]},"WebhookEndpointList":{"type":"object","required":["data","next_cursor","has_more"],"properties":{"data":{"type":"array","items":{"$ref":"#/components/schemas/WebhookEndpoint"}},"next_cursor":{"type":["string","null"]},"has_more":{"type":"boolean"}}},"CreateWebhookEndpointRequest":{"type":"object","required":["url"],"additionalProperties":false,"properties":{"url":{"type":"string","format":"uri","maxLength":2048,"description":"`https`, resolving to a public address; redirects are not followed."},"events":{"type":"array","minItems":1,"items":{"type":"string","enum":["call.completed","contact.created","contact.assigned","contact.stage_changed","contact.substatus_changed","contact.section_row_added","contact.link_changed","lead.created","lead.status_changed","document.status_changed","contact.updated","case.created","case.completed","approval.requested"]},"description":"Events to send here. Default `[\"call.completed\"]`. `GET /events` lists them."},"enabled":{"type":"boolean","default":true}}},"UpdateWebhookEndpointRequest":{"type":"object","minProperties":1,"additionalProperties":false,"description":"Send only what changes. `events` replaces the list.","properties":{"url":{"type":"string","format":"uri","maxLength":2048},"events":{"type":"array","minItems":1,"items":{"type":"string","enum":["call.completed","contact.created","contact.assigned","contact.stage_changed","contact.substatus_changed","contact.section_row_added","contact.link_changed","lead.created","lead.status_changed","document.status_changed","contact.updated","case.created","case.completed","approval.requested"]}},"enabled":{"type":"boolean","description":"`false` stops sending; `true` resumes and clears `failure_count`."}}},"DeletedWebhookEndpoint":{"type":"object","required":["id","object","deleted"],"properties":{"id":{"type":"string","format":"uuid"},"object":{"type":"string","const":"webhook_endpoint"},"deleted":{"type":"boolean","const":true}}},"WebhookDelivery":{"type":"object","description":"One event for one endpoint.","required":["id","object","endpoint_id","event","event_id","call_uuid","status","attempts","response_status","error","created_at","delivered_at","next_attempt_at","redelivery_of"],"properties":{"id":{"type":"string","format":"uuid"},"object":{"type":"string","const":"webhook_delivery"},"endpoint_id":{"type":"string","format":"uuid"},"event":{"type":"string","enum":["call.completed","contact.created","contact.assigned","contact.stage_changed","contact.substatus_changed","contact.section_row_added","contact.link_changed","lead.created","lead.status_changed","document.status_changed","contact.updated","case.created","case.completed","approval.requested"]},"event_id":{"type":["string","null"],"description":"The body's `event_id` (a redelivery carries the original event's). `null` for a `call.completed` sent the in-process way."},"call_uuid":{"type":["string","null"],"format":"uuid","description":"For `call.completed`."},"redelivery_of":{"type":["string","null"],"format":"uuid","description":"For a redelivery (`POST /webhook_endpoints/{id}/deliveries/{delivery_id}/redeliver`): the delivery it repeats."},"status":{"type":"string","enum":["pending","delivered","failed"]},"attempts":{"type":"integer","description":"HTTP requests made so far."},"response_status":{"type":["integer","null"],"description":"Your endpoint's last HTTP status."},"error":{"type":["string","null"],"description":"Why the last send failed (`HTTP 500`, a timeout), or why the event was not sent: `expired` (not sent within 24 hours), `endpoint_disabled`, `contact_deleted`."},"created_at":{"type":"string","format":"date-time"},"delivered_at":{"type":["string","null"],"format":"date-time","description":"When your endpoint answered 2xx."},"next_attempt_at":{"type":["string","null"],"format":"date-time","description":"While `pending`: when it is (re)tried."}}},"WebhookDeliveryList":{"type":"object","required":["data","next_cursor","has_more"],"properties":{"data":{"type":"array","items":{"$ref":"#/components/schemas/WebhookDelivery"}},"next_cursor":{"type":["string","null"]},"has_more":{"type":"boolean"}}},"EventType":{"type":"object","required":["object","name","group","description"],"properties":{"object":{"type":"string","const":"event_type"},"name":{"type":"string","enum":["call.completed","contact.created","contact.assigned","contact.stage_changed","contact.substatus_changed","contact.section_row_added","contact.link_changed","lead.created","lead.status_changed","document.status_changed","contact.updated","case.created","case.completed","approval.requested"]},"group":{"type":"string","enum":["calls","crm","operator"]},"description":{"type":"string"}}},"EventTypeList":{"type":"object","required":["data","next_cursor","has_more"],"properties":{"data":{"type":"array","items":{"$ref":"#/components/schemas/EventType"}},"next_cursor":{"type":"null"},"has_more":{"type":"boolean","const":false}}},"WebhookChange":{"type":"object","required":["from","to","by","user"],"description":"What changed. `by` is `person` (someone in Finn, named in `user`) or `auto` (a rule, a call, an agent, the API).","properties":{"from":{"description":"The value before (`null` when none)."},"to":{"description":"The value after (`null` when cleared)."},"by":{"type":"string","enum":["person","auto"]},"user":{"type":["object","null"],"properties":{"id":{"type":"string","format":"uuid"},"name":{"type":["string","null"]}}}}},"ContactCreatedEvent":{"type":"object","required":["event","event_id","created_at","occurred_at","org_id","data"],"properties":{"event":{"type":"string","const":"contact.created"},"event_id":{"type":"string","description":"The same for every delivery of one event. De-duplicate on it: delivery is at least once."},"created_at":{"type":"string","format":"date-time","description":"When the body was built."},"occurred_at":{"type":"string","format":"date-time","description":"When it happened."},"org_id":{"type":"string","format":"uuid"},"data":{"type":"object","required":["contact"],"properties":{"contact":{"$ref":"#/components/schemas/WebhookContact"}}}}},"ContactAssignedEvent":{"type":"object","required":["event","event_id","created_at","occurred_at","org_id","data"],"properties":{"event":{"type":"string","const":"contact.assigned"},"event_id":{"type":"string","description":"The same for every delivery of one event. De-duplicate on it: delivery is at least once."},"created_at":{"type":"string","format":"date-time","description":"When the body was built."},"occurred_at":{"type":"string","format":"date-time","description":"When it happened."},"org_id":{"type":"string","format":"uuid"},"data":{"type":"object","required":["contact","change"],"properties":{"contact":{"$ref":"#/components/schemas/WebhookContact"},"change":{"$ref":"#/components/schemas/WebhookChange"}}}}},"ContactStageChangedEvent":{"type":"object","required":["event","event_id","created_at","occurred_at","org_id","data"],"properties":{"event":{"type":"string","const":"contact.stage_changed"},"event_id":{"type":"string","description":"The same for every delivery of one event. De-duplicate on it: delivery is at least once."},"created_at":{"type":"string","format":"date-time","description":"When the body was built."},"occurred_at":{"type":"string","format":"date-time","description":"When it happened."},"org_id":{"type":"string","format":"uuid"},"data":{"type":"object","required":["contact","change"],"properties":{"contact":{"$ref":"#/components/schemas/WebhookContact"},"change":{"$ref":"#/components/schemas/WebhookChange"},"substatus":{"type":"object","description":"Organizations with substatuses only: the substatus before and after.","properties":{"from":{"type":["string","null"]},"to":{"type":["string","null"]}}}}}}},"ContactSubstatusChangedEvent":{"type":"object","required":["event","event_id","created_at","occurred_at","org_id","data"],"properties":{"event":{"type":"string","const":"contact.substatus_changed"},"event_id":{"type":"string","description":"The same for every delivery of one event. De-duplicate on it: delivery is at least once."},"created_at":{"type":"string","format":"date-time","description":"When the body was built."},"occurred_at":{"type":"string","format":"date-time","description":"When it happened."},"org_id":{"type":"string","format":"uuid"},"data":{"type":"object","required":["contact","substatus"],"properties":{"contact":{"$ref":"#/components/schemas/WebhookContact"},"substatus":{"type":"object","required":["stage","from","to"],"properties":{"stage":{"type":["string","null"]},"from":{"type":["string","null"]},"to":{"type":["string","null"]},"by":{"type":["string","null"],"description":"`user`, or what moved it (a rule, a call)."},"via":{"type":["string","null"]}}}}}}},"ContactSectionRowAddedEvent":{"type":"object","required":["event","event_id","created_at","occurred_at","org_id","data"],"properties":{"event":{"type":"string","const":"contact.section_row_added"},"event_id":{"type":"string","description":"The same for every delivery of one event. De-duplicate on it: delivery is at least once."},"created_at":{"type":"string","format":"date-time","description":"When the body was built."},"occurred_at":{"type":"string","format":"date-time","description":"When it happened."},"org_id":{"type":"string","format":"uuid"},"data":{"type":"object","required":["contact","section_row"],"properties":{"contact":{"$ref":"#/components/schemas/WebhookContact"},"section_row":{"type":"object","required":["field_key","row_id","data"],"properties":{"field_key":{"type":"string","description":"The section's field key."},"row_id":{"type":"string"},"data":{"type":"object","additionalProperties":true,"description":"The row's values by field key."},"supersedes_id":{"type":["string","null"],"description":"The row this one replaces, if any."},"meta":{"type":["object","null"],"additionalProperties":true}}}}}}},"ContactLinkChangedEvent":{"type":"object","required":["event","event_id","created_at","occurred_at","org_id","data"],"properties":{"event":{"type":"string","const":"contact.link_changed"},"event_id":{"type":"string","description":"The same for every delivery of one event. De-duplicate on it: delivery is at least once."},"created_at":{"type":"string","format":"date-time","description":"When the body was built."},"occurred_at":{"type":"string","format":"date-time","description":"When it happened."},"org_id":{"type":"string","format":"uuid"},"data":{"type":"object","required":["contact","link"],"properties":{"contact":{"$ref":"#/components/schemas/WebhookContact"},"link":{"type":"object","required":["field_key","from_contact_id","old_to","new_to"],"properties":{"field_key":{"type":"string"},"from_contact_id":{"type":"string","format":"uuid"},"old_to":{"type":["string","null"],"format":"uuid","description":"The record it pointed to before (`null`: none)."},"new_to":{"type":["string","null"],"format":"uuid","description":"The record it points to now (`null`: the link ended)."}}}}}}},"LeadCreatedEvent":{"type":"object","required":["event","event_id","created_at","occurred_at","org_id","data"],"properties":{"event":{"type":"string","const":"lead.created"},"event_id":{"type":"string","description":"The same for every delivery of one event. De-duplicate on it: delivery is at least once."},"created_at":{"type":"string","format":"date-time","description":"When the body was built."},"occurred_at":{"type":"string","format":"date-time","description":"When it happened."},"org_id":{"type":"string","format":"uuid"},"data":{"type":"object","required":["contact","lead"],"properties":{"contact":{"$ref":"#/components/schemas/WebhookContact"},"lead":{"type":"object","required":["via","source"],"properties":{"via":{"type":["string","null"],"description":"How it arrived, e.g. `form`, `meta`, `api`, `import`, `call`."},"source":{"type":["string","null"],"description":"The lead source, when known."}}}}}}},"LeadStatusChangedEvent":{"type":"object","required":["event","event_id","created_at","occurred_at","org_id","data"],"properties":{"event":{"type":"string","const":"lead.status_changed"},"event_id":{"type":"string","description":"The same for every delivery of one event. De-duplicate on it: delivery is at least once."},"created_at":{"type":"string","format":"date-time","description":"When the body was built."},"occurred_at":{"type":"string","format":"date-time","description":"When it happened."},"org_id":{"type":"string","format":"uuid"},"data":{"type":"object","required":["contact","change"],"properties":{"contact":{"$ref":"#/components/schemas/WebhookContact"},"change":{"$ref":"#/components/schemas/WebhookChange"}}}}},"DocumentStatusChangedEvent":{"type":"object","required":["event","event_id","created_at","occurred_at","org_id","data"],"properties":{"event":{"type":"string","const":"document.status_changed"},"event_id":{"type":"string","description":"The same for every delivery of one event. De-duplicate on it: delivery is at least once."},"created_at":{"type":"string","format":"date-time","description":"When the body was built."},"occurred_at":{"type":"string","format":"date-time","description":"When it happened."},"org_id":{"type":"string","format":"uuid"},"data":{"type":"object","required":["contact","document"],"properties":{"contact":{"$ref":"#/components/schemas/WebhookContact"},"document":{"type":"object","required":["type","from","to","via","requested_count"],"properties":{"type":{"type":"object","properties":{"id":{"type":"string"},"key":{"type":"string"},"name":{"type":"string"}}},"from":{"type":["string","null"],"enum":["missing","received","verified","rejected",null]},"to":{"type":"string","enum":["missing","received","verified","rejected"]},"via":{"type":["string","null"],"description":"What changed it (a person, an upload link, the AI check)."},"reason":{"type":"string","description":"Why it was rejected, when given."},"file_id":{"type":"string"},"version":{"type":"integer"},"requested_count":{"type":"integer","description":"How many times it has been asked for."}}}}}}},"Organization":{"type":"object","required":["id","name","plan","created_at","updated_at"],"properties":{"id":{"type":"string","format":"uuid"},"name":{"type":["string","null"]},"plan":{"type":["string","null"],"description":"The plan name, e.g. `pro`."},"created_at":{"type":["string","null"],"format":"date-time"},"updated_at":{"type":["string","null"],"format":"date-time"}}},"OrganizationResponse":{"type":"object","required":["data"],"properties":{"data":{"$ref":"#/components/schemas/Organization"}}},"OrganizationMember":{"type":"object","required":["user_id","name","email","role"],"properties":{"user_id":{"type":"string","format":"uuid"},"name":{"type":["string","null"]},"email":{"type":["string","null"]},"role":{"type":["string","null"],"description":"The member's role in this organization, lower case, e.g. `admin` or `member`."}}},"OrganizationMemberList":{"type":"object","required":["data","next_cursor","has_more"],"properties":{"data":{"type":"array","items":{"$ref":"#/components/schemas/OrganizationMember"}},"next_cursor":{"type":["string","null"]},"has_more":{"type":"boolean"}}},"IntegrationConnection":{"type":"object","required":["id","provider","provider_name","category","label","status","last_error","last_tested_at","last_activity_at","webhook_configured","created_at"],"properties":{"id":{"type":"string","format":"uuid"},"provider":{"type":"string","examples":["shopify"]},"provider_name":{"type":"string"},"category":{"type":["string","null"],"examples":["store"]},"label":{"type":["string","null"]},"status":{"type":"string","enum":["pending","connected","expired","error"]},"last_error":{"type":["string","null"]},"last_tested_at":{"type":["string","null"],"format":"date-time"},"last_activity_at":{"type":["string","null"],"format":"date-time","description":"The latest agent tool call or inbound event on this connection."},"webhook_configured":{"type":"boolean"},"created_at":{"type":"string","format":"date-time"}}},"IntegrationConnectionList":{"type":"object","required":["data","next_cursor","has_more"],"properties":{"data":{"type":"array","items":{"$ref":"#/components/schemas/IntegrationConnection"}},"next_cursor":{"type":["string","null"]},"has_more":{"type":"boolean"}}},"ApiKeyInfo":{"type":"object","description":"An API key's metadata. Never the key or its hash.","required":["id","name","prefix","scopes","product_scopes","created_by","created_at","last_used_at","revoked_at","expires_at","expiring_soon"],"properties":{"id":{"type":"string","format":"uuid"},"name":{"type":["string","null"]},"prefix":{"type":"string","description":"The first characters of the key, to recognise it.","examples":["finn_live_ab12"]},"scopes":{"type":["array","null"],"items":{"type":"string"},"description":"The scopes as the key was made with them; null for a key made before scopes existed."},"product_scopes":{"type":"array","items":{"type":"string"},"description":"What the key can do today, as product scopes."},"created_by":{"type":["string","null"],"format":"uuid"},"created_at":{"type":"string","format":"date-time"},"last_used_at":{"type":["string","null"],"format":"date-time"},"revoked_at":{"type":["string","null"],"format":"date-time","description":"Set when the key was revoked; a revoked key no longer works."},"expires_at":{"type":["string","null"],"format":"date-time","description":"When the key stops working (30, 90, 180 or 365 days after it was made, 90 by default; a rotated key's grace period can end it sooner). Null for a key made before keys expired; it never expires. An expired key gets 401 key_expired."},"expiring_soon":{"type":"boolean","description":"True when the key has not been revoked and expires within 14 days. Rotate it before then."}}},"ApiKeyInfoList":{"type":"object","required":["data","next_cursor","has_more"],"properties":{"data":{"type":"array","items":{"$ref":"#/components/schemas/ApiKeyInfo"}},"next_cursor":{"type":["string","null"]},"has_more":{"type":"boolean"}}},"FinnSummary":{"type":"object","properties":{"id":{"type":"string","format":"uuid"},"name":{"type":["string","null"]},"status":{"type":["string","null"]}}},"FinnFlowSummary":{"type":"object","description":"The call flow canvas, summarized. It runs instead of the prompt only when `enabled` is true.","properties":{"enabled":{"type":"boolean"},"nodes":{"type":"array","items":{"type":"object","properties":{"id":{"type":"string"},"type":{"type":["string","null"]},"label":{"type":["string","null"]}}}},"edges":{"type":"array","items":{"type":"object","properties":{"from":{"type":"string"},"to":{"type":"string"},"label":{"type":["string","null"]}}}},"updated_at":{"type":["string","null"],"format":"date-time"}}},"FinnFlow":{"type":"object","properties":{"finn":{"$ref":"#/components/schemas/FinnSummary"},"flow":{"$ref":"#/components/schemas/FinnFlowSummary"}}},"FinnHandbook":{"type":"object","description":"Agent handbook switches. The prompt text each one adds is Finn's own.","properties":{"personality":{"type":"object","properties":{"defaultToneEnabled":{"type":"boolean"},"defaultTone":{"type":"string","enum":["professional","professional_conversational"]},"naturalFillerWords":{"type":"boolean"},"highEmpathy":{"type":"boolean"}}},"accuracy":{"type":"object","properties":{"echoVerification":{"type":"boolean"},"natoPhonetic":{"type":"boolean"},"speechNormalization":{"type":"boolean"},"smartMatching":{"type":"boolean"}}},"trustSafety":{"type":"object","properties":{"aiDisclosure":{"type":"boolean"},"scopeBoundaries":{"type":"boolean"}}}}},"FinnSettings":{"type":"object","properties":{"finn":{"$ref":"#/components/schemas/FinnSummary"},"handbook":{"$ref":"#/components/schemas/FinnHandbook"},"handbook_saved":{"type":"boolean","description":"False when the handbook was never saved and the defaults apply."},"speech_settings":{"type":["object","null"],"additionalProperties":true},"flow":{"$ref":"#/components/schemas/FinnFlowSummary"},"task_settings":{"type":"object","additionalProperties":true,"description":"Per call flow task, keyed by task id."},"knowledge":{"type":"array","items":{"type":"object","properties":{"name":{"type":"string"},"type":{"type":["string","null"]},"added":{"type":["string","null"],"format":"date-time"}}}}}},"FinnVariableUse":{"type":"object","properties":{"name":{"type":"string"},"found_in":{"type":"array","items":{"type":"string"},"description":"Prompt fields that mention it, e.g. `Workflow prompt`, `Opening line`."}}},"FinnVariables":{"type":"object","properties":{"finn":{"$ref":"#/components/schemas/FinnSummary"},"variables":{"type":"object","properties":{"placeholders":{"type":"array","items":{"$ref":"#/components/schemas/FinnVariableUse"},"description":"`{name}` placeholders, filled per contact from the audience at call time."},"dynamic_variables":{"type":"array","items":{"$ref":"#/components/schemas/FinnVariableUse"},"description":"`{{ name }}` dynamic variables."},"mapping":{"type":"object","additionalProperties":{"type":"string"},"description":"Placeholder to CSV column."},"mapping_enabled":{"type":"boolean"},"post_call_fields":{"type":"array","items":{"type":"object","properties":{"name":{"type":"string"},"type":{"type":["string","null"]},"description":{"type":["string","null"]},"choices":{"type":"array","items":{"type":"string"}}}}}}}}},"UpdateFinnVariablesRequest":{"type":"object","minProperties":1,"additionalProperties":false,"properties":{"set_mapping":{"type":"object","additionalProperties":{"type":"string"},"description":"Placeholder to CSV column pairs to add or change; others are kept. Each must be a placeholder in the prompt."},"remove_mapping":{"type":"array","items":{"type":"string"}},"mapping_enabled":{"type":"boolean"},"post_call_fields":{"type":"array","minItems":1,"description":"Replaces the whole list. A field keeps its id (and past answers) when its name is unchanged.","items":{"type":"object","required":["name","type"],"properties":{"name":{"type":"string"},"type":{"type":"string","enum":["Text","Selector","Yes/No","Number"]},"description":{"type":"string"},"choices":{"type":"array","items":{"type":"string"},"description":"Selector only, at least 2."}}}}}},"FinnShareLink":{"type":"object","properties":{"url":{"type":"string","format":"uri"},"enabled":{"type":"boolean"},"expires_at":{"type":["string","null"],"format":"date-time"},"created":{"type":"boolean","description":"True when this call made the link."}}},"PendingApproval":{"type":"object","description":"The answer to a write held for a person to approve (`202`).","required":["status","approval_id","approve_url","expires_at","action","summary"],"properties":{"status":{"type":"string","const":"pending_approval"},"approval_id":{"type":"string","format":"uuid","description":"Read it with `GET /approvals/{id}`."},"approve_url":{"type":"string","format":"uri","description":"The page in Finn where a person approves or declines it."},"expires_at":{"type":"string","format":"date-time","description":"When it expires if nobody decides."},"action":{"type":"string","enum":["call.create","phone_number.rent","deployment.create","chat.start","chat.send","case.start"]},"summary":{"type":["string","null"],"description":"What it will do, as the approver sees it."}}},"ApiHold":{"type":"object","description":"A write held for approval, as `GET /approvals/{id}` returns it to the credential that made it. Another credential gets 404.","required":["id","object","source","status","action","class","summary","approve_url","expires_at","created_at","decided_at","finished_at","outcome_unknown","result"],"properties":{"id":{"type":"string","format":"uuid"},"object":{"type":"string","const":"approval"},"source":{"type":"string","const":"api_hold"},"status":{"type":"string","enum":["pending","running","succeeded","failed","declined","expired"]},"action":{"type":"string","enum":["call.create","phone_number.rent","deployment.create","chat.start","chat.send","case.start"]},"class":{"type":"string","enum":["outward","money"],"description":"`money` spends; `outward` contacts someone outside the organization."},"summary":{"type":["string","null"]},"approve_url":{"type":["string","null"],"format":"uri","description":"Set while `pending`."},"expires_at":{"type":"string","format":"date-time"},"created_at":{"type":"string","format":"date-time"},"decided_at":{"type":["string","null"],"format":"date-time"},"finished_at":{"type":["string","null"],"format":"date-time"},"outcome_unknown":{"type":"boolean","description":"The approved run started more than 10 minutes ago and has not finished; a person may retry it in Finn."},"result":{"type":["object","null"],"description":"The approved run's own answer (`status` is its HTTP status), once it finished.","properties":{"status":{"type":"integer"},"body":{}}}}},"UsageReport":{"type":"object","required":["data"],"properties":{"data":{"type":"object","required":["from","to","group_by","timezone","credentials","spend","limits","coverage"],"properties":{"from":{"type":"string","format":"date"},"to":{"type":"string","format":"date"},"group_by":{"type":"string","enum":["day"]},"timezone":{"type":"string","enum":["UTC"]},"credentials":{"type":"array","description":"Credentials with activity in the range, busiest first.","items":{"type":"object","required":["credential_id","credential_kind","channel","name","totals","days"],"properties":{"credential_id":{"type":"string","format":"uuid"},"credential_kind":{"type":["string","null"],"enum":["org_key","mcp_key","oauth_token",null]},"channel":{"type":["string","null"],"description":"`api`, `mcp` or `oauth`."},"name":{"type":["string","null"],"description":"The key's name, or the OAuth client's name."},"totals":{"type":"object","required":["calls","holds_created"],"properties":{"calls":{"type":"object","required":["read","write","refused"],"properties":{"read":{"type":"integer","description":"Reads the audit log records (see `coverage.reads`)."},"write":{"type":"integer","description":"Successful writes (2xx)."},"refused":{"type":"integer","description":"Requests refused with 403."}}},"holds_created":{"type":"integer"}}},"days":{"type":"array","items":{"type":"object","required":["calls","holds_created","date"],"properties":{"date":{"type":"string","format":"date"},"calls":{"type":"object","required":["read","write","refused"],"properties":{"read":{"type":"integer","description":"Reads the audit log records (see `coverage.reads`)."},"write":{"type":"integer","description":"Successful writes (2xx)."},"refused":{"type":"integer","description":"Requests refused with 403."}}},"holds_created":{"type":"integer"}}}}}}},"spend":{"type":"object","required":["scope","totals","days"],"properties":{"scope":{"type":"string","enum":["organization"]},"totals":{"type":"object","required":["credits_spent","credits_added","transactions"],"properties":{"credits_spent":{"type":"number","description":"Credits debited (sum of negative `credits_delta`, as a positive number)."},"credits_added":{"type":"number","description":"Credits added (top-ups, refunds)."},"transactions":{"type":"integer"}}},"days":{"type":"array","items":{"type":"object","required":["date","credits_spent","credits_added","transactions"],"properties":{"date":{"type":"string","format":"date"},"credits_spent":{"type":"number","description":"Credits debited (sum of negative `credits_delta`, as a positive number)."},"credits_added":{"type":"number","description":"Credits added (top-ups, refunds)."},"transactions":{"type":"integer"}}}}}},"limits":{"type":"object","required":["per_minute","daily_caps"],"properties":{"per_minute":{"type":"object","properties":{"read":{"type":"integer","description":"Usual read limit per key per minute (300)."},"write":{"type":"integer","description":"Usual write limit per key per minute (120); some writes are lower."},"credential_ceiling":{"type":["integer","null"],"description":"Overall requests per credential per minute across the API, where per-credential limits are on; else null."}}},"daily_caps":{"type":["object","null"],"description":"Where per-credential limits are on: the organization's daily caps (UTC) for sends by AI apps, with today's use (null if it could not be read).","additionalProperties":{"type":"object","properties":{"cap":{"type":"integer"},"used_today":{"type":["integer","null"]}}}}}},"coverage":{"type":"object","description":"What each count covers: `all`, `sensitive_reads_only`, `organization`, or `unavailable` where the source is not set up yet.","properties":{"reads":{"type":"string"},"writes":{"type":"string"},"holds":{"type":"string"},"spend":{"type":"string"}}}}}}},"WebhookTestResult":{"type":"object","description":"The outcome of one test send.","required":["object","endpoint_id","event","delivered","response_status","error","duration_ms"],"properties":{"object":{"type":"string","const":"webhook_test"},"endpoint_id":{"type":"string","format":"uuid"},"event":{"type":"string","const":"webhook.test"},"delivered":{"type":"boolean","description":"Your endpoint answered `2xx`."},"response_status":{"type":["integer","null"],"description":"Your endpoint's HTTP status; `null` when no answer came."},"error":{"type":["string","null"],"description":"Why it failed (`HTTP 500`, a timeout, a URL that is not public `https`)."},"duration_ms":{"type":"integer","description":"How long the send took."}}},"WebhookEndpointRotated":{"allOf":[{"$ref":"#/components/schemas/WebhookEndpoint"},{"type":"object","required":["secret","previous_secret_expires_at"],"properties":{"secret":{"type":"string","pattern":"^whsec_","description":"The new signing secret. Shown only in this response: store it now."},"previous_secret_expires_at":{"type":"string","format":"date-time","description":"Until then events are signed with the previous secret as well (a second `v1=` in `X-Finn-Signature`)."}}}]}},"parameters":{"IdempotencyKey":{"name":"Idempotency-Key","in":"header","required":false,"description":"Your unique key for this request, 1 to 255 visible ASCII characters (a UUID works well). A retry with the same key and the same body within 24 hours returns the first response instead of doing the work again. See [api-idempotency](/docs/api-idempotency).","schema":{"type":"string","minLength":1,"maxLength":255}},"FinnVersion":{"name":"Finn-Version","in":"header","required":false,"description":"Pins the API version for this request, a date such as `2026-11-01`. Overrides the version pinned on the API key; without either, the current version is used. An unknown version returns `400 invalid_finn_version`. Honoured only while version pinning is switched on (beta); otherwise ignored.","schema":{"type":"string","pattern":"^\\d{4}-\\d{2}-\\d{2}$","example":"2026-11-01"}},"FinnOrg":{"name":"Finn-Org","in":"header","required":false,"description":"The organization this request acts in (its id). An OAuth token (`finn_oat_`) or personal MCP key (`finn_mcp_`) that can act in more than one organization must name one, else 400 `org_required`; it must be an organization the person is a member of and the token is allowed in. With an org API key (`finn_live_`) leave it out, or send the key's own organization: any other answers 403 `org_mismatch`. Not a uuid: 400 `invalid_org`.","schema":{"type":"string","format":"uuid"}}}}}