openapi: '3.1.0' info: title: Drop Cowboy Public API version: '2.0.0' description: | The Drop Cowboy® API sends ringless voicemail, texts, email, voice broadcasts and AI calls, and manages the contacts, campaigns, phone numbers and AI agents behind them. The base URL is `https://api-v2.dropcowboy.com`. ## Authentication Send your API key and secret as the `x-key` and `x-secret` headers. You can use an OAuth 2.0 client-credentials token from `https://login.dropcowboy.com/oauth/token` instead, as `Authorization: Bearer`; each operation lists the scope it needs. ## Responses and errors Successful responses are `{"data": ..., "meta": {"request_id": ...}}`, and errors are RFC 9457 problem details with the same `request_id`. Branch on the HTTP status, and retry `429` and `5xx` with exponential backoff and jitter. ## Asynchronous sends `POST /rvm`, `/sms`, `/voice-broadcast` and `/ai-broadcast` answer `202` and check your credentials and payload afterwards, so a `202` doesn't mean the send will succeed. The result arrives on your `callback_url` and on the status webhooks. ## Guides - [Send lifecycle](https://www.dropcowboy.com/developers/api/send-lifecycle) - [Webhooks](https://www.dropcowboy.com/developers/api/webhooks) - [Outcomes](https://www.dropcowboy.com/developers/api/outcomes) - [Troubleshooting](https://www.dropcowboy.com/developers/api/troubleshooting) - [Responses, errors and limits](https://www.dropcowboy.com/developers/api/errors-and-limits) contact: name: Drop Cowboy API Support email: support@dropcowboy.com url: https://www.dropcowboy.com/developers/api license: name: Proprietary url: https://dropcowboy.com/terms servers: - url: https://api-v2.dropcowboy.com description: Public REST API. Detection and Building Blocks widget operations set their own server. tags: - name: Authentication description: | Create, list and delete API keys. See [Authentication](https://www.dropcowboy.com/developers/api/authentication) for OAuth and scopes. - name: Account description: Your account, balance, users, carrier connection and Building Blocks. - name: Webhooks description: | Subscribe a URL to an event type. Each subscription has its own signing secret. Verify `X-Signature` (`sha256=` plus the HMAC-SHA256 of `{X-Timestamp}.{raw body}`), answer within 5 seconds, and deduplicate on `event_id`. The event payloads are listed under **Webhooks** at the end of this reference. See [Webhooks](https://www.dropcowboy.com/developers/api/webhooks). - name: Ringless voicemail description: | Send one ringless voicemail, and fetch the proof of delivery for any voicemail left by a ringless voicemail, voice broadcast or AI call. See [Ringless voicemail](https://www.dropcowboy.com/developers/api/ringless-voicemail). - name: Texts description: | Send one text, MMS or RCS message. See [Texts](https://www.dropcowboy.com/developers/api/texts). - name: Voice broadcasts and AI calls description: | Place one Press-1 voice broadcast call or one call handled by an AI agent. See [Voice broadcasts and AI calls](https://www.dropcowboy.com/developers/api/voice-calls). - name: Email description: Send email and read email history. - name: Campaigns description: Create, schedule, start and pause campaigns, and read their statistics. - name: Conversations description: Read text threads, reply to texts, and reply to website chats. - name: Call history description: Read call records and recordings. - name: Contacts description: Create, find, update and delete contacts. - name: Contact details description: Notes, follow-ups, owner, disposition, tags, custom fields and timeline events on a contact. - name: Lists description: Create lists and manage which contacts are on them. - name: Tags description: Create, update and delete tags. - name: Consent description: Record, read and revoke a contact's consent. - name: Do-not-contact list description: Add, check and remove numbers on your do-not-contact list. - name: Media description: Upload and manage audio files for voicemail and calls. - name: Templates description: Read message templates and merge them with contact data. - name: Documents description: Upload and manage documents. - name: Phone numbers description: Rent and read phone numbers, and manage the phone lines that decide how calls and texts to them are handled. - name: Inbox Tasks description: Create, update and list tasks. - name: Pipelines description: Manage sales pipeline boards and the lists on them. - name: Automation description: Lookups and contact helpers for automation platforms, and integration webhooks. - name: Detection description: | Real-time answering-machine, live-person and beep detection over WebSocket at `wss://detect.dropcowboy.com`, for calls you place on your own carrier. Authenticate with a Detection API key from `POST /register/public/detection-keys` or the Detection building block in the dashboard, not with `x-key` and `x-secret`. Keep that key on your server. - name: Voices description: List, clone and design voices, turn text into audio, and transcribe audio. - name: AI agents description: Create, update and publish AI agents. - name: Knowledge bases description: | Knowledge bases that AI agents search during calls and chats. Attach one to an agent with `think.knowledge`. - name: Building Blocks description: | Create short-lived site tokens on your server for Building Blocks widgets, and the routes those widgets call with the token. - name: OpenAPI document description: This document, served with no credentials. security: - apiKey: [] apiSecret: [] paths: /openapi.yaml: get: operationId: getOpenApiDocument summary: Get the OpenAPI document description: | The OpenAPI 3.1 document for this API. No credentials. Responses carry `ETag` and `Cache-Control: public, max-age=300`. A matching `If-None-Match` returns `304` with an empty body. The same file is published at `https://www.dropcowboy.com/openapi.yaml`. tags: [OpenAPI document] security: [] responses: '200': description: This document. headers: ETag: description: SHA-256 of the document, a quoted strong validator. schema: type: string Cache-Control: schema: type: string content: application/yaml: schema: type: string '304': description: The document has not changed since the `If-None-Match` value. '429': $ref: '#/components/responses/TooManyRequests' /contact/public/contacts: get: operationId: listContacts summary: List contacts description: | Page with `limit` and `offset`. The page is `data.contacts`, and `data.total_contacts` counts every match. tags: [Contacts] security: - apiKey: [] apiSecret: [] - oauth2: [contacts:read] parameters: - name: list_id in: query description: Only contacts on this list. schema: type: string - name: search_term in: query description: Free-text search across field values, such as a name, phone number or email. schema: type: string - name: disposition in: query description: Only contacts with this disposition id. `none` returns contacts without one. schema: type: string - name: owner in: query description: Only contacts owned by this user id. `__unassigned__` returns contacts with no owner. schema: type: string - name: brand_id in: query description: Only contacts of this brand. Without it you get every brand. schema: type: string - name: sort_by in: query schema: type: string enum: [modified_at, created_at, last_activity, last_called_at, first_name] - name: sort_order in: query schema: type: string enum: [asc, desc] - $ref: '#/components/parameters/ContactPageLimitParam' - $ref: '#/components/parameters/OffsetParam' responses: '200': description: A page of contacts content: application/json: schema: type: object properties: data: $ref: '#/components/schemas/ContactsPage' meta: $ref: '#/components/schemas/Meta' '400': $ref: '#/components/responses/BadRequest' '401': $ref: '#/components/responses/Unauthorized' '429': $ref: '#/components/responses/TooManyRequests' post: operationId: createContact x-webhooks: [contact.created, contact.updated, contact.list.added] summary: Create contacts description: | Creates one or more contacts. Describe your columns once in `fields`, then send one row per contact in `values`. A row that matches an existing contact by `record_id`, email or phone number updates that contact instead, following `conflict_mode`. Every row needs a `record_id`, an email or a phone number; rows without one are rejected. By default no webhooks are sent and no automations start. tags: [Contacts] security: - apiKey: [] apiSecret: [] - oauth2: [contacts:write] requestBody: required: true content: application/json: schema: $ref: '#/components/schemas/CreateContact' responses: '201': description: Per-row results, or totals when `return_counts` is true. content: application/json: schema: type: object properties: data: oneOf: - $ref: '#/components/schemas/ContactsCreated' - $ref: '#/components/schemas/ContactsCreatedCounts' meta: $ref: '#/components/schemas/Meta' '400': $ref: '#/components/responses/BadRequest' '401': $ref: '#/components/responses/Unauthorized' '402': $ref: '#/components/responses/PaymentRequired' '429': $ref: '#/components/responses/TooManyRequests' /contact/public/contacts/search: get: operationId: searchContacts summary: Search contacts by phone or email description: | Exact match on `phone`, `email`, or both. At least one is required; a request with neither answers `400`. To search by name, use `search_term` on `GET /contact/public/contacts`. Page with `limit` and `offset`. tags: [Contacts] security: - apiKey: [] apiSecret: [] - oauth2: [contacts:read] parameters: - name: phone in: query schema: type: string pattern: '^\+[1-9]\d{1,14}$' example: '+13125550142' - name: email in: query description: Matched without regard to case. schema: type: string format: email - name: brand_id in: query description: Only contacts of this brand. schema: type: string - $ref: '#/components/parameters/ContactPageLimitParam' - $ref: '#/components/parameters/OffsetParam' responses: '200': description: Matching contacts. No match returns an empty `contacts` array. content: application/json: schema: type: object properties: data: $ref: '#/components/schemas/ContactsPage' meta: $ref: '#/components/schemas/Meta' '400': $ref: '#/components/responses/BadRequest' '401': $ref: '#/components/responses/Unauthorized' '429': $ref: '#/components/responses/TooManyRequests' /contact/public/contacts/{id}: parameters: - name: id in: path required: true schema: type: string get: operationId: getContact summary: Get a contact description: | Returns everything about one contact in `data.contact`: field values, phone numbers, lists, tags, pinned notes, consent and do-not-contact flags. An unknown id still answers `200`, with `data.contact` set to `null`. tags: [Contacts] security: - apiKey: [] apiSecret: [] - oauth2: [contacts:read] responses: '200': description: Contact details content: application/json: schema: type: object properties: data: $ref: '#/components/schemas/ContactDetailResult' meta: $ref: '#/components/schemas/Meta' '400': $ref: '#/components/responses/BadRequest' '401': $ref: '#/components/responses/Unauthorized' '429': $ref: '#/components/responses/TooManyRequests' put: operationId: updateContact x-webhooks: [contact.updated, contact.disposition] summary: Update a contact description: | Changes field values. Send standard fields by name, such as `first_name`, `email` or `main_phone`, at the top level of the body or inside `contact`; each one updates the contact's field of that type, or adds it. For custom fields, or to target one field by id, send `contact.field_data` entries with the `field_id` from `GET /contact/public/contacts/{id}`. A `field_data` entry wins over a standard field of the same type. Phone numbers are stored in E.164, and one that can't be read is left out. A body with nothing to update is a `400` (`details.code` `no_updatable_fields`). Lists, tags, the owner and the disposition have their own routes. The response is the updated contact as stored. tags: [Contacts] security: - apiKey: [] apiSecret: [] - oauth2: [contacts:write] requestBody: required: true content: application/json: schema: $ref: '#/components/schemas/UpdateContact' examples: standardFields: summary: Standard fields by name value: first_name: Danielle email: danielle@example.com main_phone: '+13125550142' insideContact: summary: The same fields inside contact value: contact: first_name: Danielle company: Acme Roofing fieldData: summary: Field values by field_id, including a custom field value: contact: field_data: - field_id: 8e42bc36-efeb-42c1-b99b-41ae9c60aa66 type: first_name value: Danielle - type: custom_field custom_field_id: 34f38d24-632d-4a9c-bf89-b9371a722b35 value: Gold responses: '200': description: Contact updated content: application/json: schema: type: object properties: data: $ref: '#/components/schemas/ContactRecord' meta: $ref: '#/components/schemas/Meta' '400': $ref: '#/components/responses/BadRequest' '401': $ref: '#/components/responses/Unauthorized' '403': $ref: '#/components/responses/Forbidden' '404': $ref: '#/components/responses/NotFound' '429': $ref: '#/components/responses/TooManyRequests' delete: operationId: deleteContact x-webhooks: [contact.deleted] summary: Delete a contact description: | Deletes the contact. It no longer appears when you list, search or get contacts. The response is the deleted contact with `deleted_at` and `deleted_by` set. tags: [Contacts] security: - apiKey: [] apiSecret: [] - oauth2: [contacts:write] responses: '200': description: Contact deleted content: application/json: schema: type: object properties: data: $ref: '#/components/schemas/ContactRecord' meta: $ref: '#/components/schemas/Meta' '401': $ref: '#/components/responses/Unauthorized' '404': $ref: '#/components/responses/NotFound' '429': $ref: '#/components/responses/TooManyRequests' /contact/public/contacts/{id}/lists: parameters: - name: id in: path required: true schema: type: string post: operationId: addContactToLists x-webhooks: [contact.list.added, contact.pipeline.stage.entered] summary: Add a contact to lists description: | Adds the contact to each list in `list_ids`, one list at a time in the order given. Repeated ids are added once. Each list sends its own `contact.list.added`. If adding to a list fails, the request stops there and returns the error: lists earlier in `list_ids` keep the contact and later ones are not attempted. On success `data.contact` is the updated contact, as in `GET /contact/public/contacts/{id}`. tags: [Lists] security: - apiKey: [] apiSecret: [] - oauth2: [lists:write] requestBody: required: true content: application/json: schema: type: object required: [list_ids] properties: list_ids: type: array minItems: 1 description: The lists to add the contact to. A missing or empty array, or any id that is not a non-empty string, is a 400. items: type: string minLength: 1 responses: '200': description: Contact added to lists content: application/json: schema: type: object properties: data: $ref: '#/components/schemas/ContactDetailResult' meta: $ref: '#/components/schemas/Meta' '400': $ref: '#/components/responses/BadRequest' '401': $ref: '#/components/responses/Unauthorized' '402': $ref: '#/components/responses/PaymentRequired' '404': $ref: '#/components/responses/NotFound' '429': $ref: '#/components/responses/TooManyRequests' /contact/public/contacts/{id}/lists/{list_id}: parameters: - name: id in: path required: true schema: type: string - name: list_id in: path required: true schema: type: string delete: operationId: removeContactFromList x-webhooks: [contact.list.removed, contact.pipeline.stage.exited] summary: Remove a contact from a list description: | Removes one contact from one list. A contact whose brand differs from the list's brand is skipped, and the response reports how many were skipped. tags: [Lists] security: - apiKey: [] apiSecret: [] - oauth2: [lists:write] responses: '200': description: Contact removed from list content: application/json: schema: type: object properties: data: $ref: '#/components/schemas/ListMembershipResult' meta: $ref: '#/components/schemas/Meta' '400': $ref: '#/components/responses/BadRequest' '401': $ref: '#/components/responses/Unauthorized' '404': $ref: '#/components/responses/NotFound' '429': $ref: '#/components/responses/TooManyRequests' /contact/public/contacts/{id}/lists/move: parameters: - name: id in: path required: true schema: type: string post: operationId: moveContactBetweenLists x-webhooks: [contact.list.removed, contact.list.added, contact.pipeline.stage.exited, contact.pipeline.stage.entered] summary: Move a contact between lists description: | Adds the contact to `to_list_id` and removes it from `from_list_id`. If the target list belongs to a different brand than the contact, the contact stays where it is and is counted in `skipped`. tags: [Lists] security: - apiKey: [] apiSecret: [] - oauth2: [lists:write] requestBody: required: true content: application/json: schema: type: object required: [from_list_id, to_list_id] properties: from_list_id: type: string to_list_id: type: string responses: '200': description: Contact moved content: application/json: schema: type: object properties: data: $ref: '#/components/schemas/ListMembershipResult' meta: $ref: '#/components/schemas/Meta' '400': $ref: '#/components/responses/BadRequest' '401': $ref: '#/components/responses/Unauthorized' '402': $ref: '#/components/responses/PaymentRequired' '404': $ref: '#/components/responses/NotFound' '429': $ref: '#/components/responses/TooManyRequests' /contact/public/contacts/{id}/tags/{tag_id}: parameters: - name: id in: path required: true schema: type: string - name: tag_id in: path required: true schema: type: string post: operationId: addTagToContact x-webhooks: [contact.tag.added] summary: Add a tag to a contact description: | Adds an existing tag to the contact. Adding a tag the contact already has is safe. The response lists the contact's tags after the change. An unknown contact or tag returns `404`. tags: [Contact details] security: - apiKey: [] apiSecret: [] - oauth2: [contacts:write] responses: '200': description: The contact's tags content: application/json: schema: type: object properties: data: type: array items: $ref: '#/components/schemas/ContactTag' meta: $ref: '#/components/schemas/Meta' '400': $ref: '#/components/responses/BadRequest' '401': $ref: '#/components/responses/Unauthorized' '404': $ref: '#/components/responses/NotFound' '429': $ref: '#/components/responses/TooManyRequests' delete: operationId: removeTagFromContact x-webhooks: [contact.tag.removed] summary: Remove a tag from a contact description: | Removes the tag from the contact; the tag itself still exists. The response lists the tags the contact still has, which can be empty. An unknown contact or tag, or a tag the contact doesn't have, returns `404`. tags: [Contact details] security: - apiKey: [] apiSecret: [] - oauth2: [contacts:write] responses: '200': description: The contact's remaining tags content: application/json: schema: type: object properties: data: type: array items: $ref: '#/components/schemas/ContactTag' meta: $ref: '#/components/schemas/Meta' '400': $ref: '#/components/responses/BadRequest' '401': $ref: '#/components/responses/Unauthorized' '404': $ref: '#/components/responses/NotFound' '429': $ref: '#/components/responses/TooManyRequests' /tag/public/tags: get: operationId: listTags summary: List tags description: Returns every tag on your account. tags: [Tags] security: - apiKey: [] apiSecret: [] - oauth2: [contacts:read] parameters: - name: search_term in: query description: Filter tags whose label matches (case-insensitive substring). schema: type: string - $ref: '#/components/parameters/LimitParam' responses: '200': description: List of tags content: application/json: schema: type: object properties: data: type: array items: $ref: '#/components/schemas/TagDocument' meta: $ref: '#/components/schemas/Meta' '401': $ref: '#/components/responses/Unauthorized' '429': $ref: '#/components/responses/TooManyRequests' post: operationId: createTag summary: Create a tag description: | Creates a tag. Send a `label` and, optionally, a `color` and a `text_color` as hex values. The response is the new tag, and its `tag_id` is the id you use to refer to it. tags: [Tags] security: - apiKey: [] apiSecret: [] - oauth2: [contacts:write] requestBody: required: true content: application/json: schema: $ref: '#/components/schemas/CreateTag' responses: '201': description: Tag created. The tag's id is `tag_id`. content: application/json: schema: type: object properties: data: $ref: '#/components/schemas/TagCreated' meta: $ref: '#/components/schemas/Meta' '400': $ref: '#/components/responses/BadRequest' '401': $ref: '#/components/responses/Unauthorized' '429': $ref: '#/components/responses/TooManyRequests' /tag/public/tags/{tag_id}: parameters: - name: tag_id in: path required: true schema: type: string put: operationId: updateTag summary: Update a tag description: Changes only the fields you send. Always send `label`, even when you change only the colors. tags: [Tags] security: - apiKey: [] apiSecret: [] - oauth2: [contacts:write] requestBody: required: true content: application/json: schema: $ref: '#/components/schemas/UpdateTag' responses: '200': description: Tag updated content: application/json: schema: type: object properties: data: $ref: '#/components/schemas/Tag' meta: $ref: '#/components/schemas/Meta' '400': $ref: '#/components/responses/BadRequest' '401': $ref: '#/components/responses/Unauthorized' '404': $ref: '#/components/responses/NotFound' '429': $ref: '#/components/responses/TooManyRequests' delete: operationId: deleteTag summary: Delete a tag description: | Deletes the tag, which then no longer appears in `GET /tag/public/tags`. Contacts that had the tag are not updated, so their `tags` can still hold its id. tags: [Tags] security: - apiKey: [] apiSecret: [] - oauth2: [contacts:write] responses: '200': description: Tag deleted content: application/json: schema: type: object properties: data: type: object properties: id: type: string deleted: type: boolean enum: [true] meta: $ref: '#/components/schemas/Meta' '401': $ref: '#/components/responses/Unauthorized' '404': $ref: '#/components/responses/NotFound' '429': $ref: '#/components/responses/TooManyRequests' /tag/public/tags/bulk-delete: post: operationId: bulkDeleteTags summary: Delete several tags description: Deletes every tag in `tag_ids`. Ids that aren't on your account are skipped without an error. tags: [Tags] security: - apiKey: [] apiSecret: [] - oauth2: [contacts:write] requestBody: required: true content: application/json: schema: type: object required: [tag_ids] properties: tag_ids: type: array items: type: string responses: '200': description: Bulk delete result content: application/json: schema: type: object properties: data: type: object properties: matched_count: type: integer modified_count: type: integer meta: $ref: '#/components/schemas/Meta' '400': $ref: '#/components/responses/BadRequest' '401': $ref: '#/components/responses/Unauthorized' '429': $ref: '#/components/responses/TooManyRequests' /contact/public/contacts/{id}/notes: parameters: - name: id in: path required: true schema: type: string get: operationId: listContactNotes summary: List a contact's notes description: | Returns every note on the contact, pinned or not, newest first. `username` is the author's name. tags: [Contact details] security: - apiKey: [] apiSecret: [] - oauth2: [contacts:read] responses: '200': description: The contact's notes content: application/json: schema: type: object properties: data: type: array items: allOf: - $ref: '#/components/schemas/Note' - type: object properties: username: type: [string, 'null'] description: The author's first and last name. meta: $ref: '#/components/schemas/Meta' '401': $ref: '#/components/responses/Unauthorized' '404': $ref: '#/components/responses/NotFound' '429': $ref: '#/components/responses/TooManyRequests' post: operationId: createContactNote x-webhooks: [contact.note.created] summary: Add a note to a contact description: | Adds a note. `note` can contain basic HTML, which is sanitized. New notes aren't pinned, and `GET /contact/public/contacts/{id}` returns only pinned notes. tags: [Contact details] security: - apiKey: [] apiSecret: [] - oauth2: [contacts:write] requestBody: required: true content: application/json: schema: $ref: '#/components/schemas/CreateNote' responses: '201': description: Note created content: application/json: schema: type: object properties: data: $ref: '#/components/schemas/Note' meta: $ref: '#/components/schemas/Meta' '400': $ref: '#/components/responses/BadRequest' '401': $ref: '#/components/responses/Unauthorized' '429': $ref: '#/components/responses/TooManyRequests' /contact/public/contacts/{id}/follow-ups: parameters: - name: id in: path required: true schema: type: string get: operationId: listContactFollowUps summary: 'List a contact''s follow-ups' description: | Returns all of the contact's follow-ups in one response, soonest first. Cancelled follow-ups are left out unless you ask for them with `status`. tags: [Contact details] security: - apiKey: [] apiSecret: [] - oauth2: [contacts:read] parameters: - name: status in: query description: Only follow-ups with this status. schema: type: string enum: [pending, triggered, completed, cancelled] responses: '200': description: Contact follow-ups content: application/json: schema: type: object properties: data: type: array items: $ref: '#/components/schemas/FollowUp' meta: $ref: '#/components/schemas/Meta' '400': $ref: '#/components/responses/BadRequest' '401': $ref: '#/components/responses/Unauthorized' '429': $ref: '#/components/responses/TooManyRequests' post: operationId: createContactFollowUp x-webhooks: [contact.reminder.created] summary: Create a follow-up description: | Schedules a reminder to get back to the contact. When it's due, the follow-up becomes `triggered` and creates an Inbox task for whoever it's assigned to. tags: [Contact details] security: - apiKey: [] apiSecret: [] - oauth2: [contacts:write] requestBody: required: true content: application/json: schema: $ref: '#/components/schemas/CreateFollowUp' responses: '201': description: Follow-up created content: application/json: schema: type: object properties: data: $ref: '#/components/schemas/FollowUp' meta: $ref: '#/components/schemas/Meta' '400': $ref: '#/components/responses/BadRequest' '401': $ref: '#/components/responses/Unauthorized' '429': $ref: '#/components/responses/TooManyRequests' /contact/public/follow-ups/{id}: parameters: - name: id in: path required: true description: '`followup_id` of the follow-up.' schema: type: string put: operationId: updateFollowUp x-webhooks: [contact.reminder.updated] summary: Update a follow-up description: | Changes when it's due, what it says, or who it's assigned to. Send only the fields you are changing. Only pending follow-ups can change; any other status is a `409`. To close one, cancel it with `DELETE /contact/public/follow-ups/{id}`. tags: [Contact details] security: - apiKey: [] apiSecret: [] - oauth2: [contacts:write] requestBody: required: true content: application/json: schema: $ref: '#/components/schemas/UpdateFollowUp' responses: '200': description: Follow-up updated content: application/json: schema: type: object properties: data: $ref: '#/components/schemas/FollowUp' meta: $ref: '#/components/schemas/Meta' '400': $ref: '#/components/responses/BadRequest' '401': $ref: '#/components/responses/Unauthorized' '404': $ref: '#/components/responses/NotFound' '409': $ref: '#/components/responses/Conflict' '429': $ref: '#/components/responses/TooManyRequests' delete: operationId: deleteFollowUp x-webhooks: [contact.reminder.deleted] summary: Cancel a follow-up description: | Cancels a pending follow-up so it never becomes a task. Any other status is a `409`. tags: [Contact details] security: - apiKey: [] apiSecret: [] - oauth2: [contacts:write] responses: '200': description: Follow-up cancelled content: application/json: schema: type: object properties: data: $ref: '#/components/schemas/FollowUpCancelled' meta: $ref: '#/components/schemas/Meta' '401': $ref: '#/components/responses/Unauthorized' '404': $ref: '#/components/responses/NotFound' '409': $ref: '#/components/responses/Conflict' '429': $ref: '#/components/responses/TooManyRequests' /contact/public/contacts/{id}/owner: parameters: - name: id in: path required: true schema: type: string put: operationId: assignContactOwner x-webhooks: [contact.updated, contact.assigned] summary: 'Set a contact''s owner' description: | Makes a user the contact's owner. Only the current owner or a manager can change the owner of a contact someone else owns. The response is the updated contact as stored. tags: [Contact details] security: - apiKey: [] apiSecret: [] - oauth2: [contacts:write] requestBody: required: true content: application/json: schema: type: object required: [owner_id] properties: owner_id: type: string description: User id of the new owner. responses: '200': description: Owner assigned content: application/json: schema: type: object properties: data: $ref: '#/components/schemas/ContactRecord' meta: $ref: '#/components/schemas/Meta' '400': $ref: '#/components/responses/BadRequest' '401': $ref: '#/components/responses/Unauthorized' '403': $ref: '#/components/responses/Forbidden' '404': $ref: '#/components/responses/NotFound' '429': $ref: '#/components/responses/TooManyRequests' /contact/public/contacts/{id}/disposition: parameters: - name: id in: path required: true schema: type: string put: operationId: setContactDisposition x-webhooks: [contact.disposition, contact.updated] summary: 'Set a contact''s disposition' description: | Records a disposition on one contact. Send the `disposition_id` of the disposition to record. The response is the updated contact, whose `disposition` field holds the latest one. tags: [Contact details] security: - apiKey: [] apiSecret: [] - oauth2: [contacts:write] requestBody: required: true content: application/json: schema: type: object required: [disposition_id] properties: disposition_id: type: string minLength: 1 description: Id of the disposition to record. A missing, empty or non-string value is a 400. responses: '200': description: Disposition set. The response is the updated contact as stored. content: application/json: schema: type: object properties: data: $ref: '#/components/schemas/ContactRecord' meta: $ref: '#/components/schemas/Meta' '400': $ref: '#/components/responses/BadRequest' '401': $ref: '#/components/responses/Unauthorized' '404': $ref: '#/components/responses/NotFound' '429': $ref: '#/components/responses/TooManyRequests' /contact/public/contacts/{id}/consent: parameters: - name: id in: path required: true schema: type: string get: operationId: getContactConsent summary: 'List a contact''s consent records' description: | Returns every consent record for the contact in one response, newest first, including opt-outs and revocations. An unknown contact returns an empty array. tags: [Consent] security: - apiKey: [] apiSecret: [] - oauth2: [consent:read] responses: '200': description: Consent records content: application/json: schema: type: object properties: data: type: array items: $ref: '#/components/schemas/ConsentRecord' meta: $ref: '#/components/schemas/Meta' '401': $ref: '#/components/responses/Unauthorized' '429': $ref: '#/components/responses/TooManyRequests' post: operationId: createContactConsent x-webhooks: [contact.consent.granted] summary: Record consent for a contact description: | Stores a consent record and updates the contact's consent flags. Send `phone_number`, `email`, or both. Opt-out types (`tcpa_optout`, `sms_optout`, `email_optout`) record a revocation and send no `contact.consent.granted`. tags: [Consent] security: - apiKey: [] apiSecret: [] - oauth2: [consent:write] requestBody: required: true content: application/json: schema: $ref: '#/components/schemas/CreateConsent' responses: '201': description: Consent recorded content: application/json: schema: type: object properties: data: $ref: '#/components/schemas/ConsentCreated' meta: $ref: '#/components/schemas/Meta' '400': $ref: '#/components/responses/BadRequest' '401': $ref: '#/components/responses/Unauthorized' '429': $ref: '#/components/responses/TooManyRequests' /contact/public/contacts/{id}/consent/{cid}/revoke: parameters: - name: id in: path required: true schema: type: string - name: cid in: path required: true schema: type: string put: operationId: revokeContactConsent x-webhooks: [contact.consent.revoked] summary: Revoke a consent record description: | Records a `tcpa_optout` for the phone number and email on the consent `cid`, revokes the contact's TCPA consents, and adds every phone number on the contact to your do-not-contact list. Revoking an `esign` consent revokes all of the contact's consents. The response is the new opt-out record. A `cid` that doesn't exist leaves no phone number or email to opt out, which is a `400`. tags: [Consent] security: - apiKey: [] apiSecret: [] - oauth2: [consent:write] requestBody: required: false content: application/json: schema: type: object properties: consent_method: type: string default: api description: How the contact revoked consent. ip_address: type: string description: The contact's IP address. Defaults to the caller's. user_agent: type: string description: The contact's browser user agent. Defaults to the caller's. responses: '200': description: Consent revoked content: application/json: schema: type: object properties: data: $ref: '#/components/schemas/ConsentCreated' meta: $ref: '#/components/schemas/Meta' '400': $ref: '#/components/responses/BadRequest' '401': $ref: '#/components/responses/Unauthorized' '429': $ref: '#/components/responses/TooManyRequests' /contact/public/fields: get: operationId: listCustomFields summary: List custom fields description: | Returns every custom field on your account in one response, newest first. Use `custom_field_id` as `field_id` when you create contacts with a `custom_field` column. tags: [Contact details] security: - apiKey: [] apiSecret: [] - oauth2: [contacts:read] responses: '200': description: Custom field definitions content: application/json: schema: type: object properties: data: type: array items: $ref: '#/components/schemas/CustomField' meta: $ref: '#/components/schemas/Meta' '401': $ref: '#/components/responses/Unauthorized' '429': $ref: '#/components/responses/TooManyRequests' /contact/public/lists: get: operationId: listContactLists summary: List lists description: | Page with `limit` and `offset`. The page is `data.lists`. The built-in Unlisted list, which holds contacts on no other list, is moved to the front of whichever page it falls on. Each list carries `contact_count`; for line-type and other counts, get the list with `GET /contact/public/lists/{id}`. tags: [Lists] security: - apiKey: [] apiSecret: [] - oauth2: [lists:read] parameters: - name: search_term in: query description: Only lists whose name matches. schema: type: string - name: sort_by in: query schema: type: string enum: [created_at, list_name, modified_at, contact_count] default: created_at - name: sort_order in: query schema: type: string enum: [asc, desc] default: desc - name: brand_id in: query description: Only lists of this brand. schema: type: string - $ref: '#/components/parameters/ContactPageLimitParam' - $ref: '#/components/parameters/OffsetParam' responses: '200': description: A page of lists content: application/json: schema: type: object properties: data: $ref: '#/components/schemas/ContactListsPage' meta: $ref: '#/components/schemas/Meta' '401': $ref: '#/components/responses/Unauthorized' '429': $ref: '#/components/responses/TooManyRequests' post: operationId: createContactList x-webhooks: [list.created] summary: Create a list description: | Creates an empty list. List names are unique on your account; a duplicate name is a `409`. The response is the stored list, without counts. tags: [Lists] security: - apiKey: [] apiSecret: [] - oauth2: [lists:write] requestBody: required: true content: application/json: schema: $ref: '#/components/schemas/CreateContactList' responses: '201': description: List created content: application/json: schema: type: object properties: data: $ref: '#/components/schemas/ContactList' meta: $ref: '#/components/schemas/Meta' '400': $ref: '#/components/responses/BadRequest' '401': $ref: '#/components/responses/Unauthorized' '402': $ref: '#/components/responses/PaymentRequired' '409': $ref: '#/components/responses/Conflict' '429': $ref: '#/components/responses/TooManyRequests' /contact/public/lists/{id}: parameters: - name: id in: path required: true schema: type: string get: operationId: getContactList summary: Get a list description: The list, with its counts, is in `data.list`. tags: [Lists] security: - apiKey: [] apiSecret: [] - oauth2: [lists:read] responses: '200': description: List details content: application/json: schema: type: object properties: data: type: object properties: list: $ref: '#/components/schemas/ContactListDetail' meta: $ref: '#/components/schemas/Meta' '401': $ref: '#/components/responses/Unauthorized' '404': $ref: '#/components/responses/NotFound' '429': $ref: '#/components/responses/TooManyRequests' put: operationId: updateContactList x-webhooks: [list.updated] summary: Update a list description: | Changes only the fields you send. The response is the updated list as stored, without counts. tags: [Lists] security: - apiKey: [] apiSecret: [] - oauth2: [lists:write] requestBody: required: true content: application/json: schema: $ref: '#/components/schemas/UpdateContactList' responses: '200': description: List updated content: application/json: schema: type: object properties: data: $ref: '#/components/schemas/ContactList' meta: $ref: '#/components/schemas/Meta' '400': $ref: '#/components/responses/BadRequest' '401': $ref: '#/components/responses/Unauthorized' '404': $ref: '#/components/responses/NotFound' '429': $ref: '#/components/responses/TooManyRequests' delete: operationId: deleteContactList x-webhooks: [list.deleted] summary: Delete a list description: | Deletes the list. Its contacts stay on your account unless you send `delete_contacts: true`, which needs permission to delete contacts. System lists can't be deleted. tags: [Lists] security: - apiKey: [] apiSecret: [] - oauth2: [lists:write] requestBody: required: false content: application/json: schema: type: object properties: delete_contacts: type: boolean default: false description: Also delete the list's contacts. responses: '200': description: List deleted content: application/json: schema: type: object properties: data: $ref: '#/components/schemas/ContactListDeleted' meta: $ref: '#/components/schemas/Meta' '400': $ref: '#/components/responses/BadRequest' '401': $ref: '#/components/responses/Unauthorized' '403': $ref: '#/components/responses/Forbidden' '404': $ref: '#/components/responses/NotFound' '429': $ref: '#/components/responses/TooManyRequests' /contact/public/lists/{id}/contacts: parameters: - name: id in: path required: true description: '`list_id` from `GET /contact/public/lists`.' schema: type: string format: uuid get: operationId: listContactsInList summary: 'List a list''s contacts' description: | Cursor pagination, oldest first. Pass `next_cursor` back as `after_id` until `has_more` is false. Operation webhooks such as `contact.list.added` carry a ready-made `more_via` URL for this route. tags: [Lists] security: - apiKey: [] apiSecret: [] - oauth2: [lists:read] parameters: - $ref: '#/components/parameters/AfterIdParam' - name: limit in: query description: Page size. Values above 500 are treated as 500. schema: type: integer default: 25 minimum: 1 maximum: 500 responses: '200': description: A page of contacts content: application/json: schema: type: object properties: data: type: object properties: contacts: type: array items: $ref: '#/components/schemas/ContactListMember' next_cursor: type: [string, 'null'] description: Send as `after_id` for the next page. `null` on the last page. has_more: type: boolean meta: $ref: '#/components/schemas/Meta' '400': $ref: '#/components/responses/BadRequest' '401': $ref: '#/components/responses/Unauthorized' '403': $ref: '#/components/responses/InsufficientScope' '404': $ref: '#/components/responses/NotFound' '429': $ref: '#/components/responses/TooManyRequests' /dnc/public/dnc: get: operationId: lookupDnc summary: Check numbers or emails against the do-not-contact list description: | Send `phone_numbers`, `emails` or both. `on_dnc` is true when any of them is listed. With `brand_id`, entries for that brand and entries that apply to every brand both count. tags: [Do-not-contact list] security: - apiKey: [] apiSecret: [] - oauth2: [dnc:read] parameters: - name: phone_numbers in: query description: Comma-separated phone numbers in E.164 format. schema: type: string example: '+13125550142,+13125550187' - name: emails in: query description: Comma-separated email addresses. Matching ignores case. schema: type: string example: jordan@example.com - name: brand_id in: query description: Check as this brand. schema: type: string responses: '200': description: Lookup result content: application/json: schema: type: object properties: data: $ref: '#/components/schemas/DncLookup' meta: $ref: '#/components/schemas/Meta' '400': $ref: '#/components/responses/BadRequest' '401': $ref: '#/components/responses/Unauthorized' '429': $ref: '#/components/responses/TooManyRequests' post: operationId: createDncEntry summary: Add numbers or emails to the do-not-contact list description: | Adds or updates one entry per number and email, and flags matching contacts as do-not-contact. Without `brand_id` the entries apply to every brand. Adding something already listed for the same brand updates its entry and is reported in `already_listed`. tags: [Do-not-contact list] security: - apiKey: [] apiSecret: [] - oauth2: [dnc:write] requestBody: required: true content: application/json: schema: $ref: '#/components/schemas/CreateDnc' responses: '200': description: Entries added content: application/json: schema: type: object properties: data: $ref: '#/components/schemas/DncAddResult' meta: $ref: '#/components/schemas/Meta' '400': $ref: '#/components/responses/BadRequest' '401': $ref: '#/components/responses/Unauthorized' '429': $ref: '#/components/responses/TooManyRequests' /dnc/public/dnc/bulk-delete: post: operationId: bulkDeleteDncEntries summary: Remove several numbers or emails from the do-not-contact list description: | Removes each number and email and clears the do-not-contact flag on matching contacts. Opt-outs the recipient made themselves (for example a STOP reply) are kept and reported in `skipped`. tags: [Do-not-contact list] security: - apiKey: [] apiSecret: [] - oauth2: [dnc:write] requestBody: required: true content: application/json: schema: $ref: '#/components/schemas/BulkDeleteDnc' responses: '200': description: Removal result content: application/json: schema: type: object properties: data: $ref: '#/components/schemas/DncBulkDeleteResult' meta: $ref: '#/components/schemas/Meta' '400': $ref: '#/components/responses/BadRequest' '401': $ref: '#/components/responses/Unauthorized' '429': $ref: '#/components/responses/TooManyRequests' /dnc/public/dnc/{phoneNumber}: parameters: - name: phoneNumber in: path required: true description: The number exactly as it was added, in E.164 format. schema: type: string pattern: '^\+[1-9]\d{1,14}$' example: '+13125550142' get: operationId: getDncEntry summary: Check one number against the do-not-contact list description: | Returns the matching entries when the number is listed, and `404` when it isn't. To check several numbers or emails at once, use `GET /dnc/public/dnc`. tags: [Do-not-contact list] security: - apiKey: [] apiSecret: [] - oauth2: [dnc:read] parameters: - name: brand_id in: query description: Check as this brand. Entries for that brand and entries that apply to every brand both count. schema: type: string responses: '200': description: The number is listed content: application/json: schema: type: object properties: data: $ref: '#/components/schemas/DncLookup' meta: $ref: '#/components/schemas/Meta' '401': $ref: '#/components/responses/Unauthorized' '404': description: Not on the do-not-contact list. content: application/json: schema: $ref: '#/components/schemas/Error' '429': $ref: '#/components/responses/TooManyRequests' delete: operationId: deleteDncEntry summary: Remove a number from the do-not-contact list description: | Removes the entry and clears the do-not-contact flag on matching contacts, and returns the entry that was removed. Returns 400 if the recipient opted out themselves. A number that isn't listed still returns 200, with `deleted_at` and `deleted_by` set to `null`. tags: [Do-not-contact list] security: - apiKey: [] apiSecret: [] - oauth2: [dnc:write] parameters: - name: brand_id in: query description: Brand of the entry to remove. Without it, the newest matching entry is removed. schema: type: string responses: '200': description: Number removed content: application/json: schema: type: object properties: data: $ref: '#/components/schemas/DncRemovedEntry' meta: $ref: '#/components/schemas/Meta' '400': $ref: '#/components/responses/BadRequest' '401': $ref: '#/components/responses/Unauthorized' '429': $ref: '#/components/responses/TooManyRequests' /dnc/public/dnc/email/{email}: parameters: - name: email in: path required: true description: The email address. Matching ignores case. schema: type: string format: email example: jordan.rivera@example.com get: operationId: getDncEmailEntry summary: Check one email against the do-not-contact list description: | Returns the matching entries when the address is listed, and `404` when it isn't. tags: [Do-not-contact list] security: - apiKey: [] apiSecret: [] - oauth2: [dnc:read] parameters: - name: brand_id in: query description: Check as this brand. Entries for that brand and entries that apply to every brand both count. schema: type: string responses: '200': description: The email is listed content: application/json: schema: type: object properties: data: $ref: '#/components/schemas/DncLookup' meta: $ref: '#/components/schemas/Meta' '401': $ref: '#/components/responses/Unauthorized' '404': description: Not on the do-not-contact list. content: application/json: schema: $ref: '#/components/schemas/Error' '429': $ref: '#/components/responses/TooManyRequests' delete: operationId: deleteDncEmailEntry summary: Remove an email from the do-not-contact list description: | Removes the entry and clears the do-not-contact flag on contacts with that email, and returns the entry that was removed. Returns 400 if the recipient unsubscribed themselves. An email that isn't listed still returns 200, with `deleted_at` and `deleted_by` set to `null`. tags: [Do-not-contact list] security: - apiKey: [] apiSecret: [] - oauth2: [dnc:write] parameters: - name: brand_id in: query description: Brand of the entry to remove. Without it, the newest matching entry is removed. schema: type: string responses: '200': description: Email removed content: application/json: schema: type: object properties: data: $ref: '#/components/schemas/DncRemovedEntry' meta: $ref: '#/components/schemas/Meta' '400': $ref: '#/components/responses/BadRequest' '401': $ref: '#/components/responses/Unauthorized' '429': $ref: '#/components/responses/TooManyRequests' /phone/public/numbers: get: operationId: listPhoneNumbers summary: List phone numbers description: | Returns every number on the account in one response, with the account's number limits. Released numbers, dial-in numbers and 911 callback numbers are left out. This route isn't paged. tags: [Phone numbers] security: - apiKey: [] apiSecret: [] - oauth2: [numbers:read] responses: '200': description: Every number on the account in one response, with the account's number limits. content: application/json: schema: type: object properties: data: $ref: '#/components/schemas/PhoneNumbersPage' meta: $ref: '#/components/schemas/Meta' '401': $ref: '#/components/responses/Unauthorized' '429': $ref: '#/components/responses/TooManyRequests' /phone/public/numbers/rent: post: operationId: rentPhoneNumber x-webhooks: [number.provisioned, number.updated] summary: Rent phone numbers description: > Rents the numbers in `numbers`. Accounts on Building Blocks rent from their own connected carrier (BYOC): connect a carrier first, or the call fails with `403`. A connected carrier with no default number pool fails with `400`. Other accounts can rent here only during their trial period; afterwards the call fails with `400` and code `use_cart_instead`. Going over the account's number limit fails with `403`. tags: [Phone numbers] security: - apiKey: [] apiSecret: [] - oauth2: [numbers:write] requestBody: required: true content: application/json: schema: $ref: '#/components/schemas/RentPhoneNumbers' responses: '200': description: Rented numbers content: application/json: schema: type: object properties: data: $ref: '#/components/schemas/RentedPhoneNumbers' meta: $ref: '#/components/schemas/Meta' '400': $ref: '#/components/responses/BadRequest' '401': $ref: '#/components/responses/Unauthorized' '402': $ref: '#/components/responses/PaymentRequired' '403': $ref: '#/components/responses/Forbidden' '429': $ref: '#/components/responses/TooManyRequests' /phone/public/numbers/{number}: parameters: - name: number in: path required: true schema: type: string get: operationId: getPhoneNumber summary: Get a phone number description: Accepts the number with or without the leading `+`. Returns 404 for a number not on the account. tags: [Phone numbers] security: - apiKey: [] apiSecret: [] - oauth2: [numbers:read] responses: '200': description: Phone number details content: application/json: schema: type: object properties: data: $ref: '#/components/schemas/PhoneNumber' meta: $ref: '#/components/schemas/Meta' '401': $ref: '#/components/responses/Unauthorized' '404': $ref: '#/components/responses/NotFound' '429': $ref: '#/components/responses/TooManyRequests' /phone/public/calls/{call_id}: parameters: - name: call_id in: path required: true schema: type: string get: operationId: getCall summary: Get a call description: An unknown `call_id` returns 200 with an empty object. tags: [Call history] security: - apiKey: [] apiSecret: [] - oauth2: [contacts:read] responses: '200': description: Call details content: application/json: schema: type: object properties: data: $ref: '#/components/schemas/Call' meta: $ref: '#/components/schemas/Meta' '401': $ref: '#/components/responses/Unauthorized' '429': $ref: '#/components/responses/TooManyRequests' /phone/public/calls/{call_id}/recording: parameters: - name: call_id in: path required: true schema: type: string get: operationId: getCallRecording summary: Get a call recording description: | Returns a signed download link for the call's first recording, valid for five days. Returns 404 when the call or its recording doesn't exist. Outside a trial, the response holds only `recording_unavailable_reason` when your balance is below zero now, or when it was too low to record at the time of the call. tags: [Call history] security: - apiKey: [] apiSecret: [] - oauth2: [contacts:read] responses: '200': description: Recording URL content: application/json: schema: type: object properties: data: $ref: '#/components/schemas/CallRecordingUrl' meta: $ref: '#/components/schemas/Meta' '401': $ref: '#/components/responses/Unauthorized' '404': $ref: '#/components/responses/NotFound' '429': $ref: '#/components/responses/TooManyRequests' /phone/public/sms/{sms_id}: parameters: - name: sms_id in: path required: true schema: type: string get: operationId: getSmsMessage summary: Get a text description: | Inbound texts and messages sent from the inbox or with `POST /phone/public/sms/reply`. `POST /sms` sends are not stored here. An unknown `sms_id`, or one that belongs to another account, answers `404`. Files are listed under `mms_media`, with a download `url` once a file's scan is `clean`. tags: [Conversations] security: - apiKey: [] apiSecret: [] - oauth2: [contacts:read] responses: '200': description: The text content: application/json: schema: type: object properties: data: $ref: '#/components/schemas/SmsMessage' meta: $ref: '#/components/schemas/Meta' examples: inboundMms: $ref: './examples/sms-message-mms.response.json' '401': $ref: '#/components/responses/Unauthorized' '404': $ref: '#/components/responses/NotFound' '429': $ref: '#/components/responses/TooManyRequests' /phone/public/sms/thread/{contact_id}: parameters: - name: contact_id in: path required: true schema: type: string get: operationId: getSmsThread summary: 'Get a contact''s text thread' description: | Stored messages recorded against the contact, inbound and outbound, newest first. `POST /sms` sends are not included. A `contact_id` with no messages, or one that is not on your account, returns an empty `smss`. Reading the thread marks that contact's unseen messages as seen, with `seen_by` set to `null`; no other contact's messages are touched. Messages in the response still carry the `seen_at` they had before this read. A `skip` or `limit` that is not a whole number in range answers `400` (`invalid-parameters`). tags: [Conversations] security: - apiKey: [] apiSecret: [] - oauth2: [contacts:read] parameters: - name: limit in: query schema: type: integer minimum: 1 default: 50 - $ref: '#/components/parameters/SkipParam' responses: '200': description: SMS thread content: application/json: schema: type: object properties: data: type: object properties: smss: type: array items: $ref: '#/components/schemas/SmsMessage' meta: $ref: '#/components/schemas/Meta' '400': $ref: '#/components/responses/BadRequest' '401': $ref: '#/components/responses/Unauthorized' '429': $ref: '#/components/responses/TooManyRequests' /phone/public/sms/reply: post: operationId: sendSmsReply x-webhooks: [contact.msg.sent] summary: Send a text reply description: | Sends one text or MMS to one number and answers with the stored message. Unlike `POST /sms` it is synchronous: media is fetched, stored and scanned (up to 12 seconds) before the answer, and media problems come back as HTTP errors. Media limits are the same as on `POST /sms`; see [MMS](https://www.dropcowboy.com/developers/api/texts#mms). Billed per file for MMS. tags: [Conversations] security: - apiKey: [] apiSecret: [] - oauth2: [sms:send] requestBody: required: true content: application/json: schema: $ref: '#/components/schemas/SendSmsReply' examples: mms: $ref: './examples/sms-reply-mms.request.json' responses: '200': description: Sent. The body is the stored message. content: application/json: schema: type: object properties: data: $ref: '#/components/schemas/SmsReplyResult' meta: $ref: '#/components/schemas/Meta' examples: mms: $ref: './examples/sms-reply-mms.response.json' '400': description: | `type` `.../invalid-parameters`: bad `media_urls` or `media_ids`, more than 10 files, or a `media_id` that is not found. `type` `.../server-error` with status `400`: missing `phone_number`, `caller_id`, or both `sms_body` and media; a do-not-contact number; or no usable sender. content: application/json: schema: $ref: '#/components/schemas/Error' '401': $ref: '#/components/responses/Unauthorized' '402': description: Account billing is past due. `type` is `.../server-error`. content: application/json: schema: $ref: '#/components/schemas/Error' '413': description: '`.../payload-too-large`: a file is over 1 MiB, or the files total over 5 MiB.' content: application/json: schema: $ref: '#/components/schemas/Error' '415': description: '`.../unsupported-media-type`: a file is not JPEG, PNG, GIF, WAV or MP3.' content: application/json: schema: $ref: '#/components/schemas/Error' '422': description: | `.../media-fetch-failed`: a URL did not answer `200` in time, or redirected; retry later. `.../media-blocked`: the malware scan flagged a file; do not retry. content: application/json: schema: $ref: '#/components/schemas/Error' '429': $ref: '#/components/responses/TooManyRequests' '500': description: '`.../server-error`: storing a file failed. Retry later.' content: application/json: schema: $ref: '#/components/schemas/Error' '502': description: '`.../server-error`: the malware scan could not finish. Retry later.' content: application/json: schema: $ref: '#/components/schemas/Error' '503': description: '`.../media-scan-pending`: the scan did not finish in time. Retry after `Retry-After`; the retry reuses the finished scan.' headers: Retry-After: description: Seconds to wait. schema: type: integer example: 15 content: application/json: schema: $ref: '#/components/schemas/Error' /phone/public/lines: get: operationId: listPhoneLines summary: List phone lines description: | Phone lines group your numbers and decide how calls and texts to them are handled. Use `ivr_id` as `phone_line_id` on sends and campaigns. Pass `statistics=true` to get `sms_enabled_count` and `rcs_enabled_count`, which tell you which lines can text. tags: [Phone numbers] security: - apiKey: [] apiSecret: [] - oauth2: [numbers:read] parameters: - name: type in: query schema: type: string - name: search_term in: query schema: type: string - name: statistics in: query schema: type: boolean responses: '200': description: Phone lines content: application/json: schema: type: object properties: data: type: array items: $ref: '#/components/schemas/PhoneLine' meta: $ref: '#/components/schemas/Meta' '401': $ref: '#/components/responses/Unauthorized' '429': $ref: '#/components/responses/TooManyRequests' post: operationId: createPhoneLine summary: Create a phone line description: | Creates a phone line on your account. Send a `name` and a `type`, and optionally call-handling `rules`, `availability`, SMS settings, and the registered texting `campaign_id` the line sends under. The response is the new line. Its `ivr_id` is the `phone_line_id` used elsewhere. tags: [Phone numbers] security: - apiKey: [] apiSecret: [] - oauth2: [numbers:write] requestBody: required: true content: application/json: schema: $ref: '#/components/schemas/CreatePhoneLine' responses: '200': description: Created phone line content: application/json: schema: type: object properties: data: $ref: '#/components/schemas/PhoneLine' meta: $ref: '#/components/schemas/Meta' '400': $ref: '#/components/responses/BadRequest' '401': $ref: '#/components/responses/Unauthorized' '429': $ref: '#/components/responses/TooManyRequests' /phone/public/lines/{line_id}: parameters: - name: line_id in: path required: true schema: type: string format: uuid get: operationId: getPhoneLine summary: Get a phone line description: | Returns one phone line by id, with its call-handling rules, availability and SMS settings. Its `ivr_id` is the `phone_line_id` used elsewhere. tags: [Phone numbers] security: - apiKey: [] apiSecret: [] - oauth2: [numbers:read] responses: '200': description: Phone line content: application/json: schema: type: object properties: data: $ref: '#/components/schemas/PhoneLine' meta: $ref: '#/components/schemas/Meta' '401': $ref: '#/components/responses/Unauthorized' '404': $ref: '#/components/responses/NotFound' '429': $ref: '#/components/responses/TooManyRequests' post: operationId: updatePhoneLine summary: Update a phone line description: | Changes how the line answers (this route uses POST, not PUT). `rules` run during business hours and `after_hour_rules` outside them, as set by `availability`. Each array needs one step with `start_rule: true` (the call arrives) and one with `end_rule: true` (no key pressed). Send the whole array you want; it replaces the existing one. To have an AI agent answer, use the `AI Agent` action with a published agent: `{"start_rule": true, "action": "AI Agent", "action_data": {"agent_id": "..."}}`. See [Receptionist](https://www.dropcowboy.com/developers/api/agents/ai-receptionist). tags: [Phone numbers] security: - apiKey: [] apiSecret: [] - oauth2: [numbers:write] requestBody: required: true content: application/json: schema: $ref: '#/components/schemas/CreatePhoneLine' examples: aiReceptionist: $ref: './examples/line-ai-receptionist.request.json' responses: '200': description: Updated phone line content: application/json: schema: type: object properties: data: $ref: '#/components/schemas/PhoneLine' meta: $ref: '#/components/schemas/Meta' '400': $ref: '#/components/responses/BadRequest' '401': $ref: '#/components/responses/Unauthorized' '404': $ref: '#/components/responses/NotFound' '429': $ref: '#/components/responses/TooManyRequests' delete: operationId: deletePhoneLine x-webhooks: [number.released] summary: Delete a phone line description: | Deletes one phone line from your account. An id that isn't one of your lines answers `404`. tags: [Phone numbers] security: - apiKey: [] apiSecret: [] - oauth2: [numbers:write] responses: '200': description: Phone line deleted '401': $ref: '#/components/responses/Unauthorized' '404': $ref: '#/components/responses/NotFound' '429': $ref: '#/components/responses/TooManyRequests' /phone/public/lines/{line_id}/assign: parameters: - name: line_id in: path required: true schema: type: string format: uuid post: operationId: assignNumberToPhoneLine x-webhooks: [number.updated] summary: Assign a number to a phone line description: | Points the number's calls and texts at this line. Returns 409 with code `TN_ALREADY_ON_CAMPAIGN` when the number is registered to a different texting campaign than the line's. A number not on your account returns `404` and nothing changes. tags: [Phone numbers] security: - apiKey: [] apiSecret: [] - oauth2: [numbers:write] requestBody: required: true content: application/json: schema: type: object required: [number] properties: number: type: string pattern: '^\+[1-9]\d{1,14}$' example: '+12125550120' confirm_move: type: boolean default: false description: Required to move a number onto a line that texts through an imported Twilio campaign when the number is on another campaign. responses: '200': description: Number assigned content: application/json: schema: type: object properties: data: $ref: '#/components/schemas/PhoneNumberLineAssignment' meta: $ref: '#/components/schemas/Meta' '400': $ref: '#/components/responses/BadRequest' '401': $ref: '#/components/responses/Unauthorized' '409': $ref: '#/components/responses/Conflict' '404': $ref: '#/components/responses/NotFound' '429': $ref: '#/components/responses/TooManyRequests' /phone/public/embed/token: post: operationId: mintEmbedToken summary: Create a site token description: | Call from your server, never the browser. Returns a short-lived JWT (up to one hour) that your page hands to a Building Blocks widget. `dialer:webrtc` and `phone:hub` tokens need BYOC turned on and a positive balance. A site token cannot mint another site token. `site_id`, when sent, must be a UUID, and it is checked before the BYOC and balance gates. Every refusal from those checks carries a stable snake_case reason in `details.code`. Branch on it rather than on `type`, which stays the generic status code (`bad-request`, `payment-required`, `forbidden`): | Status | `details.code` | Meaning | |---|---|---| | 400 | `invalid_site_id` | `site_id` was sent and is not a UUID. | | 402 | `insufficient_balance` | No developer allotment left and no available balance. | | 403 | `byoc_required` | `dialer:webrtc` or `phone:hub` was requested and BYOC is off. | | 403 | `site_token_not_allowed` | The caller authenticated with a site token. | A `403` without `details` is an authorization failure (the key lacks `numbers:write`, or the signed-in user cannot edit phone numbers). tags: [Building Blocks] security: - apiKey: [] apiSecret: [] - oauth2: [numbers:write] requestBody: required: true content: application/json: schema: $ref: '#/components/schemas/MintEmbedToken' examples: dialer: $ref: './examples/embed-token.request.json' responses: '200': description: Site token content: application/json: schema: type: object properties: data: $ref: '#/components/schemas/EmbedToken' meta: $ref: '#/components/schemas/Meta' examples: token: $ref: './examples/embed-token.response.json' '400': description: '`site_id` is not a UUID (`details.code` `invalid_site_id`).' content: application/json: schema: $ref: '#/components/schemas/EmbedMintError' example: type: https://api-v2.dropcowboy.com/errors/bad-request title: Bad Request status: 400 detail: site_id must be a UUID instance: /phone/public/embed/token request_id: 5a4b3c2d-1e0f-4a9b-8c7d-6e5f4a3b2c1d details: code: invalid_site_id '401': $ref: '#/components/responses/Unauthorized' '402': description: No developer allotment and no available balance for a dialer or phone-hub token (`details.code` `insufficient_balance`). content: application/json: schema: $ref: '#/components/schemas/EmbedMintError' example: type: https://api-v2.dropcowboy.com/errors/payment-required title: Payment Required status: 402 detail: Insufficient balance for embed dialer usage. instance: /phone/public/embed/token request_id: 5a4b3c2d-1e0f-4a9b-8c7d-6e5f4a3b2c1d details: code: insufficient_balance '403': description: | BYOC is off for dialer or phone-hub scopes (`byoc_required`), or the caller is a site token (`site_token_not_allowed`). Without `details`, the key or user is not allowed to mint. content: application/json: schema: $ref: '#/components/schemas/EmbedMintError' examples: byoc_required: value: type: https://api-v2.dropcowboy.com/errors/forbidden title: Forbidden status: 403 detail: Building Blocks dialer requires BYOC. Enable BYOC on your team to mint embed tokens. instance: /phone/public/embed/token request_id: 5a4b3c2d-1e0f-4a9b-8c7d-6e5f4a3b2c1d details: code: byoc_required site_token_not_allowed: value: type: https://api-v2.dropcowboy.com/errors/forbidden title: Forbidden status: 403 detail: A site token cannot mint another site token instance: /phone/public/embed/token request_id: 5a4b3c2d-1e0f-4a9b-8c7d-6e5f4a3b2c1d details: code: site_token_not_allowed '429': $ref: '#/components/responses/TooManyRequests' /carrier/ws: servers: - url: wss://detect.dropcowboy.com description: Detection WebSocket get: operationId: openDetectionSocket summary: Open a detection session description: | WebSocket upgrade. After it succeeds: 1. Send a text frame `{"type":"welcome","payload":{...}}` (see `DetectionWelcome`). 2. Wait for `{"type":"detection","event":"ready"}`. 3. Stream binary frames of mono audio (20 ms each) in the encoding and sample rate you declared. 4. Read text frames `{"type":"detection","event":"","payload":{...}}` (see `DetectionEvent`). `detection` can be refined: act on the first result and treat later ones with `is_refinement: true` as corrections. 5. Send `{"command":"stop"}` when the call no longer needs detection. The server sends `close` for the session, then `stopped`, then closes the socket with code 1000 and reason `stop`. To pool sockets instead, set `keep_alive` in the welcome: the socket stays open after `stopped` for the next welcome, and is closed with 1001 `idle` if none arrives within 60 seconds. A socket that never sends a welcome is also closed after 60 seconds. If you set `webhook` in the welcome, events are also POSTed to your URL, signed with your Detection API key (see the `detectionEvent` webhook). Do not reconnect mid-call: a new socket is a new session with no history. tags: [Detection] security: - detectionApiKey: [] parameters: - name: api_key in: query description: Alternative to the `Authorization` header when your client cannot set headers. schema: type: string requestBody: description: | Not an HTTP body: the JSON text frames you send after the upgrade. Send the welcome first; send stop to end the session. content: application/json: schema: oneOf: - $ref: '#/components/schemas/DetectionWelcome' - $ref: '#/components/schemas/DetectionStop' examples: welcome: $ref: './examples/detection-welcome.json' responses: '101': description: | Switching protocols. The schema below describes the JSON text frames the server sends on the open socket. content: application/json: schema: $ref: '#/components/schemas/DetectionEvent' '401': description: No Detection API key (plain text) '402': description: Account balance is exhausted (plain text) '403': description: Unknown or disabled Detection API key (plain text) '503': description: At capacity or draining. Treat as "no result" and continue the call. x-websocket-messages: client: - $ref: '#/components/schemas/DetectionWelcome' - $ref: '#/components/schemas/DetectionStop' server: - $ref: '#/components/schemas/DetectionEvent' /carrier/ws/twilio: servers: - url: wss://detect.dropcowboy.com description: Detection WebSocket get: operationId: openTwilioDetectionStream summary: Open a detection session for a Twilio Media Stream description: | Point a Twilio `` here. Twilio cannot send headers, so pass the key and options as `` elements: | Parameter | Meaning | |---|---| | `api_key` | Your Detection API key (required) | | `webhook_url` | Where to POST events. When set, results go to the webhook instead of back on the stream | | `webhook_events` | Comma-separated filter, for example `detection,beep,close` | | `strategy` | `standard`, `beep_only` or `live_check` | | `call_id` | Your own call identifier, echoed on every event | | `detection_timeout_sec` | Give up after this many seconds (default 45) | Audio is the stream's 8 kHz mu-law, so you don't need to transcode it. The key is checked when the stream starts, not at the upgrade. If it is missing, unknown, revoked or out of funds, the stream is closed with code `1008` (`unauthorized`). Check the key's `status` with `GET /register/public/detection-keys`. tags: [Detection] security: - detectionApiKey: [] responses: '101': description: Switching protocols (subprotocol `audio.twilio.com`) '403': description: The request has no `X-Twilio-Signature` header. Only Twilio can open this route; to test without Twilio, use `/carrier/ws`. '503': description: At capacity or draining. Retry with backoff. /phone/embed/playground-token: servers: - url: https://app-api-v2.dropcowboy.com description: Building Blocks widget routes are served from this host, not from api-v2.dropcowboy.com post: operationId: mintPlaygroundToken summary: Create a playground token description: > Returns a token, valid for 10 minutes, for trying Building Blocks widgets in the dashboard. Authenticate with your dashboard session token, not `x-key` and `x-secret`. The token carries `scope: ["dialer:webrtc"]`, your `team_id` and your default `pool_id`. Never put the token in a URL. tags: [Building Blocks] security: - portalJwt: [] responses: '200': description: Playground token content: application/json: schema: $ref: '#/components/schemas/PlaygroundToken' example: token: example-playground-token expires_at: 1774041600000 jti: d1f3a8e2-7c4b-4f9a-9d22-9c1e2f3a4b5c pool_id: 8f3e1a9c-2d4b-4e7a-9c1f-5b6a7c8d9e0f '401': $ref: '#/components/responses/Unauthorized' '402': description: | Your billing status blocks the request. The body is `{message, detail}`, not problem details. content: application/json: schema: type: object properties: message: type: string detail: type: object '403': $ref: '#/components/responses/Forbidden' '429': $ref: '#/components/responses/TooManyRequests' /phone/embed/consent: servers: - url: https://app-api-v2.dropcowboy.com description: Building Blocks widget routes are served from this host, not from api-v2.dropcowboy.com post: operationId: captureEmbedConsent summary: Record consent from a widget description: > Records a visitor's consent on your site, together with the page's `Origin`, and returns the `consent_id` that widget texts and `POST /phone/embed/callback` need. Send the site token from `POST /phone/public/embed/token` as `Authorization: Bearer`. Never put the token in a URL. tags: [Building Blocks] security: - embedJwt: [] requestBody: required: true content: application/json: schema: type: object required: [text] properties: text: type: string example: By checking this box you agree to receive calls and texts. contact_phone: type: string example: '+12125550120' responses: '200': description: Consent captured content: application/json: schema: type: object properties: data: type: object properties: consent_id: type: string format: uuid example: 5a4b3c2d-1e0f-4a9b-8c7d-6e5f4a3b2c1d '401': $ref: '#/components/responses/Unauthorized' '429': $ref: '#/components/responses/TooManyRequests' /phone/embed/callback: servers: - url: https://app-api-v2.dropcowboy.com description: Building Blocks widget routes are served from this host, not from api-v2.dropcowboy.com post: operationId: requestEmbedCallback summary: Request a callback from a widget description: > Calls the visitor back. Send the site token as `Authorization: Bearer`, never in a URL, and the `consent_id` from `POST /phone/embed/consent` or the `` widget. If your account has turned on `POST /register/public/building-blocks/contact-consent`, you can send `contact_id` instead and the contact's granted calling consent is used; a US number without either is refused with `403` and `consent_required`. tags: [Building Blocks] security: - embedJwt: [] requestBody: required: true content: application/json: schema: type: object required: [to] properties: to: type: string example: '+12125550120' consent_id: type: string format: uuid description: Required for US numbers unless the contact-consent setting is on and `contact_id` is sent. example: 5a4b3c2d-1e0f-4a9b-8c7d-6e5f4a3b2c1d contact_id: type: string format: uuid description: A contact in your account. Used to look up existing consent only when the contact-consent setting is on. example: 7c8d9e0f-1a2b-4c3d-8e9f-0a1b2c3d4e5f from: type: string example: '+14155550120' responses: '200': description: Callback requested '400': $ref: '#/components/responses/BadRequest' '401': $ref: '#/components/responses/Unauthorized' '429': $ref: '#/components/responses/TooManyRequests' /phone/embed/inbox: servers: - url: https://app-api-v2.dropcowboy.com description: Building Blocks widget routes are served from this host, not from api-v2.dropcowboy.com get: operationId: listEmbedInbox summary: 'List a site''s Inbox Tasks' description: > Lists the Inbox Tasks for the site named in the site token. Send the token as `Authorization: Bearer`, never in a URL. The site always comes from the token, so a `site_id` query parameter is ignored. tags: [Building Blocks] security: - embedJwt: [] parameters: - name: origin_type in: query schema: type: string enum: [embed, direct, portal, api] default: embed - name: origin_component in: query schema: type: string example: chat - name: limit in: query schema: type: integer minimum: 1 maximum: 100 - name: cursor in: query schema: type: string - name: status in: query schema: type: string example: open responses: '200': description: Site-scoped inbox tasks content: application/json: schema: type: object properties: data: type: object properties: tasks: type: array items: type: object total: type: integer next_cursor: type: [string, 'null'] '401': $ref: '#/components/responses/Unauthorized' '403': $ref: '#/components/responses/Forbidden' '429': $ref: '#/components/responses/TooManyRequests' /media/public/media: get: operationId: listMedia summary: List media files tags: [Media] security: - apiKey: [] apiSecret: [] - oauth2: [media:read] description: | Lists playable, non-deleted media files, newest first. Page with `skip` and `limit` until `skip` reaches `total`. parameters: - name: limit in: query schema: type: integer minimum: 1 default: 100 - $ref: '#/components/parameters/SkipParam' responses: '200': description: Paginated media files content: application/json: schema: type: object properties: data: $ref: '#/components/schemas/MediaPage' meta: $ref: '#/components/schemas/Meta' '401': $ref: '#/components/responses/Unauthorized' '429': $ref: '#/components/responses/TooManyRequests' post: operationId: uploadMedia summary: Add a media file description: | Two ways to add audio, both JSON: - **From a URL.** Send `url`. We fetch the file and the entry is playable when this returns (`media_exists: true`). - **Signed upload.** Send `signed_upload: true`. The response carries `upload.mp3` and `upload.wav`, each a signed `url` and the `content_type` to send with it. `PUT` the file bytes to one of them with that `Content-Type`, then call `POST /media/public/media/{media_id}/complete`. Until then the entry has `media_exists: false` and is hidden from the list. Speech to text and voice cloning detect whether a `media_id` was uploaded as MP3 or WAV, so you do not need to pass `ext` when you use it there. tags: [Media] security: - apiKey: [] apiSecret: [] - oauth2: [media:write] requestBody: required: true content: application/json: schema: $ref: '#/components/schemas/CreateMedia' responses: '201': description: Media created content: application/json: schema: type: object properties: data: $ref: '#/components/schemas/MediaCreated' meta: $ref: '#/components/schemas/Meta' '400': $ref: '#/components/responses/BadRequest' '401': $ref: '#/components/responses/Unauthorized' '429': $ref: '#/components/responses/TooManyRequests' /media/public/media/{media_id}: parameters: - name: media_id in: path required: true schema: type: string get: operationId: getMedia summary: Get a media file description: | Returns the entry, including one whose audio hasn't arrived yet (`media_exists: false`). A deleted entry returns `404`. tags: [Media] security: - apiKey: [] apiSecret: [] - oauth2: [media:read] responses: '200': description: Media details content: application/json: schema: type: object properties: data: $ref: '#/components/schemas/Media' meta: $ref: '#/components/schemas/Meta' '401': $ref: '#/components/responses/Unauthorized' '404': $ref: '#/components/responses/NotFound' '429': $ref: '#/components/responses/TooManyRequests' put: operationId: updateMedia summary: Update a media file description: | Sets `name` and `type`. Returns the fields written, not the full entry. An unknown or deleted `media_id` returns `404`. tags: [Media] security: - apiKey: [] apiSecret: [] - oauth2: [media:write] requestBody: required: true content: application/json: schema: $ref: '#/components/schemas/UpdateMedia' responses: '200': description: Fields written content: application/json: schema: type: object properties: data: $ref: '#/components/schemas/MediaUpdated' meta: $ref: '#/components/schemas/Meta' '400': $ref: '#/components/responses/BadRequest' '401': $ref: '#/components/responses/Unauthorized' '404': $ref: '#/components/responses/NotFound' '429': $ref: '#/components/responses/TooManyRequests' delete: operationId: deleteMedia summary: Delete a media file description: | Soft delete: sets `deleted_at` and hides the entry. An unknown or already deleted `media_id` returns `404`. tags: [Media] security: - apiKey: [] apiSecret: [] - oauth2: [media:write] responses: '200': description: Media deleted content: application/json: schema: type: object properties: data: type: object properties: deleted: type: boolean enum: [true] meta: $ref: '#/components/schemas/Meta' '401': $ref: '#/components/responses/Unauthorized' '404': $ref: '#/components/responses/NotFound' '429': $ref: '#/components/responses/TooManyRequests' /media/public/media/{media_id}/complete: parameters: - name: media_id in: path required: true schema: type: string format: uuid post: operationId: completeMediaUpload summary: Complete a signed upload description: | Call after the `PUT` to a signed upload URL succeeds. Marks the entry playable (`media_exists: true`). Returns `400` when no file was uploaded yet. tags: [Media] security: - apiKey: [] apiSecret: [] - oauth2: [media:write] responses: '200': description: Upload completed content: application/json: schema: type: object properties: data: $ref: '#/components/schemas/MediaUploadCompleted' meta: $ref: '#/components/schemas/Meta' '400': $ref: '#/components/responses/BadRequest' '401': $ref: '#/components/responses/Unauthorized' '404': $ref: '#/components/responses/NotFound' '429': $ref: '#/components/responses/TooManyRequests' /media/public/media/{media_id}/policy: parameters: - name: media_id in: path required: true schema: type: string format: uuid get: operationId: getMediaUploadPolicy summary: Get new signed upload URLs description: | Returns new signed upload URLs for an existing entry, for example when the ones from `POST /media/public/media` have expired. tags: [Media] security: - apiKey: [] apiSecret: [] - oauth2: [media:write] responses: '200': description: Signed upload URLs content: application/json: schema: type: object properties: data: $ref: '#/components/schemas/MediaUploadPolicy' meta: $ref: '#/components/schemas/Meta' '401': $ref: '#/components/responses/Unauthorized' '404': $ref: '#/components/responses/NotFound' '429': $ref: '#/components/responses/TooManyRequests' /campaign/public/campaigns: get: operationId: listCampaigns summary: List campaigns description: | Lists the campaigns on your account. Narrow the list with `status`, `type` and `search_term`, and limit the page with `limit`. The response also carries `counts`, the number of campaigns in each status across the whole account rather than only this page. tags: [Campaigns] security: - apiKey: [] apiSecret: [] - oauth2: [campaigns:read] parameters: - $ref: '#/components/parameters/SkipParam' - name: limit in: query schema: type: integer - name: search_term in: query schema: type: string - name: status in: query schema: type: string - name: type in: query schema: type: string enum: [rvm, sms, email, voice_broadcast, ai_broadcast] responses: '200': description: Campaigns content: application/json: schema: type: object properties: data: $ref: '#/components/schemas/CampaignsPage' meta: $ref: '#/components/schemas/Meta' '401': $ref: '#/components/responses/Unauthorized' '403': $ref: '#/components/responses/InsufficientScope' '429': $ref: '#/components/responses/TooManyRequests' post: operationId: createCampaign x-webhooks: [campaign.created] summary: Create a campaign description: | Creates a campaign for one channel: `rvm`, `sms`, `email`, `voice_broadcast` or `ai_broadcast`. A new campaign may need review by Drop Cowboy before it can send; until then `approved` is `false`. Voice and messaging campaigns follow TCPA rules (calling hours, consent, your account's contact frequency limit). Email campaigns follow CAN-SPAM (unsubscribe link, physical address, email frequency limit). Accounts that have not verified their brand get `403`; past-due accounts get `402`. tags: [Campaigns] security: - apiKey: [] apiSecret: [] - oauth2: [campaigns:write] requestBody: required: true content: application/json: schema: $ref: '#/components/schemas/CreateCampaign' examples: rvm: $ref: './examples/campaign-rvm.request.json' email: $ref: './examples/campaign-email.request.json' responses: '201': description: Campaign created content: application/json: schema: type: object properties: data: $ref: '#/components/schemas/CampaignCreated' meta: $ref: '#/components/schemas/Meta' '400': $ref: '#/components/responses/BadRequest' '401': $ref: '#/components/responses/Unauthorized' '402': $ref: '#/components/responses/PaymentRequired' '403': description: Scope missing, brand not verified, or a trial limit was reached. content: application/json: schema: $ref: '#/components/schemas/Error' '429': $ref: '#/components/responses/TooManyRequests' /campaign/public/campaigns/{id}: parameters: - $ref: '#/components/parameters/CampaignIdParam' get: operationId: getCampaign summary: Get a campaign description: | Returns one campaign, including the settings you sent and its current `status`. Find the `campaign_id` with List campaigns. tags: [Campaigns] security: - apiKey: [] apiSecret: [] - oauth2: [campaigns:read] responses: '200': description: Campaign content: application/json: schema: type: object properties: data: $ref: '#/components/schemas/Campaign' meta: $ref: '#/components/schemas/Meta' '401': $ref: '#/components/responses/Unauthorized' '403': $ref: '#/components/responses/InsufficientScope' '404': $ref: '#/components/responses/NotFound' '429': $ref: '#/components/responses/TooManyRequests' put: operationId: updateCampaign x-webhooks: [campaign.updated] summary: Update a campaign description: | Replaces `campaign_data` and optionally schedules the start with `deliver_at`. `risk` and `approved` are set by Drop Cowboy and ignored here. Changing what contacts receive (message, audio, voice, AI agent, lists, brand, caller ID or sending numbers) sends the campaign back to compliance review, and a sending campaign that isn't approved again straight away is paused. tags: [Campaigns] security: - apiKey: [] apiSecret: [] - oauth2: [campaigns:write] requestBody: required: true content: application/json: schema: $ref: '#/components/schemas/UpdateCampaign' responses: '200': description: Campaign updated content: application/json: schema: type: object properties: data: $ref: '#/components/schemas/Campaign' meta: $ref: '#/components/schemas/Meta' '400': $ref: '#/components/responses/BadRequest' '401': $ref: '#/components/responses/Unauthorized' '403': $ref: '#/components/responses/InsufficientScope' '404': $ref: '#/components/responses/NotFound' '429': $ref: '#/components/responses/TooManyRequests' delete: operationId: deleteCampaign x-webhooks: [campaign.deleted] summary: Delete a campaign description: | Deletes one campaign. Find the `campaign_id` with List campaigns. tags: [Campaigns] security: - apiKey: [] apiSecret: [] - oauth2: [campaigns:write] responses: '200': description: Campaign deleted content: application/json: schema: type: object properties: data: type: object meta: $ref: '#/components/schemas/Meta' '401': $ref: '#/components/responses/Unauthorized' '403': $ref: '#/components/responses/InsufficientScope' '404': $ref: '#/components/responses/NotFound' '429': $ref: '#/components/responses/TooManyRequests' /campaign/public/campaigns/{id}/start: parameters: - $ref: '#/components/parameters/CampaignIdParam' post: operationId: startCampaign summary: Start a campaign description: | Starts sending to the campaign's lists. Each contact's outcome arrives on the channel's status webhook (`contact.rvm.status`, `contact.sms.status` or `contact.email.status`) and the campaign itself emits `campaign.started` and `campaign.completed`. tags: [Campaigns] security: - apiKey: [] apiSecret: [] - oauth2: [campaigns:send] x-webhooks: [campaign.started, campaign.completed, contact.rvm.status, contact.rvm.receipt, contact.sms.status, contact.email.status] responses: '200': description: | Campaign started, or held for compliance review. `started` is false when nothing was queued. Starting a campaign that already completed returns it unchanged with `started: true`. A campaign still waiting for review returns `campaign_id`, `approved: false` and `started: false` and stays in the review queue; start it again once it's approved. content: application/json: schema: type: object properties: data: oneOf: - type: object title: Started required: [campaign, started] properties: campaign: $ref: '#/components/schemas/Campaign' started: type: boolean - type: object title: Awaiting review required: [campaign_id, approved, started] properties: campaign_id: type: string format: uuid approved: type: boolean const: false started: type: boolean const: false meta: $ref: '#/components/schemas/Meta' '400': description: Bad request, for example the campaign is still a draft. content: application/json: schema: $ref: '#/components/schemas/Error' '401': $ref: '#/components/responses/Unauthorized' '402': $ref: '#/components/responses/PaymentRequired' '403': description: Scope missing, or the brand is not verified (`error` is `trial_feature_blocked`). content: application/json: schema: oneOf: - $ref: '#/components/schemas/Error' - $ref: '#/components/schemas/PlanGateError' '404': $ref: '#/components/responses/NotFound' '429': $ref: '#/components/responses/TooManyRequests' /campaign/public/campaigns/{id}/pause: parameters: - $ref: '#/components/parameters/CampaignIdParam' post: operationId: pauseCampaign x-webhooks: [campaign.paused] summary: Pause a campaign description: | Pauses one campaign. The response is the campaign with its updated `status`. Find the `campaign_id` with List campaigns. tags: [Campaigns] security: - apiKey: [] apiSecret: [] - oauth2: [campaigns:send] responses: '200': description: Campaign paused content: application/json: schema: type: object properties: data: $ref: '#/components/schemas/Campaign' meta: $ref: '#/components/schemas/Meta' '400': $ref: '#/components/responses/BadRequest' '401': $ref: '#/components/responses/Unauthorized' '403': $ref: '#/components/responses/InsufficientScope' '404': $ref: '#/components/responses/NotFound' '429': $ref: '#/components/responses/TooManyRequests' /campaign/public/campaigns/{id}/stats: parameters: - $ref: '#/components/parameters/CampaignIdParam' get: operationId: getCampaignStats summary: Get campaign statistics description: | Returns delivery totals for one campaign: sent, delivered, failed, pending and billable counts, with the matching rates. Use `start_ts` and `end_ts` to limit the window and `bucket_type` to choose how the results are grouped. tags: [Campaigns] security: - apiKey: [] apiSecret: [] - oauth2: [campaigns:read] parameters: - name: bucket_type in: query schema: type: string - name: start_ts in: query description: Epoch milliseconds. schema: type: integer - name: end_ts in: query description: Epoch milliseconds. schema: type: integer responses: '200': description: Campaign statistics content: application/json: schema: type: object properties: data: $ref: '#/components/schemas/CampaignStats' meta: $ref: '#/components/schemas/Meta' '401': $ref: '#/components/responses/Unauthorized' '403': $ref: '#/components/responses/InsufficientScope' '404': $ref: '#/components/responses/NotFound' '429': $ref: '#/components/responses/TooManyRequests' /campaign/public/deliveries/{drop_id}/receipt: parameters: - name: drop_id in: path required: true description: The `drop_id` from `contact.rvm.status`, `contact.rvm.receipt` or the `callback_url` body. schema: type: string get: operationId: getDeliveryReceipt summary: Get a new proof-of-delivery link description: | Issues a new `proof_of_delivery_url` for one of your drops, for example after the link you were sent has expired. Each call issues another link, valid for 7 days and never past the drop's 7-day window; earlier links keep working until they expire. Proof of delivery is available for 7 days after the drop, so links can be re-issued only within that time. `receipt_ready_at` is null while the recording is not stored yet, and the link answers `404` with `Retry-After` until it is. Only drops where a voicemail system took the call have one: a ringless voicemail that ended with `reason_code` 0, 4001 or 4002, or a voice broadcast or AI call that ended with 0 in a mailbox. Everything else, including another account's drop or one more than 7 days old, answers `404`. tags: [Ringless voicemail] security: - apiKey: [] apiSecret: [] - oauth2: [campaigns:read] responses: '200': description: A fresh link content: application/json: schema: type: object properties: data: $ref: '#/components/schemas/DeliveryReceiptLink' meta: $ref: '#/components/schemas/Meta' examples: ready: $ref: './examples/receipt.remint.response.json' '401': $ref: '#/components/responses/Unauthorized' '403': $ref: '#/components/responses/InsufficientScope' '404': description: No proof of delivery for this drop. content: application/json: schema: $ref: '#/components/schemas/Error' example: type: https://api-v2.dropcowboy.com/errors/not-found title: Not Found status: 404 detail: No proof of delivery for this drop instance: /campaign/public/deliveries/4b50e620-5173-4cb2-96f8-5a0f39a4f1ba/receipt '429': $ref: '#/components/responses/TooManyRequests' '500': $ref: '#/components/responses/ServerError' /campaign/public/receipts/{token}: parameters: - name: token in: path required: true description: The last path segment of a `proof_of_delivery_url`. example: EXAMPLE_TOKEN_FROM_PROOF_OF_DELIVERY_URL___ schema: type: string pattern: '^[A-Za-z0-9_-]{43}$' get: operationId: openDeliveryReceipt summary: Play a proof of delivery description: | The `proof_of_delivery_url` itself. No credentials: the URL is the credential, so treat it like a password and share it only with people who should hear the recording. Opening it redirects to a short-lived audio URL (a few minutes), so fetch the audio immediately rather than storing the redirect target. Each play is recorded. For a ringless voicemail, all three codes play the call recording, both sides, from the moment the recipient's carrier answered until hangup. For code 0 it includes your message. For 4001 (VoiceMail Not Setup) and 4002 (Mailbox full) it contains the carrier's announcement, which shows the mailbox was reached and why the message couldn't be left. For a voice broadcast or AI call it plays the message left after the beep. A link is valid for 7 days. After that it answers `410` for 7 days, then `404`. Proof of delivery is available for 7 days after the drop, so store the audio yourself if you need to keep it. Within those 7 days, get a new link with `GET /campaign/public/deliveries/{drop_id}/receipt`. Every response carries `Cache-Control: no-store` and `Referrer-Policy: no-referrer`. Error bodies here are `{error, message}`, not problem details. tags: [Ringless voicemail] security: [] responses: '302': description: Redirect to the audio. headers: Location: description: Short-lived audio URL. schema: type: string format: uri '404': description: | Either the recording is not stored yet (`error` is `not_ready`, with `Retry-After` in seconds; retry then), or the token is unknown or no longer servable (`error` is `not_found`; do not retry). headers: Retry-After: description: Seconds to wait. Sent only with `not_ready`. schema: type: integer example: 60 content: application/json: schema: $ref: '#/components/schemas/DeliveryReceiptLinkError' examples: notReady: summary: Recording not stored yet value: error: not_ready message: Proof of delivery is not available yet. Retry later. notFound: summary: Unknown token value: error: not_found message: Not found '410': description: The link has expired. Request a new one. content: application/json: schema: $ref: '#/components/schemas/DeliveryReceiptLinkError' example: error: expired message: This proof of delivery link has expired. Request a new one from the API. '429': $ref: '#/components/responses/TooManyRequests' '500': description: Unexpected failure. Safe to retry. content: application/json: schema: $ref: '#/components/schemas/DeliveryReceiptLinkError' example: error: server_error message: Internal error /campaign/public/balance: get: operationId: getBalance summary: Get your balance description: | Returns your account balance in US dollars. One balance covers every channel. The response includes the `available` amount, which is the balance minus what is reserved for sends still in flight, and your auto-recharge settings. tags: [Account] security: - apiKey: [] apiSecret: [] - oauth2: [balance:read] responses: '200': description: Account balance content: application/json: schema: type: object properties: data: $ref: '#/components/schemas/Balance' meta: $ref: '#/components/schemas/Meta' '401': $ref: '#/components/responses/Unauthorized' '429': $ref: '#/components/responses/TooManyRequests' /register/public/webhooks: get: operationId: listWebhooks summary: List webhook subscriptions description: One subscription per event type. Signing secrets are not included; see `GET /register/public/account/webhook-signing-secret`. tags: [Webhooks] security: - apiKey: [] apiSecret: [] - oauth2: [webhooks:read] responses: '200': description: Webhook subscriptions content: application/json: schema: type: object properties: data: type: array items: $ref: '#/components/schemas/Webhook' meta: $ref: '#/components/schemas/Meta' examples: list: $ref: './examples/webhooks.list.response.json' '401': $ref: '#/components/responses/Unauthorized' '403': $ref: '#/components/responses/InsufficientScope' '429': $ref: '#/components/responses/TooManyRequests' post: operationId: createWebhook summary: Subscribe to an event description: | Subscribes `hook_url` to one event type. An account holds one subscription per event type: creating another for the same `hook_type` replaces the old one and issues a new signing secret, so posting again is also how you rotate a secret or move a URL. The `signing_secret` is returned here and by `GET /register/public/account/webhook-signing-secret`. `hook_type` and `hook_url` are not validated when you subscribe. Check the event name against `GET /register/public/events` and use an HTTPS URL that answers 2xx within 5 seconds. Only event types on that list are delivered with signing and retries; any other `hook_type` is saved but gets no signed, retried delivery. See [Webhooks](https://www.dropcowboy.com/developers/api/webhooks) for signing, retries and deduplication. tags: [Webhooks] security: - apiKey: [] apiSecret: [] - oauth2: [webhooks:write] requestBody: required: true content: application/json: schema: $ref: '#/components/schemas/CreateWebhook' examples: rvmStatus: $ref: './examples/webhooks.create.request.json' responses: '200': description: Subscribed content: application/json: schema: type: object properties: data: $ref: '#/components/schemas/WebhookCreated' meta: $ref: '#/components/schemas/Meta' examples: created: $ref: './examples/webhooks.create.response.json' '401': $ref: '#/components/responses/Unauthorized' '403': $ref: '#/components/responses/InsufficientScope' '429': $ref: '#/components/responses/TooManyRequests' '500': $ref: '#/components/responses/ServerError' /register/public/webhooks/{hook_type}: parameters: - name: hook_type in: path required: true description: The event type of the subscription, for example `contact.rvm.status`. Not the `webhook_id`. schema: type: string example: contact.rvm.status get: operationId: getWebhook summary: Get a webhook subscription description: | Returns your subscription for one event type, such as `contact.rvm.status`. Pass the event type as `hook_type`, not a `webhook_id`. An event type you have no subscription for answers `404`. tags: [Webhooks] security: - apiKey: [] apiSecret: [] - oauth2: [webhooks:read] responses: '200': description: Webhook subscription content: application/json: schema: type: object properties: data: $ref: '#/components/schemas/Webhook' meta: $ref: '#/components/schemas/Meta' '401': $ref: '#/components/responses/Unauthorized' '403': $ref: '#/components/responses/InsufficientScope' '404': $ref: '#/components/responses/NotFound' '429': $ref: '#/components/responses/TooManyRequests' delete: operationId: deleteWebhook summary: Delete a webhook subscription description: Succeeds even when there was no subscription. tags: [Webhooks] security: - apiKey: [] apiSecret: [] - oauth2: [webhooks:write] responses: '200': description: Unsubscribed content: application/json: schema: type: object properties: data: type: object properties: result: type: boolean meta: $ref: '#/components/schemas/Meta' '401': $ref: '#/components/responses/Unauthorized' '403': $ref: '#/components/responses/InsufficientScope' '429': $ref: '#/components/responses/TooManyRequests' /register/public/account: get: operationId: getAccount summary: Get your account description: | Your team id, the account owner, and the contact frequency limit every phone send is checked against. For the balance use `GET /campaign/public/balance`. tags: [Account] security: - apiKey: [] apiSecret: [] - oauth2: [balance:read] responses: '200': description: Account details content: application/json: schema: type: object properties: data: $ref: '#/components/schemas/Account' meta: $ref: '#/components/schemas/Meta' examples: account: $ref: './examples/account.response.json' '401': $ref: '#/components/responses/Unauthorized' '429': $ref: '#/components/responses/TooManyRequests' /register/public/integration-readiness: get: operationId: getIntegrationReadiness summary: Get Building Blocks readiness description: > Reports your Building Blocks setup: whether your own carrier (BYOC) is connected, whether Building Blocks is on, your numbers, voices and agents, your prepaid funds, and how much of your builder allotment is left. Carrier credentials are never returned. tags: [Account] security: - apiKey: [] apiSecret: [] - oauth2: [balance:read] responses: '200': description: Readiness snapshot content: application/json: schema: type: object properties: data: $ref: '#/components/schemas/IntegrationReadiness' meta: $ref: '#/components/schemas/Meta' '401': $ref: '#/components/responses/Unauthorized' '429': $ref: '#/components/responses/TooManyRequests' /register/public/building-blocks/enable: post: operationId: enableBuildingBlocks summary: Turn on Building Blocks description: > Moves your account to the Building Blocks plan, where you call and text through your own carrier (BYOC). This releases the numbers you rent from Drop Cowboy, so send `confirm_leave_retail: true` to confirm; without it the request fails with `400`. tags: [Account] security: - apiKey: [] apiSecret: [] - oauth2: [account:write] requestBody: required: true content: application/json: schema: type: object required: [confirm_leave_retail] properties: confirm_leave_retail: type: boolean enum: [true] responses: '200': description: Building Blocks enabled content: application/json: schema: type: object properties: data: $ref: '#/components/schemas/EnableBuildingBlocksResult' meta: $ref: '#/components/schemas/Meta' '400': $ref: '#/components/responses/BadRequest' '401': $ref: '#/components/responses/Unauthorized' '429': $ref: '#/components/responses/TooManyRequests' /register/public/building-blocks/contact-consent: post: operationId: setEmbedContactConsent summary: Let embedded widgets use a contact's existing consent description: > Turns on or off the setting that lets `POST /phone/embed/sms` and `POST /phone/embed/callback` send to a contact who already gave consent in Drop Cowboy. It is off by default, and while it is off every US send needs a `consent_id` from the consent widget. When it is on and a request sends `contact_id` without `consent_id`, Drop Cowboy loads that contact from your account and uses its granted texting consent (for SMS) or calling consent (for callbacks). The destination must be one of the contact's own numbers, and the send is refused with `consent_required` if the contact has no granted consent for that channel, has opted out or replied STOP, or is on your Do Not Call list. A signed-in dashboard user also needs permission to edit the Trust Center. tags: [Account] security: - apiKey: [] apiSecret: [] - oauth2: [account:write] requestBody: required: true content: application/json: schema: type: object required: [enabled] properties: enabled: type: boolean responses: '200': description: Setting saved content: application/json: schema: type: object properties: data: $ref: '#/components/schemas/EmbedContactConsentSettingResult' meta: $ref: '#/components/schemas/Meta' '400': $ref: '#/components/responses/BadRequest' '401': $ref: '#/components/responses/Unauthorized' '403': $ref: '#/components/responses/Forbidden' '429': $ref: '#/components/responses/TooManyRequests' /register/public/account/webhook-signing-secret: get: operationId: getWebhookSigningSecret summary: List webhook signing secrets description: | Each subscription has its own secret. Verify a delivery with the secret of the subscription it arrived on, which is the event type in the payload's `event` field. tags: [Webhooks] security: - apiKey: [] apiSecret: [] - oauth2: [webhooks:read] responses: '200': description: One entry per subscription content: application/json: schema: type: object properties: data: type: array items: type: object properties: hook_type: type: string signing_secret: type: string meta: $ref: '#/components/schemas/Meta' examples: secrets: $ref: './examples/webhooks.signing-secrets.response.json' '401': $ref: '#/components/responses/Unauthorized' '403': $ref: '#/components/responses/InsufficientScope' '429': $ref: '#/components/responses/TooManyRequests' /register/public/apikeys: get: operationId: listApiKeys summary: List API keys description: | Secrets are never returned after creation. `last_used_at` and `request_count` show which keys are still in use, which is what you check before deleting an old key during rotation. `expires_at` is `null` for a key that never expires. tags: [Authentication] security: - apiKey: [] apiSecret: [] - oauth2: [balance:read] parameters: - name: type in: query required: false schema: type: string responses: '200': description: API keys content: application/json: schema: type: object properties: data: type: array items: $ref: '#/components/schemas/ApiKey' meta: $ref: '#/components/schemas/Meta' examples: list: $ref: './examples/apikeys.list.response.json' '401': $ref: '#/components/responses/Unauthorized' '403': $ref: '#/components/responses/InsufficientScope' '429': $ref: '#/components/responses/TooManyRequests' post: operationId: createApiKey summary: Create an API key description: | Creates a key and secret pair. The `secret` is in this response only; store it before you do anything else. To rotate: create a new key, deploy it, confirm the old key's `last_used_at` stops moving in `GET /register/public/apikeys`, then delete the old key. For an agent or a one-off script, pass `scopes` and `expires_in_seconds` to get a least-privilege key that removes itself. Trial accounts get `403` with `trial_feature_blocked`. Only a verified account can create keys: until verification is finished in the dashboard this returns `403` with `team-not-verified`. Existing keys keep working. tags: [Authentication] security: - apiKey: [] apiSecret: [] - oauth2: [account:write] requestBody: required: false content: application/json: schema: type: object properties: name: type: string description: Label shown in the dashboard. Defaults to "API Key". type: type: string default: standard description: Leave as `standard`. scopes: type: array minItems: 1 items: type: string description: > Scopes for the new key, from the list in Authentication. Omit to give the key the scopes of the credential making this request. Unknown names get `400`; a scope the calling credential lacks gets `403`. expires_in_seconds: type: integer minimum: 300 maximum: 31536000 description: > Lifetime of the key in seconds, from 300 (5 minutes) to 31536000 (365 days). The response's `expires_at` is when it ends. An expired key is refused by the API and deleted permanently, usually within two minutes of `expires_at`. Omit it, or send `null`, for a key that never expires. Anything else, including a string or a fraction, gets `400`. examples: rotation: $ref: './examples/apikeys.create.request.json' shortLived: $ref: './examples/apikeys.create.short-lived.request.json' responses: '200': description: Key created. The secret is shown once. content: application/json: schema: type: object properties: data: $ref: '#/components/schemas/ApiKeyCreated' meta: $ref: '#/components/schemas/Meta' examples: created: $ref: './examples/apikeys.create.response.json' '400': description: > `type` ends in `validation-error`: `scopes` is empty, not an array or names an unknown scope, or `expires_in_seconds` is not an integer from 300 to 31536000. content: application/json: schema: $ref: '#/components/schemas/Error' example: type: https://api-v2.dropcowboy.com/errors/validation-error title: Validation Error status: 400 detail: expires_in_seconds must be an integer from 300 to 31536000 instance: /register/public/apikeys request_id: c2a6e8f4-3b9d-4c1e-8a7f-5d3b1e9c6a24 '401': $ref: '#/components/responses/Unauthorized' '403': description: > Scope missing, a requested scope the calling credential lacks (`type` ends in `insufficient-scope`), API access is not available on a trial (`type` ends in `trial_feature_blocked`), or the account is not verified yet (`type` ends in `team-not-verified`). content: application/json: schema: $ref: '#/components/schemas/Error' examples: teamNotVerified: summary: The account has not finished verification value: type: https://api-v2.dropcowboy.com/errors/team-not-verified title: Team Not Verified status: 403 detail: Verify your account before creating API keys. instance: /register/public/apikeys request_id: c2a6e8f4-3b9d-4c1e-8a7f-5d3b1e9c6a24 '429': $ref: '#/components/responses/TooManyRequests' /register/public/apikeys/{key_id}: parameters: - name: key_id in: path required: true description: > The key's `_id` from `GET /register/public/apikeys` (24-character hex). Not the `key` credential. schema: type: string pattern: '^[0-9a-f]{24}$' example: 65f2a9b7c8d4e1f234567890 delete: operationId: deleteApiKey summary: Delete an API key description: > The key stops authenticating immediately and can't be restored. Use this as the last step of a rotation. tags: [Authentication] security: - apiKey: [] apiSecret: [] - oauth2: [account:write] responses: '200': description: API key deleted content: application/json: schema: type: object properties: data: type: object properties: deleted: type: boolean meta: $ref: '#/components/schemas/Meta' '400': $ref: '#/components/responses/BadRequest' '401': $ref: '#/components/responses/Unauthorized' '403': $ref: '#/components/responses/InsufficientScope' '404': $ref: '#/components/responses/NotFound' '429': $ref: '#/components/responses/TooManyRequests' /register/public/detection-keys: get: operationId: listDetectionKeys summary: List Detection keys description: | Keys that authenticate `wss://detect.dropcowboy.com`. The key itself is never returned after creation; `key_hint` is its last four characters so you can tell keys apart. `status` is `active`, `suspended` (your balance ran out or Detection is switched off; the key comes back on its own when that clears) or `expired`. An account without its own number pool gets an empty list. tags: [Detection] security: - apiKey: [] apiSecret: [] - oauth2: [balance:read] responses: '200': description: Detection keys, newest first content: application/json: schema: type: object properties: data: type: array items: $ref: '#/components/schemas/DetectionKey' meta: $ref: '#/components/schemas/Meta' examples: list: $ref: './examples/detection-keys.list.response.json' '401': $ref: '#/components/responses/Unauthorized' '403': $ref: '#/components/responses/InsufficientScope' '429': $ref: '#/components/responses/TooManyRequests' '503': $ref: '#/components/responses/DetectionKeyServiceUnavailable' post: operationId: createDetectionKey summary: Create a Detection key description: | Creates a Detection API key for your server. `api_key` is in this response only; store it before you do anything else. Send it as `Authorization: Bearer ` (or `?api_key=`) on the WebSocket upgrade, or as the `api_key` `` on the Twilio route. The same value is the HMAC secret for detection webhooks. The key can do nothing but open detection sessions. Sessions are billed to your balance, and a key stops working while the balance is empty. Requires Building Blocks: an account without it gets `403` with `byoc-required`. Only a verified account can create keys (`403` with `team-not-verified`), and trial accounts get `403` with `trial_feature_blocked`. An account holds at most 25 keys (`409`). To rotate: create a new key, deploy it, then delete the old one. A deleted key can still open sessions for up to 60 seconds. tags: [Detection] security: - apiKey: [] apiSecret: [] - oauth2: [account:write] requestBody: required: false content: application/json: schema: type: object properties: name: type: string maxLength: 100 description: Label shown in the dashboard. Defaults to "Detection key" and today's date. expires_in_seconds: type: integer minimum: 300 maximum: 31536000 description: > Lifetime of the key in seconds, from 300 (5 minutes) to 31536000 (365 days). Omit it, or send `null`, for a key that never expires. Anything else, including a string or a fraction, gets `400`. examples: server: $ref: './examples/detection-keys.create.request.json' responses: '201': description: Key created. `api_key` is shown once. content: application/json: schema: type: object properties: data: $ref: '#/components/schemas/DetectionKeyCreated' meta: $ref: '#/components/schemas/Meta' examples: created: $ref: './examples/detection-keys.create.response.json' '400': description: > `type` ends in `validation-error`: `name` is longer than 100 characters or `expires_in_seconds` is not an integer from 300 to 31536000. content: application/json: schema: $ref: '#/components/schemas/Error' example: type: https://api-v2.dropcowboy.com/errors/validation-error title: Validation Error status: 400 detail: expires_in_seconds must be an integer from 300 to 31536000 instance: /register/public/detection-keys request_id: 5e8b2d4f-7a1c-4e9b-8d3f-2c6a9e1b4d70 '401': $ref: '#/components/responses/Unauthorized' '403': description: > Scope missing (`type` ends in `insufficient-scope`), not available on a trial (`trial_feature_blocked`), the account is not verified yet (`team-not-verified`), or Building Blocks is not enabled (`byoc-required`). content: application/json: schema: $ref: '#/components/schemas/Error' examples: teamNotVerified: summary: The account has not finished verification value: type: https://api-v2.dropcowboy.com/errors/team-not-verified title: Team Not Verified status: 403 detail: Verify your account before creating Detection keys. instance: /register/public/detection-keys request_id: 5e8b2d4f-7a1c-4e9b-8d3f-2c6a9e1b4d70 details: code: team-not-verified byocRequired: summary: Building Blocks is not enabled value: type: https://api-v2.dropcowboy.com/errors/byoc-required title: Byoc Required status: 403 detail: Detection keys need Building Blocks (BYOC) and your own pool. Enable Building Blocks first. instance: /register/public/detection-keys request_id: 5e8b2d4f-7a1c-4e9b-8d3f-2c6a9e1b4d70 details: code: byoc-required '409': description: The account already holds the maximum of 25 Detection keys. Delete one first. content: application/json: schema: $ref: '#/components/schemas/Error' '429': $ref: '#/components/responses/TooManyRequests' '503': $ref: '#/components/responses/DetectionKeyServiceUnavailable' /register/public/detection-keys/{key_id}: parameters: - name: key_id in: path required: true description: The key's `key_id` from `GET /register/public/detection-keys`. Not the key itself. schema: type: string format: uuid example: 3e4f5a6b-7c8d-4e9f-a0b1-c2d3e4f5a6b7 delete: operationId: deleteDetectionKey summary: Delete a Detection key description: > Revokes the key. New sessions are refused within 60 seconds; sessions already open run to completion. Deletion is permanent. tags: [Detection] security: - apiKey: [] apiSecret: [] - oauth2: [account:write] responses: '200': description: Detection key deleted content: application/json: schema: type: object properties: data: type: object properties: key_id: type: string format: uuid revoked: type: boolean meta: $ref: '#/components/schemas/Meta' '400': $ref: '#/components/responses/BadRequest' '401': $ref: '#/components/responses/Unauthorized' '403': $ref: '#/components/responses/InsufficientScope' '404': $ref: '#/components/responses/NotFound' '429': $ref: '#/components/responses/TooManyRequests' '503': $ref: '#/components/responses/DetectionKeyServiceUnavailable' /register/public/events: get: operationId: listEvents summary: List event types description: | Lists the event type names you can subscribe to. Use one as `hook_type` when you subscribe a webhook, or in `event_types` for an integration webhook. tags: [Webhooks] security: - apiKey: [] apiSecret: [] - oauth2: [webhooks:read] responses: '200': description: Event types content: application/json: schema: type: object properties: data: type: array items: $ref: '#/components/schemas/EventType' meta: $ref: '#/components/schemas/Meta' '401': $ref: '#/components/responses/Unauthorized' '429': $ref: '#/components/responses/TooManyRequests' post: operationId: createIntegrationEvent summary: 'Add an event to a contact''s timeline' description: > Adds an event from your own system to a contact's timeline, sends it to your webhook subscriptions and starts any automations that trigger on it. Identify the contact with `contact_id`, or with `contact_lookup` (email is tried first, then phone). Send `client_event_id` to make retries safe. tags: [Contact details] security: - apiKey: [] apiSecret: [] - oauth2: [webhooks:write] requestBody: required: true content: application/json: schema: $ref: '#/components/schemas/CreateIntegrationEventRequest' responses: '201': description: Event created content: application/json: schema: $ref: '#/components/schemas/IntegrationEvent' '200': description: Duplicate event (idempotent, client_event_id already exists) content: application/json: schema: $ref: '#/components/schemas/IntegrationEvent' '400': $ref: '#/components/responses/BadRequest' '401': $ref: '#/components/responses/Unauthorized' '404': description: Contact not found '429': $ref: '#/components/responses/TooManyRequests' /voice/public/voices: get: operationId: listVoices summary: List voices description: | Your account's cloned and designed voices, plus the platform catalog unless `include_pro_voices=false`. Use `voice_id` with `tts_body` on sends, with `tts_on_*` on voice broadcasts, and with `POST /voice/public/tts/synthesize`. Only a voice whose `status` is `ready` can be synthesized or sent; see `Voice` for the other states. `data.total` counts your account's voices only, not the platform catalog. tags: [Voices] security: - apiKey: [] apiSecret: [] - oauth2: [media:read] parameters: - name: include_urls in: query schema: type: boolean description: Include a sample `url` for each voice. - name: include_pro_voices in: query schema: type: boolean default: true - $ref: '#/components/parameters/SkipParam' - name: limit in: query schema: type: integer default: 100 responses: '200': description: Voice list content: application/json: schema: type: object properties: data: $ref: '#/components/schemas/VoicesPage' meta: $ref: '#/components/schemas/Meta' examples: voices: $ref: './examples/voices.list.response.json' '401': $ref: '#/components/responses/Unauthorized' '403': $ref: '#/components/responses/InsufficientScope' '429': $ref: '#/components/responses/TooManyRequests' /voice/public/tts/synthesize: post: operationId: synthesizeSpeech summary: Turn text into audio description: | Synthesizes `text` with one of your account's voices and returns a short-lived download URL. Billed per character (`tts_characters`). Any voice `GET /voice/public/voices` lists as `ready` works, including platform catalog voices. Voices that are still awaiting checkout return `402`; voices that are still processing or failed return `409` (`.../errors/voice-not-ready`); voices that are unknown, deleted or another account's return `404`. tags: [Voices] security: - apiKey: [] apiSecret: [] - oauth2: [voice:send] requestBody: required: true content: application/json: schema: $ref: '#/components/schemas/SynthesizeRequest' examples: greeting: $ref: './examples/tts.synthesize.request.json' responses: '200': description: Audio ready content: application/json: schema: type: object properties: data: $ref: '#/components/schemas/SynthesizeResult' meta: $ref: '#/components/schemas/Meta' examples: audio: $ref: './examples/tts.synthesize.response.json' '400': $ref: '#/components/responses/BadRequest' '401': $ref: '#/components/responses/Unauthorized' '402': $ref: '#/components/responses/PaymentRequired' '403': $ref: '#/components/responses/InsufficientScope' '404': $ref: '#/components/responses/NotFound' '409': description: The voice is still processing or failed to clone. content: application/json: schema: $ref: '#/components/schemas/Error' example: type: https://api-v2.dropcowboy.com/errors/voice-not-ready title: Voice Not Ready status: 409 detail: Voice is still processing. Retry when its status is ready. '429': $ref: '#/components/responses/TooManyRequests' /voice/public/voices/clone: post: operationId: cloneVoice summary: Clone a voice description: | Starts cloning from a recording, given as `sample_url` (a public audio URL) or `media_id` (a file already in your media library). Answers `202` with the new `voice_id`. Poll `GET /voice/public/voices` until that voice's `status` is `ready`. `pending_payment: true` means the voice slot must be purchased in the dashboard before the voice can be used. Get consent from the person whose voice you clone. tags: [Voices] security: - apiKey: [] apiSecret: [] - oauth2: [media:write] requestBody: required: true content: application/json: schema: $ref: '#/components/schemas/CloneVoiceRequest' examples: fromUrl: $ref: './examples/voice-clone.request.json' responses: '202': description: Cloning started content: application/json: schema: type: object properties: data: $ref: '#/components/schemas/CloneVoiceResult' meta: $ref: '#/components/schemas/Meta' examples: started: $ref: './examples/voice-clone.response.json' '400': $ref: '#/components/responses/BadRequest' '401': $ref: '#/components/responses/Unauthorized' '402': $ref: '#/components/responses/PaymentRequired' '403': $ref: '#/components/responses/InsufficientScope' '404': $ref: '#/components/responses/NotFound' '429': $ref: '#/components/responses/TooManyRequests' /voice/public/voices/{voice_id}: parameters: - name: voice_id in: path required: true description: A `voice_id` from `GET /voice/public/voices` or `POST /voice/public/voices/clone`. schema: type: string format: uuid delete: operationId: deleteVoice summary: Delete a voice description: | Deletes one of your account's cloned or designed voices. The voice disappears from `GET /voice/public/voices` and can't be used for synthesis or sends any more. If the voice counted toward your monthly saved-voice subscription, the count drops by one; an unpaid voice (`pending_payment`) is removed from your checkout cart instead. Platform catalog voices, other accounts' voices, already deleted voices and unknown ids return `404`. Wait until a voice's `status` is `ready` or `failed` before you delete it. tags: [Voices] security: - apiKey: [] apiSecret: [] - oauth2: [media:write] responses: '200': description: Voice deleted content: application/json: schema: type: object properties: data: type: object required: [voice_id, deleted] properties: voice_id: type: string format: uuid deleted: type: boolean enum: [true] meta: $ref: '#/components/schemas/Meta' example: data: voice_id: a9c3e8f1-4b2d-4a7c-8e9b-1c2d3e4f5a6b deleted: true meta: request_id: 3f6d2b9e-8a1c-4e7f-b5d4-2c9a8e7f6b1d '400': description: '`voice_id` is not a UUID, or the credentials carry no user to attribute the deletion to.' content: application/json: schema: $ref: '#/components/schemas/Error' example: type: https://api-v2.dropcowboy.com/errors/bad-request title: Bad Request status: 400 detail: voice_id must be a UUID '401': $ref: '#/components/responses/Unauthorized' '403': $ref: '#/components/responses/InsufficientScope' '404': $ref: '#/components/responses/NotFound' '429': $ref: '#/components/responses/TooManyRequests' /voice/public/voices/design: post: operationId: designVoice summary: Design a voice description: | Starts generating a new voice from a written description, read aloud with `text`. Previews are free. Answers `202` with a `long_job_id`. Review the preview and save it as a voice in the dashboard. Once saved, the voice appears in `GET /voice/public/voices` with `type: designed`. tags: [Voices] security: - apiKey: [] apiSecret: [] - oauth2: [media:write] requestBody: required: true content: application/json: schema: $ref: '#/components/schemas/DesignVoiceRequest' examples: warm: $ref: './examples/voice-design.request.json' responses: '202': description: Design started content: application/json: schema: type: object properties: data: type: object properties: long_job_id: type: string format: uuid meta: $ref: '#/components/schemas/Meta' '400': $ref: '#/components/responses/BadRequest' '401': $ref: '#/components/responses/Unauthorized' '403': $ref: '#/components/responses/InsufficientScope' '429': $ref: '#/components/responses/TooManyRequests' /voice/public/asr/transcribe: post: operationId: transcribeAudio summary: Transcribe audio description: | Transcribes a recording given as `media_id` or `url`. Runs synchronously, so keep recordings short (a few minutes). Returns `402` when the account has no funds for speech to text. - `media_id`: any MP3 or WAV in your media library. It is transcribed from the normalized copy (8 kHz mono WAV) made a few seconds after the upload completes. Until that copy exists an uploaded WAV is transcribed as is, and an uploaded MP3 answers `409`; retry after a few seconds. The upload's format is detected, so `ext` is optional. A `media_id` with no stored audio answers `404`. - `url`: must be a WAV file with PCM 16-bit samples. Any other format answers `400`; upload MP3 and other formats as media and pass `media_id` instead. Audio larger than 10 MB answers `400`. tags: [Voices] security: - apiKey: [] apiSecret: [] - oauth2: [media:read] requestBody: required: true content: application/json: schema: $ref: '#/components/schemas/TranscribeRequest' examples: media: $ref: './examples/asr.transcribe.request.json' responses: '200': description: Transcript content: application/json: schema: type: object properties: data: $ref: '#/components/schemas/TranscribeResult' meta: $ref: '#/components/schemas/Meta' examples: transcript: $ref: './examples/asr.transcribe.response.json' '400': description: | Neither `url` nor `media_id` was given, the `url` audio is not WAV (PCM 16-bit), the audio is larger than 10 MB, or it could not be fetched or transcribed. content: application/json: schema: $ref: '#/components/schemas/Error' example: type: https://api-v2.dropcowboy.com/errors/bad-request title: Bad Request status: 400 detail: Speech to text accepts WAV audio (PCM 16-bit) from a url. For MP3 or other formats, upload the file as media and pass media_id. '401': $ref: '#/components/responses/Unauthorized' '402': $ref: '#/components/responses/PaymentRequired' '403': $ref: '#/components/responses/InsufficientScope' '404': $ref: '#/components/responses/NotFound' '409': description: | The `media_id` file is an MP3 whose normalized WAV copy is still being made. Retry after a few seconds. content: application/json: schema: $ref: '#/components/schemas/Error' example: type: https://api-v2.dropcowboy.com/errors/conflict title: Conflict status: 409 detail: Media is still processing. Retry in a few seconds. '429': $ref: '#/components/responses/TooManyRequests' '502': description: Speech to text is temporarily unavailable. Safe to retry. content: application/json: schema: $ref: '#/components/schemas/Error' example: type: https://api-v2.dropcowboy.com/errors/server-error title: Bad Gateway status: 502 detail: Speech to text is temporarily unavailable /email/public/email: get: operationId: listEmails summary: List emails description: Sent and received emails, newest first. Filter by `contact_id` to read one contact's thread. tags: [Email] security: - apiKey: [] apiSecret: [] - oauth2: [contacts:read] parameters: - name: contact_id in: query schema: type: string format: uuid - name: direction in: query schema: type: string enum: [inbound, outbound] - name: state in: query schema: type: string - $ref: '#/components/parameters/SkipParam' - name: limit in: query schema: type: integer default: 50 - name: include_total in: query schema: type: boolean responses: '200': description: Emails content: application/json: schema: type: object properties: data: type: object properties: emails: type: array items: $ref: '#/components/schemas/EmailRecord' total: type: integer meta: $ref: '#/components/schemas/Meta' '401': $ref: '#/components/responses/Unauthorized' '403': $ref: '#/components/responses/InsufficientScope' '429': $ref: '#/components/responses/TooManyRequests' post: operationId: sendEmail x-webhooks: [contact.email.sent, contact.email.status] summary: Send an email description: | Sends one email from a mailbox on your verified sending domain. Give the recipient as `to` or `contact_id`; a `to` address with no contact creates one. Get `mailbox_id` from `GET /domain/public/mailboxes`. The response is `200` whenever the request was well formed. Check `data.success`: `false` with an `error` means nothing was sent (for example no sending domain). Delivery, opens, clicks and bounces arrive on the `contact.email.status` webhook. Email has its own per-contact frequency limit, separate from the phone contact frequency limit: by default 15 bulk emails per 3 days and 30 transactional emails per 24 hours, configurable per account. Addresses on your email test list are exempt. With an `Idempotency-Key`, a successful send is stored for 24 hours and a retry with the same key and body gets that stored response back with `Idempotent-Replayed: true`, without sending again. Only successes are stored: a `200` with `success: false`, or an error status, releases the key, so a retry with the same key sends again. An empty or whitespace-only header counts as no header. A first request that stopped more than 15 minutes ago without finishing releases the key: if it stopped before sending, the next retry sends; if it had already sent the email, the retry gets `{"success": true}` with `Idempotent-Replayed: true`. The template send routes do not support `Idempotency-Key`. tags: [Email] security: - apiKey: [] apiSecret: [] - oauth2: [email:send] parameters: - name: Idempotency-Key in: header required: false description: | 1 to 255 printable ASCII characters; a random UUID is the easy choice. Scoped to your account for 24 hours from first use. Unlike the four send routes, an empty value is rejected with `400`. schema: type: string minLength: 1 maxLength: 255 example: 7c9e6679-7425-40de-944b-e07fc1f90ae7 requestBody: required: true content: application/json: schema: $ref: '#/components/schemas/SendEmail' examples: transactional: $ref: './examples/send-email.request.json' responses: '200': description: Processed. Check `success`. headers: Idempotent-Replayed: description: '`true` when this is the stored response to an earlier request with the same `Idempotency-Key`. Absent otherwise.' schema: type: string enum: ['true'] content: application/json: schema: type: object properties: data: $ref: '#/components/schemas/EmailSendResult' meta: $ref: '#/components/schemas/Meta' examples: sent: $ref: './examples/send-email.response.json' '400': description: Invalid request, or an invalid `Idempotency-Key` (`invalid-idempotency-key`). content: application/json: schema: $ref: '#/components/schemas/Error' example: type: https://api-v2.dropcowboy.com/errors/invalid-idempotency-key title: Invalid Idempotency Key status: 400 detail: Idempotency-Key must be 1 to 255 printable ASCII characters. instance: /email/public/email request_id: 08aba0ac-3266-48d7-8367-644694cc3465 '401': $ref: '#/components/responses/Unauthorized' '402': $ref: '#/components/responses/PaymentRequired' '403': description: Scope missing, or the account's plan does not include email. content: application/json: schema: oneOf: - $ref: '#/components/schemas/Error' - $ref: '#/components/schemas/PlanGateError' '404': $ref: '#/components/responses/NotFound' '409': description: | `idempotency-key-reused`: the key was already used with a different body (or on a different route); nothing was sent. `idempotency-request-in-progress`: the first request with this key has not finished; retry shortly with the same key. After 15 minutes an unfinished first request releases the key and a retry goes through. content: application/json: schema: $ref: '#/components/schemas/Error' examples: reused: summary: Same key, different body value: type: https://api-v2.dropcowboy.com/errors/idempotency-key-reused title: Idempotency Key Reused status: 409 detail: This Idempotency-Key was already used with a different request body. instance: /email/public/email request_id: d6c279b8-311d-4f38-9b2c-30ddb68e15e7 inProgress: summary: First request still running value: type: https://api-v2.dropcowboy.com/errors/idempotency-request-in-progress title: Idempotency Request In Progress status: 409 detail: A request with this Idempotency-Key is still being processed. Retry later. instance: /email/public/email request_id: 4b50e620-5173-4cb2-96f8-5a0f39a4f1ba '429': $ref: '#/components/responses/TooManyRequests' /email/public/email/template: post: operationId: sendEmailFromTemplate x-webhooks: [contact.email.sent, contact.email.status] summary: Send an email and record its template description: | Same as `POST /email/public/email`, with `template_id` recorded against the send. You supply the final `subject` and `html`; nothing is merged. To merge a saved template with a contact's fields in one call, use `POST /email/public/email/merged-template`. tags: [Email] security: - apiKey: [] apiSecret: [] - oauth2: [email:send] requestBody: required: true content: application/json: schema: allOf: - $ref: '#/components/schemas/SendEmail' - type: object required: [template_id] responses: '200': description: Processed. Check `success`. content: application/json: schema: type: object properties: data: $ref: '#/components/schemas/EmailSendResult' meta: $ref: '#/components/schemas/Meta' '400': $ref: '#/components/responses/BadRequest' '401': $ref: '#/components/responses/Unauthorized' '402': $ref: '#/components/responses/PaymentRequired' '403': $ref: '#/components/responses/InsufficientScope' '429': $ref: '#/components/responses/TooManyRequests' /email/public/email/merged-template: post: operationId: sendMergedTemplateEmail x-webhooks: [contact.email.sent, contact.email.status] summary: Send an email from a template description: | Loads an email template, fills its merge fields from the recipient contact (and from `merged_user_id`, or the calling user, for sender fields), and sends it. The recipient must resolve to a contact: pass `contact_id`, or a `to` address (a contact is created if none matches). RCS templates are rejected. tags: [Email] security: - apiKey: [] apiSecret: [] - oauth2: [email:send] requestBody: required: true content: application/json: schema: $ref: '#/components/schemas/SendMergedTemplateEmail' examples: reminder: $ref: './examples/send-email-merged.request.json' responses: '200': description: Processed. Check `success`. content: application/json: schema: type: object properties: data: $ref: '#/components/schemas/EmailSendResult' meta: $ref: '#/components/schemas/Meta' examples: sent: $ref: './examples/send-email.response.json' '400': $ref: '#/components/responses/BadRequest' '401': $ref: '#/components/responses/Unauthorized' '402': $ref: '#/components/responses/PaymentRequired' '403': $ref: '#/components/responses/InsufficientScope' '404': $ref: '#/components/responses/NotFound' '429': $ref: '#/components/responses/TooManyRequests' /domain/public/mailboxes: get: operationId: listMailboxes summary: List mailboxes description: Mailboxes you can send from. Pass `mailbox_id` to the email send routes and to email campaigns (`email_from_mailbox_id`). tags: [Email] security: - apiKey: [] apiSecret: [] - oauth2: [email:read] responses: '200': description: Mailboxes content: application/json: schema: type: object properties: data: type: object properties: mailboxes: type: array items: $ref: '#/components/schemas/Mailbox' meta: $ref: '#/components/schemas/Meta' examples: mailboxes: $ref: './examples/mailboxes.list.response.json' '401': $ref: '#/components/responses/Unauthorized' '403': $ref: '#/components/responses/InsufficientScope' '429': $ref: '#/components/responses/TooManyRequests' /chat/public/sites: get: operationId: listChatSites summary: List chat sites description: Websites with the Drop Cowboy chat widget installed. Use `chat_site_id` wherever a chat site is asked for, for example in chat automations. tags: [Conversations] security: - apiKey: [] apiSecret: [] - oauth2: [chat:read] parameters: - name: search_term in: query schema: type: string - name: limit in: query schema: type: integer responses: '200': description: Chat sites content: application/json: schema: type: object properties: data: type: array items: type: object properties: chat_site_id: type: string format: uuid site_name: type: string meta: $ref: '#/components/schemas/Meta' examples: sites: $ref: './examples/chat-sites.list.response.json' '401': $ref: '#/components/responses/Unauthorized' '403': $ref: '#/components/responses/InsufficientScope' '429': $ref: '#/components/responses/TooManyRequests' /chat/public/reply: post: operationId: sendChatReply summary: Reply to a chat description: | Posts a message into an existing chat conversation. The visitor sees it in the chat widget immediately, and it appears in the shared inbox as an automation reply. `conversation_id` comes from the chat automation triggers (for example "Chat Received"). Each call posts a new message and the route doesn't support `Idempotency-Key`, so don't retry after a success. The team's plan must include chat on at least one seat; otherwise the answer is `403` with `type` ending `addon_not_enabled`. An inactive account also answers `403`, with its own `type`. tags: [Conversations] security: - apiKey: [] apiSecret: [] - oauth2: [chat:write] requestBody: required: true content: application/json: schema: type: object required: [conversation_id] properties: conversation_id: type: string format: uuid message: type: string description: Reply text. Markdown is allowed. Required unless `body` is sent. body: type: string description: Same as `message`; used when both are sent. sender_name: type: string description: Name shown to the visitor. Defaults to `Automation`. metadata: type: object description: Stored with the message and echoed on chat events. example: conversation_id: 2b7d9f1a-4c6e-4e8a-b0d2-6f8a1c3e5b79 message: Thanks for reaching out! A teammate will follow up within the hour. sender_name: Example Dental responses: '200': description: Reply posted content: application/json: schema: type: object properties: data: type: object properties: message_id: type: string format: uuid conversation_id: type: string format: uuid meta: $ref: '#/components/schemas/Meta' example: data: message_id: 6e8a0c2d-4f1b-4a3c-9e5d-7b9f1d3a5c28 conversation_id: 2b7d9f1a-4c6e-4e8a-b0d2-6f8a1c3e5b79 meta: request_id: 0c2e4a6b-8d1f-4b3e-a5c7-9e1b3d5f7a41 '400': description: | `conversation_id` or the message is missing (`validation-error`). content: application/json: schema: $ref: '#/components/schemas/Error' example: type: https://api-v2.dropcowboy.com/errors/validation-error title: Validation Error status: 400 detail: missing parameters instance: /chat/public/reply request_id: 7c9e1a3b-5d2f-4e6a-8b0c-2d4f6a8c0e53 '401': $ref: '#/components/responses/Unauthorized' '402': $ref: '#/components/responses/PaymentRequired' '403': description: | Scope missing (`insufficient-scope`), the plan does not include chat (`addon_not_enabled`), or the account is inactive. content: application/json: schema: $ref: '#/components/schemas/Error' example: type: https://api-v2.dropcowboy.com/errors/addon_not_enabled title: Addon_not_enabled status: 403 detail: This team's plan does not include chat capabilities. Upgrade a seat license to send chat replies over the API. instance: /chat/public/reply request_id: 4a6c8e0f-2b3d-4f5a-8c7e-1d3f5b7a9c02 '404': description: | No conversation with that `conversation_id` in your account (`not-found`). content: application/json: schema: $ref: '#/components/schemas/Error' example: type: https://api-v2.dropcowboy.com/errors/not-found title: Not Found status: 404 detail: conversation not found instance: /chat/public/reply request_id: 3e5a7c9d-1b2f-4d4e-a6c8-0f2b4d6e8a91 '429': $ref: '#/components/responses/TooManyRequests' '500': $ref: '#/components/responses/ServerError' /document/public/documents: get: operationId: listDocuments summary: List documents description: | Lists non-deleted documents. Without `limit` every matching document is returned. This route has no offset parameter. tags: [Documents] security: - apiKey: [] apiSecret: [] - oauth2: [contacts:read] parameters: - name: limit in: query description: Most documents to return. No default. schema: type: integer minimum: 1 - name: contact_id in: query description: Only documents attached to this contact. schema: type: string - name: get_total in: query description: Send any value to add `total_documents` to the response. schema: type: boolean responses: '200': description: Documents content: application/json: schema: type: object properties: data: $ref: '#/components/schemas/DocumentsPage' meta: $ref: '#/components/schemas/Meta' '401': $ref: '#/components/responses/Unauthorized' '429': $ref: '#/components/responses/TooManyRequests' post: operationId: uploadDocument x-webhooks: [contact.document.attached] summary: Upload a document description: | Creates the document and returns a signed upload URL. `PUT` the file bytes to `policy.url` with `Content-Type: application/octet-stream`. The URL is valid for two days. tags: [Documents] security: - apiKey: [] apiSecret: [] - oauth2: [contacts:write] requestBody: required: true content: application/json: schema: $ref: '#/components/schemas/CreateDocument' responses: '200': description: Document created content: application/json: schema: type: object properties: data: $ref: '#/components/schemas/DocumentCreated' meta: $ref: '#/components/schemas/Meta' '400': $ref: '#/components/responses/BadRequest' '401': $ref: '#/components/responses/Unauthorized' '429': $ref: '#/components/responses/TooManyRequests' /document/public/documents/from-url: post: operationId: createDocumentFromUrl x-webhooks: [contact.document.attached] summary: Create a document from a URL description: | Fetches `source_url` and stores the file before responding (`uploaded: true`). tags: [Documents] security: - apiKey: [] apiSecret: [] - oauth2: [contacts:write] requestBody: required: true content: application/json: schema: allOf: - $ref: '#/components/schemas/CreateDocument' - type: object required: [source_url] properties: source_url: type: string format: uri description: Public URL of the file to fetch. responses: '200': description: Document created from URL content: application/json: schema: type: object properties: data: $ref: '#/components/schemas/DocumentCreated' meta: $ref: '#/components/schemas/Meta' '400': $ref: '#/components/responses/BadRequest' '401': $ref: '#/components/responses/Unauthorized' '429': $ref: '#/components/responses/TooManyRequests' /document/public/documents/{id}: parameters: - name: id in: path required: true schema: type: string get: operationId: getDocument summary: Get a document description: | Returns the details of one document, such as its filename, type, size and content type. To download the file itself, use Get a document download URL. tags: [Documents] security: - apiKey: [] apiSecret: [] - oauth2: [contacts:read] responses: '200': description: Document details content: application/json: schema: type: object properties: data: $ref: '#/components/schemas/Document' meta: $ref: '#/components/schemas/Meta' '401': $ref: '#/components/responses/Unauthorized' '404': $ref: '#/components/responses/NotFound' '429': $ref: '#/components/responses/TooManyRequests' delete: operationId: deleteDocument summary: Delete a document description: | Soft delete. Returns the document with `deleted_at` set. An unknown or already deleted `id` returns `404`. tags: [Documents] security: - apiKey: [] apiSecret: [] - oauth2: [contacts:write] responses: '200': description: Deleted document content: application/json: schema: type: object properties: data: $ref: '#/components/schemas/Document' meta: $ref: '#/components/schemas/Meta' '401': $ref: '#/components/responses/Unauthorized' '404': $ref: '#/components/responses/NotFound' '429': $ref: '#/components/responses/TooManyRequests' /document/public/documents/{id}/url: parameters: - name: id in: path required: true schema: type: string get: operationId: getDocumentUrl summary: Get a document download URL description: | Returns a signed URL for downloading one document. The URL is time-limited, so request a new one when you need the file again instead of storing it. tags: [Documents] security: - apiKey: [] apiSecret: [] - oauth2: [contacts:read] responses: '200': description: Signed download URL content: application/json: schema: type: object properties: data: type: object properties: url: type: string format: uri meta: $ref: '#/components/schemas/Meta' '401': $ref: '#/components/responses/Unauthorized' '404': $ref: '#/components/responses/NotFound' '429': $ref: '#/components/responses/TooManyRequests' /task/public/tasks: get: operationId: listTasks summary: List tasks description: | Lists inbox tasks. Without `limit` every matching task is returned. Page with `skip` and `limit`; there is no total, so stop when a page has fewer than `limit` tasks. tags: [Inbox Tasks] security: - apiKey: [] apiSecret: [] - oauth2: [contacts:read] parameters: - name: limit in: query description: Most tasks to return. No default. schema: type: integer minimum: 1 - $ref: '#/components/parameters/SkipParam' - name: contact_id in: query description: Only tasks for this contact. schema: type: string - name: status in: query description: Only tasks with this status. `all` or omitted returns every status. schema: type: string enum: [open, snoozed, closed, all] - name: sort in: query description: '`asc` (oldest first, the default), `desc` (newest first), `urgent` (urgent first, then newest) or `followup_soonest` (soonest reminder first).' schema: type: string enum: [asc, desc, urgent, followup_soonest] default: asc responses: '200': description: Tasks content: application/json: schema: type: object properties: data: $ref: '#/components/schemas/TasksPage' meta: $ref: '#/components/schemas/Meta' '401': $ref: '#/components/responses/Unauthorized' '429': $ref: '#/components/responses/TooManyRequests' post: operationId: createTask x-webhooks: [task.opened] summary: Create a task description: Opens a task in the shared inbox. New tasks start `open` and unread. tags: [Inbox Tasks] security: - apiKey: [] apiSecret: [] - oauth2: [contacts:write] requestBody: required: true content: application/json: schema: $ref: '#/components/schemas/CreateTask' responses: '201': description: Task created content: application/json: schema: type: object properties: data: $ref: '#/components/schemas/TaskCreated' meta: $ref: '#/components/schemas/Meta' '400': $ref: '#/components/responses/BadRequest' '401': $ref: '#/components/responses/Unauthorized' '429': $ref: '#/components/responses/TooManyRequests' /task/public/tasks/{task_id}: parameters: - name: task_id in: path required: true schema: type: string get: operationId: getTask summary: Get a task description: Returns one task, with its most recent timeline entry in `last_record`. tags: [Inbox Tasks] security: - apiKey: [] apiSecret: [] - oauth2: [contacts:read] responses: '200': description: The task content: application/json: schema: type: object properties: data: $ref: '#/components/schemas/Task' meta: $ref: '#/components/schemas/Meta' '401': $ref: '#/components/responses/Unauthorized' '404': $ref: '#/components/responses/NotFound' '429': $ref: '#/components/responses/TooManyRequests' put: operationId: updateTask x-webhooks: [task.assigned] summary: Update a task description: | Changes only the fields you send. Returns the fields you sent with `task_id`, `team_id` and `user_id`, not the full task. Returns `404` when no task on your account has that id. tags: [Inbox Tasks] security: - apiKey: [] apiSecret: [] - oauth2: [contacts:write] requestBody: required: true content: application/json: schema: $ref: '#/components/schemas/UpdateTask' responses: '200': description: Fields applied content: application/json: schema: type: object properties: data: $ref: '#/components/schemas/TaskUpdated' meta: $ref: '#/components/schemas/Meta' '400': $ref: '#/components/responses/BadRequest' '401': $ref: '#/components/responses/Unauthorized' '404': $ref: '#/components/responses/NotFound' '429': $ref: '#/components/responses/TooManyRequests' /task/public/tasks/{task_id}/status: parameters: - name: task_id in: path required: true schema: type: string put: operationId: updateTaskStatus x-webhooks: [task.closed] summary: 'Update a task''s status' description: | Opens, snoozes or closes a task. Closing also marks it read. Returns the fields you sent with `task_id`, `team_id` and `user_id`. Returns `404` when no task on your account has that id. tags: [Inbox Tasks] security: - apiKey: [] apiSecret: [] - oauth2: [contacts:write] requestBody: required: true content: application/json: schema: type: object required: [status] properties: status: type: string enum: [open, snoozed, closed] snoozed_until: type: string format: date-time description: When a snoozed task reopens. Required when `status` is `snoozed`. responses: '200': description: Fields applied content: application/json: schema: type: object properties: data: $ref: '#/components/schemas/TaskUpdated' meta: $ref: '#/components/schemas/Meta' '400': $ref: '#/components/responses/BadRequest' '401': $ref: '#/components/responses/Unauthorized' '404': $ref: '#/components/responses/NotFound' '429': $ref: '#/components/responses/TooManyRequests' /template/public/templates: get: operationId: listTemplates summary: List templates description: | Lists non-deleted templates, newest first. Without `limit` every matching template is returned. Page with `skip` and `limit` until `skip` reaches `totalItems`. tags: [Templates] security: - apiKey: [] apiSecret: [] - oauth2: [contacts:read] parameters: - name: limit in: query description: Most templates to return. No default. schema: type: integer minimum: 1 - $ref: '#/components/parameters/SkipParam' - name: type in: query description: Only templates of this type. schema: type: string enum: [email, sms, script, chat, macro, rcs] - name: status in: query description: Only templates with this status. Older email, macro and script templates have no status and match only when this is omitted. schema: type: string enum: [draft, active] - name: search in: query description: Case-insensitive match against the name and body. schema: type: string responses: '200': description: Paginated templates content: application/json: schema: type: object properties: data: $ref: '#/components/schemas/TemplatesPage' meta: $ref: '#/components/schemas/Meta' '401': $ref: '#/components/responses/Unauthorized' '429': $ref: '#/components/responses/TooManyRequests' /template/public/templates/{template_id}: parameters: - name: template_id in: path required: true schema: type: string get: operationId: getTemplate summary: Get a template description: | An unknown or deleted `template_id` returns `404`. tags: [Templates] security: - apiKey: [] apiSecret: [] - oauth2: [contacts:read] responses: '200': description: Template details content: application/json: schema: type: object properties: data: $ref: '#/components/schemas/Template' meta: $ref: '#/components/schemas/Meta' '401': $ref: '#/components/responses/Unauthorized' '404': $ref: '#/components/responses/NotFound' '429': $ref: '#/components/responses/TooManyRequests' /template/public/templates/{template_id}/merge: parameters: - name: template_id in: path required: true schema: type: string post: operationId: mergeTemplate summary: Merge a template description: | Fills the template's merge fields from a contact and a user. Returns `404` when the template, contact or user is not found. tags: [Templates] security: - apiKey: [] apiSecret: [] - oauth2: [contacts:read] requestBody: required: true content: application/json: schema: $ref: '#/components/schemas/MergeTemplate' responses: '200': description: Merged template content content: application/json: schema: type: object properties: data: $ref: '#/components/schemas/MergedTemplate' meta: $ref: '#/components/schemas/Meta' '400': $ref: '#/components/responses/BadRequest' '401': $ref: '#/components/responses/Unauthorized' '404': $ref: '#/components/responses/NotFound' '429': $ref: '#/components/responses/TooManyRequests' /integration/public/webhooks: get: operationId: listIntegrationWebhooks summary: List integration webhooks description: | Lists every integration webhook on your account. This route isn't paged. Each item is one URL subscribed to a set of event types. tags: [Automation] security: - apiKey: [] apiSecret: [] - oauth2: [webhooks:read] responses: '200': description: Every integration webhook on the account. This route isn't paged. content: application/json: schema: type: object properties: data: type: array items: $ref: '#/components/schemas/IntegrationWebhook' meta: $ref: '#/components/schemas/Meta' '401': $ref: '#/components/responses/Unauthorized' '429': $ref: '#/components/responses/TooManyRequests' post: operationId: createIntegrationWebhook summary: Create an integration webhook description: | Subscribes a URL to a set of event types. Send `webhook_url`, an HTTPS endpoint that answers 2xx within 5 seconds, and `event_types`, or `["*"]` for every event. The response includes the `signing_secret` that verifies the `X-Signature` header on deliveries. It is returned only here, so store it. tags: [Automation] security: - apiKey: [] apiSecret: [] - oauth2: [webhooks:write] requestBody: required: true content: application/json: schema: $ref: '#/components/schemas/CreateIntegrationWebhook' responses: '201': description: Integration webhook created content: application/json: schema: type: object properties: data: $ref: '#/components/schemas/IntegrationWebhookCreated' meta: $ref: '#/components/schemas/Meta' '400': $ref: '#/components/responses/BadRequest' '401': $ref: '#/components/responses/Unauthorized' '429': $ref: '#/components/responses/TooManyRequests' /integration/public/webhooks/{id}: parameters: - name: id in: path required: true schema: type: string get: operationId: getIntegrationWebhook summary: Get an integration webhook description: | Returns one integration webhook by id. The response doesn't repeat the signing secret. It only says when the current one was issued. tags: [Automation] security: - apiKey: [] apiSecret: [] - oauth2: [webhooks:read] responses: '200': description: Webhook details content: application/json: schema: type: object properties: data: $ref: '#/components/schemas/IntegrationWebhook' meta: $ref: '#/components/schemas/Meta' '401': $ref: '#/components/responses/Unauthorized' '404': $ref: '#/components/responses/NotFound' '429': $ref: '#/components/responses/TooManyRequests' delete: operationId: deleteIntegrationWebhook summary: Delete an integration webhook description: | Deletes one integration webhook, which stops deliveries to its URL. Answers `204` with no body. tags: [Automation] security: - apiKey: [] apiSecret: [] - oauth2: [webhooks:write] responses: '204': description: Webhook deleted '401': $ref: '#/components/responses/Unauthorized' '404': $ref: '#/components/responses/NotFound' '429': $ref: '#/components/responses/TooManyRequests' /integration/public/byoc: get: operationId: getByocStatus summary: Get your carrier connection description: > Shows whether your own carrier (BYOC) is connected. Carrier credentials are never returned. tags: [Account] security: - apiKey: [] apiSecret: [] - oauth2: [balance:read] responses: '200': description: BYOC status content: application/json: schema: type: object properties: data: $ref: '#/components/schemas/ByocStatus' meta: $ref: '#/components/schemas/Meta' '401': $ref: '#/components/responses/Unauthorized' '429': $ref: '#/components/responses/TooManyRequests' /integration/public/byoc/connect: post: operationId: connectByoc summary: Connect your carrier description: > Connects your own carrier account (BYOC). Send the carrier as `provider` and its API credentials as `credentials`. tags: [Account] security: - apiKey: [] apiSecret: [] - oauth2: [account:write] requestBody: required: true content: application/json: schema: $ref: '#/components/schemas/ByocConnect' responses: '200': description: Carrier connected content: application/json: schema: type: object properties: data: $ref: '#/components/schemas/ByocConnected' meta: $ref: '#/components/schemas/Meta' '400': $ref: '#/components/responses/BadRequest' '401': $ref: '#/components/responses/Unauthorized' '429': $ref: '#/components/responses/TooManyRequests' /integration/public/byoc/disconnect: post: operationId: disconnectByoc summary: Disconnect your carrier description: | Disconnects the carrier you connected to your account. Send the `provider` to disconnect. tags: [Account] security: - apiKey: [] apiSecret: [] - oauth2: [account:write] requestBody: required: true content: application/json: schema: type: object required: [provider] properties: provider: type: string enum: [twilio, thinq, telnyx, signalwire, plivo, bandwidth, vonage, sinch, flowroute, custom] responses: '200': description: Carrier disconnected content: application/json: schema: type: object properties: data: $ref: '#/components/schemas/ByocDisconnected' meta: $ref: '#/components/schemas/Meta' '400': $ref: '#/components/responses/BadRequest' '401': $ref: '#/components/responses/Unauthorized' '429': $ref: '#/components/responses/TooManyRequests' /agents/public/templates: get: operationId: listAgentTemplates summary: List agent templates description: Starting points for `POST /agents/public/agents/from-template`. tags: [AI agents] security: - apiKey: [] apiSecret: [] - oauth2: [agents:read] parameters: - name: direction in: query schema: type: string enum: [inbound, outbound] - name: category in: query schema: type: string responses: '200': description: Agent templates content: application/json: schema: type: object properties: data: $ref: '#/components/schemas/AgentTemplatesPage' meta: $ref: '#/components/schemas/Meta' '401': $ref: '#/components/responses/Unauthorized' '403': $ref: '#/components/responses/AgentForbidden' '429': $ref: '#/components/responses/TooManyRequests' /agents/public/agents: get: operationId: listAgents summary: List agents description: | Lists the agents on your account. Filter by name with `search_term` and limit the page with `limit`. The response includes `total`. tags: [AI agents] security: - apiKey: [] apiSecret: [] - oauth2: [agents:read] parameters: - name: search_term in: query schema: type: string - name: limit in: query schema: type: integer default: 50 responses: '200': description: Agents content: application/json: schema: type: object properties: data: type: object properties: agents: type: array items: $ref: '#/components/schemas/Agent' total: type: integer meta: $ref: '#/components/schemas/Meta' examples: agents: $ref: './examples/agents.list.response.json' '401': $ref: '#/components/responses/Unauthorized' '403': $ref: '#/components/responses/AgentForbidden' '429': $ref: '#/components/responses/TooManyRequests' post: operationId: createAgent summary: Create an agent description: | Creates an agent from scratch. Most integrations start from a template instead (`POST /agents/public/agents/from-template`). Changes take effect on calls only after `POST /agents/public/agents/{agent_id}/publish`. tags: [AI agents] security: - apiKey: [] apiSecret: [] - oauth2: [agents:write] requestBody: required: true content: application/json: schema: $ref: '#/components/schemas/CreateAgent' examples: receptionist: $ref: './examples/agent-create.request.json' responses: '201': description: Agent created content: application/json: schema: type: object properties: data: $ref: '#/components/schemas/Agent' meta: $ref: '#/components/schemas/Meta' examples: created: $ref: './examples/agent.response.json' '400': $ref: '#/components/responses/BadRequest' '401': $ref: '#/components/responses/Unauthorized' '403': $ref: '#/components/responses/AgentForbidden' '429': $ref: '#/components/responses/TooManyRequests' /agents/public/agents/from-template: post: operationId: createAgentFromTemplate summary: Create an agent from a template description: Copies a template's prompt, tools and defaults into a new agent you can then edit and publish. tags: [AI agents] security: - apiKey: [] apiSecret: [] - oauth2: [agents:write] requestBody: required: true content: application/json: schema: $ref: '#/components/schemas/CreateAgentFromTemplate' examples: frontDesk: $ref: './examples/agent-from-template.request.json' responses: '201': description: Agent created content: application/json: schema: type: object properties: data: $ref: '#/components/schemas/Agent' meta: $ref: '#/components/schemas/Meta' '400': $ref: '#/components/responses/BadRequest' '401': $ref: '#/components/responses/Unauthorized' '403': $ref: '#/components/responses/AgentForbidden' '404': $ref: '#/components/responses/NotFound' '429': $ref: '#/components/responses/TooManyRequests' /agents/public/agents/{agent_id}: parameters: - name: agent_id in: path required: true description: '`agent_id` from `GET /agents/public/agents`.' schema: type: string format: uuid get: operationId: getAgent summary: Get an agent description: | Agents that Drop Cowboy manages for your account (they are not in `GET /agents/public/agents`) answer `404`, as an unknown id does. tags: [AI agents] security: - apiKey: [] apiSecret: [] - oauth2: [agents:read] responses: '200': description: Agent content: application/json: schema: type: object properties: data: $ref: '#/components/schemas/Agent' meta: $ref: '#/components/schemas/Meta' '401': $ref: '#/components/responses/Unauthorized' '403': $ref: '#/components/responses/AgentForbidden' '404': $ref: '#/components/responses/NotFound' '429': $ref: '#/components/responses/TooManyRequests' post: operationId: updateAgent summary: Update an agent description: | Partial update of the working copy (this route uses POST, not PUT). Live calls keep using the last published version until you publish again. Setting `active: false` stops the agent taking new calls immediately. Agents that Drop Cowboy manages for your account are not listed and answer `404` here, as on get and delete. tags: [AI agents] security: - apiKey: [] apiSecret: [] - oauth2: [agents:write] requestBody: required: true content: application/json: schema: $ref: '#/components/schemas/UpdateAgent' responses: '200': description: Agent updated content: application/json: schema: type: object properties: data: $ref: '#/components/schemas/Agent' meta: $ref: '#/components/schemas/Meta' '400': $ref: '#/components/responses/BadRequest' '401': $ref: '#/components/responses/Unauthorized' '403': $ref: '#/components/responses/AgentForbidden' '404': $ref: '#/components/responses/NotFound' '429': $ref: '#/components/responses/TooManyRequests' delete: operationId: deleteAgent summary: Delete an agent description: | Queued AI broadcast calls for this agent are cancelled. Agents that Drop Cowboy manages for your account answer `404` and are not deleted. tags: [AI agents] security: - apiKey: [] apiSecret: [] - oauth2: [agents:write] responses: '200': description: Agent deleted content: application/json: schema: type: object properties: data: type: object meta: $ref: '#/components/schemas/Meta' '401': $ref: '#/components/responses/Unauthorized' '403': $ref: '#/components/responses/AgentForbidden' '404': $ref: '#/components/responses/NotFound' '429': $ref: '#/components/responses/TooManyRequests' /agents/public/agents/{agent_id}/publish: parameters: - name: agent_id in: path required: true schema: type: string format: uuid post: operationId: publishAgent summary: Publish an agent description: | Freezes the working copy as a new published version that calls, receptionist lines and AI broadcasts use. Publishing fails with `400` when a tool definition is invalid or the agent references something (a tool connection or automation flow) your account no longer has, or when a selected knowledge base reads like instructions for staff rather than answers for callers. The response carries the new `published_version`. A rejected publish names its reason in `details.code`: `invalid_agent_tools`, `invalid_handoff_targets`, `unusable_knowledge_base`, `unresolved_agent_references`, `unresolved_voice_flow` or `voice_compile_error` (all `400`), or `stale_published_version` (`409`, another publish finished first; read the agent and retry). Branch on `details.code`, not on `detail`: an explanation of 200 characters or more is replaced by a generic message. Built-in system agents cannot be published: they answer `404` (`agent not found`), the same as an unknown `agent_id`. tags: [AI agents] security: - apiKey: [] apiSecret: [] - oauth2: [agents:write] responses: '200': description: Agent published content: application/json: schema: type: object properties: data: $ref: '#/components/schemas/Agent' meta: $ref: '#/components/schemas/Meta' '400': description: The agent failed a publish check. The reason is in `details.code`. content: application/json: schema: $ref: '#/components/schemas/Error' example: type: https://api-v2.dropcowboy.com/errors/validation-error title: Validation Error status: 400 detail: 'Agent tools failed validation: book_visit needs a url' instance: /agents/public/agents/4c8e2a6f-3b9d-4e1a-9c7b-5d2f8a3e6b19/publish request_id: 3a7c1e9f-5b2d-4f8a-9e6c-1d4b7a2f8c05 details: code: invalid_agent_tools '401': $ref: '#/components/responses/Unauthorized' '403': $ref: '#/components/responses/AgentForbidden' '404': $ref: '#/components/responses/NotFound' '409': description: Another publish of this agent finished first. Read the agent and retry. content: application/json: schema: $ref: '#/components/schemas/Error' example: type: https://api-v2.dropcowboy.com/errors/conflict title: Conflict status: 409 detail: agent was published concurrently by another request; reload the draft and retry instance: /agents/public/agents/4c8e2a6f-3b9d-4e1a-9c7b-5d2f8a3e6b19/publish request_id: 9e2b5d8a-1c4f-4a7e-b3d6-8f0a2c5e7b14 details: code: stale_published_version '429': $ref: '#/components/responses/TooManyRequests' /document/public/knowledge-bases: get: operationId: listKnowledgeBases summary: List knowledge bases description: | Lists the knowledge bases on your account. Filter with `search`, `ai_audience` and `status`, sort with `sort_by` and `sort_order`, and page with `limit` and `offset`. `meta` carries the total. tags: [Knowledge bases] security: - apiKey: [] apiSecret: [] - oauth2: [agents:read] parameters: - name: search in: query description: Matches the name. schema: type: string - name: ai_audience in: query schema: type: string enum: [internal, public] - name: status in: query schema: type: string enum: [active, archived] - name: sort_by in: query schema: type: string enum: [name, created_at, updated_at] - name: sort_order in: query schema: type: string enum: [asc, desc] default: desc - $ref: '#/components/parameters/KnowledgeLimit' - $ref: '#/components/parameters/KnowledgeOffset' responses: '200': description: Knowledge bases content: application/json: schema: type: object properties: data: type: array items: $ref: '#/components/schemas/KnowledgeBase' meta: $ref: '#/components/schemas/PaginatedMeta' examples: knowledgeBases: $ref: './examples/knowledge-bases.list.response.json' '400': $ref: '#/components/responses/BadRequest' '401': $ref: '#/components/responses/Unauthorized' '403': $ref: '#/components/responses/KnowledgeForbidden' '429': $ref: '#/components/responses/TooManyRequests' post: operationId: createKnowledgeBase summary: Create a knowledge base description: | Creates an empty knowledge base. Add documents with `POST /document/public/knowledge-bases/{knowledge_base_id}/documents`, then attach it to an agent through `think.knowledge`. tags: [Knowledge bases] security: - apiKey: [] apiSecret: [] - oauth2: [agents:write] requestBody: required: true content: application/json: schema: $ref: '#/components/schemas/CreateKnowledgeBase' examples: faq: $ref: './examples/knowledge-base.create.request.json' responses: '201': description: Knowledge base created content: application/json: schema: type: object properties: data: $ref: '#/components/schemas/KnowledgeBase' meta: $ref: '#/components/schemas/Meta' examples: knowledgeBase: $ref: './examples/knowledge-base.response.json' '400': $ref: '#/components/responses/BadRequest' '401': $ref: '#/components/responses/Unauthorized' '403': $ref: '#/components/responses/KnowledgeForbidden' '429': $ref: '#/components/responses/TooManyRequests' /document/public/knowledge-bases/{knowledge_base_id}: parameters: - $ref: '#/components/parameters/KnowledgeBaseId' get: operationId: getKnowledgeBase summary: Get a knowledge base description: | Returns one knowledge base: its name, description, audience, status and statistics. `ai_audience` is `public` when content may be read to customers and `internal` when it is for agents that help your staff. tags: [Knowledge bases] security: - apiKey: [] apiSecret: [] - oauth2: [agents:read] responses: '200': description: Knowledge base content: application/json: schema: type: object properties: data: $ref: '#/components/schemas/KnowledgeBase' meta: $ref: '#/components/schemas/Meta' '401': $ref: '#/components/responses/Unauthorized' '403': $ref: '#/components/responses/KnowledgeForbidden' '404': $ref: '#/components/responses/NotFound' '429': $ref: '#/components/responses/TooManyRequests' post: operationId: updateKnowledgeBase summary: Update a knowledge base description: Send only the fields you want to change (this route uses POST, not PUT). tags: [Knowledge bases] security: - apiKey: [] apiSecret: [] - oauth2: [agents:write] requestBody: required: true content: application/json: schema: $ref: '#/components/schemas/UpdateKnowledgeBase' responses: '200': description: Updated knowledge base content: application/json: schema: type: object properties: data: $ref: '#/components/schemas/KnowledgeBase' meta: $ref: '#/components/schemas/Meta' '400': $ref: '#/components/responses/BadRequest' '401': $ref: '#/components/responses/Unauthorized' '403': $ref: '#/components/responses/KnowledgeForbidden' '404': $ref: '#/components/responses/NotFound' '429': $ref: '#/components/responses/TooManyRequests' delete: operationId: deleteKnowledgeBase summary: Delete a knowledge base description: Deletes the knowledge base. Remove its id from the `think.knowledge` of any agent that uses it. tags: [Knowledge bases] security: - apiKey: [] apiSecret: [] - oauth2: [agents:write] responses: '200': description: Deleted content: application/json: schema: type: object properties: data: type: object properties: knowledge_base_id: type: string format: uuid deleted: type: boolean meta: $ref: '#/components/schemas/Meta' '401': $ref: '#/components/responses/Unauthorized' '403': $ref: '#/components/responses/KnowledgeForbidden' '404': $ref: '#/components/responses/NotFound' '429': $ref: '#/components/responses/TooManyRequests' /document/public/knowledge-bases/{knowledge_base_id}/query: parameters: - $ref: '#/components/parameters/KnowledgeBaseId' post: operationId: queryKnowledgeBase summary: Search a knowledge base description: | Returns the passages an agent would retrieve for `query`. Use it to check that a knowledge base answers the questions callers will ask before you attach it. Only documents whose `ingest_status` is `ready` are searched. tags: [Knowledge bases] security: - apiKey: [] apiSecret: [] - oauth2: [agents:read] requestBody: required: true content: application/json: schema: type: object required: [query] properties: query: type: string maxLength: 1000 example: What are your hours on Saturday? responses: '200': description: Matching passages, best first content: application/json: schema: type: object properties: data: $ref: '#/components/schemas/KnowledgeQueryResponse' meta: $ref: '#/components/schemas/Meta' examples: hours: $ref: './examples/knowledge-query.response.json' '400': $ref: '#/components/responses/BadRequest' '401': $ref: '#/components/responses/Unauthorized' '403': $ref: '#/components/responses/KnowledgeForbidden' '404': $ref: '#/components/responses/NotFound' '429': $ref: '#/components/responses/TooManyRequests' /document/public/knowledge-bases/{knowledge_base_id}/documents: parameters: - $ref: '#/components/parameters/KnowledgeBaseId' get: operationId: listKnowledgeDocuments summary: 'List a knowledge base''s documents' description: | Lists the documents in one knowledge base. Filter with `search` and page with `limit` and `offset`. tags: [Knowledge bases] security: - apiKey: [] apiSecret: [] - oauth2: [agents:read] parameters: - name: search in: query description: Matches the title. schema: type: string - $ref: '#/components/parameters/KnowledgeLimit' - $ref: '#/components/parameters/KnowledgeOffset' responses: '200': description: Documents content: application/json: schema: type: object properties: data: type: array items: $ref: '#/components/schemas/KnowledgeDocument' meta: $ref: '#/components/schemas/PaginatedMeta' '400': $ref: '#/components/responses/BadRequest' '401': $ref: '#/components/responses/Unauthorized' '403': $ref: '#/components/responses/KnowledgeForbidden' '404': $ref: '#/components/responses/NotFound' '429': $ref: '#/components/responses/TooManyRequests' post: operationId: addKnowledgeDocument summary: Add a document to a knowledge base description: | Three ways to add a document. The fields you send pick the method: - `markdown` + `title`: inline text, up to 1 MB. Answers `201` with the document. - `source_url`: Drop Cowboy fetches the file (up to 25 MB, public http(s) URLs only). Answers `201` with the document. - `filename` + `mime_type` + `byte_size`: answers `201` with an upload ticket. PUT the file to `upload.url` with exactly the `Content-Type` in `upload.headers`, then call `POST .../documents/{knowledge_document_id}/uploaded`. Every document is processed in the background. Poll the document list until `ingest_status` is `ready` (or `failed`, with a reason in `ingest_error`). Adding a file identical to one already in the knowledge base answers `409`, with the existing document's id in `details.existing_knowledge_document_id`. tags: [Knowledge bases] security: - apiKey: [] apiSecret: [] - oauth2: [agents:write] requestBody: required: true content: application/json: schema: $ref: '#/components/schemas/AddKnowledgeDocument' examples: markdown: $ref: './examples/knowledge-document.markdown.request.json' upload: $ref: './examples/knowledge-document.upload.request.json' responses: '201': description: Document created, or an upload ticket when you sent `filename` content: application/json: schema: type: object properties: data: anyOf: - $ref: '#/components/schemas/KnowledgeDocument' - $ref: '#/components/schemas/KnowledgeUploadTicket' meta: $ref: '#/components/schemas/Meta' examples: uploadTicket: $ref: './examples/knowledge-document.upload.response.json' '400': description: | Invalid request. With `source_url`, the `type` code says why the URL was refused: `url-required`, `url-invalid`, `url-protocol-blocked` (not http or https), `url-credentials` (user:password in the URL), `url-private-host` (the host is or resolves to a private address), `url-unresolvable`, or `empty-file` (the URL returned nothing). content: application/json: schema: $ref: '#/components/schemas/Error' example: type: https://api-v2.dropcowboy.com/errors/url-private-host title: Url Private Host status: 400 detail: The URL must point at a public host instance: /document/public/knowledge-bases/2694f968-93fd-44ca-9b92-2110ed1ee61e/documents request_id: fe3face6-ab9d-40e8-bf24-7d911cbf4ce8 '401': $ref: '#/components/responses/Unauthorized' '403': $ref: '#/components/responses/KnowledgeForbidden' '404': $ref: '#/components/responses/NotFound' '409': description: | The same file is already in this knowledge base (`duplicate-knowledge-document`). `details.existing_knowledge_document_id` names it. content: application/json: schema: $ref: '#/components/schemas/Error' '413': description: | Content over the size limit: 1 MB for markdown, 25 MB for files (`payload-too-large`), or the file at `source_url` is over 25 MB (`url-too-large`). content: application/json: schema: $ref: '#/components/schemas/Error' '415': description: '`mime_type` not supported, the file extension does not match it, or `source_url` returned an unsupported type' content: application/json: schema: $ref: '#/components/schemas/Error' '429': $ref: '#/components/responses/TooManyRequests' '502': description: | `source_url` could not be fetched (`url-fetch-failed`, including a non-2xx answer from that server) or redirected too many times (`url-too-many-redirects`). Nothing was added. content: application/json: schema: $ref: '#/components/schemas/Error' '504': description: Fetching `source_url` timed out (`url-timeout`). Nothing was added. content: application/json: schema: $ref: '#/components/schemas/Error' /document/public/knowledge-bases/{knowledge_base_id}/documents/{knowledge_document_id}/uploaded: parameters: - $ref: '#/components/parameters/KnowledgeBaseId' - $ref: '#/components/parameters/KnowledgeDocumentId' post: operationId: confirmKnowledgeDocumentUpload summary: Confirm a document upload description: | Call after the PUT to `upload.url` succeeds. Processing starts now. Answers `409` if the upload was already confirmed (`already-confirmed`) or the file duplicates one already in the knowledge base (`duplicate-knowledge-document`), `413` if the stored file is over 25 MB, and `400` if no file was uploaded (PUT it, then confirm again). A duplicate `409` or a `413` marks the document `ingest_status: failed`, with `ingest_error` set to `Uploaded file exceeds the 26214400 byte limit` or `This document is already in this knowledge base`. PUT different bytes to `upload.url` (or a new one from `POST .../upload-url`) and confirm again; a successful confirm clears `ingest_error`. Or delete the document with `DELETE .../documents/{knowledge_document_id}`. The other refusals (`already-confirmed`, no file uploaded) leave the document unchanged. tags: [Knowledge bases] security: - apiKey: [] apiSecret: [] - oauth2: [agents:write] responses: '200': description: Upload confirmed content: application/json: schema: type: object properties: data: $ref: '#/components/schemas/KnowledgeDocument' meta: $ref: '#/components/schemas/Meta' '400': $ref: '#/components/responses/BadRequest' '401': $ref: '#/components/responses/Unauthorized' '403': $ref: '#/components/responses/KnowledgeForbidden' '404': $ref: '#/components/responses/NotFound' '409': $ref: '#/components/responses/Conflict' '413': description: The stored file is over 25 MB content: application/json: schema: $ref: '#/components/schemas/Error' '429': $ref: '#/components/responses/TooManyRequests' /document/public/knowledge-bases/{knowledge_base_id}/documents/{knowledge_document_id}/upload-url: parameters: - $ref: '#/components/parameters/KnowledgeBaseId' - $ref: '#/components/parameters/KnowledgeDocumentId' post: operationId: reissueKnowledgeDocumentUploadUrl summary: Get a new document upload URL description: | Signs a new presigned PUT for a file upload that has not been confirmed yet: its `upload.url` expired (after 48 hours), or a confirm was refused and you want to send different bytes. The response has the same shape as the upload ticket from `POST .../documents`. The URL writes the same file with the same `Content-Type`, so PUT exactly the `upload.headers` and then confirm with `POST .../uploaded`. A document marked `ingest_status: failed` by a refused confirm goes back to `pending`, with `ingest_error` cleared. A `pending` document is not changed. Each call signs another URL, so retrying is safe; earlier URLs keep working until they expire. Once an upload is confirmed its file is fixed, so this answers `409` (`already-confirmed`) whatever the document's `ingest_status`, and for markdown and URL documents, which are confirmed when created. Delete the document and add it again to replace it. Another account's document answers `404`. tags: [Knowledge bases] security: - apiKey: [] apiSecret: [] - oauth2: [agents:write] responses: '200': description: A new upload ticket content: application/json: schema: type: object properties: data: $ref: '#/components/schemas/KnowledgeUploadTicket' meta: $ref: '#/components/schemas/Meta' examples: uploadTicket: $ref: './examples/knowledge-document.upload.response.json' '401': $ref: '#/components/responses/Unauthorized' '403': $ref: '#/components/responses/KnowledgeForbidden' '404': $ref: '#/components/responses/NotFound' '409': $ref: '#/components/responses/Conflict' '429': $ref: '#/components/responses/TooManyRequests' /document/public/knowledge-bases/{knowledge_base_id}/documents/{knowledge_document_id}: parameters: - $ref: '#/components/parameters/KnowledgeBaseId' - $ref: '#/components/parameters/KnowledgeDocumentId' delete: operationId: deleteKnowledgeDocument summary: Delete a document from a knowledge base description: | Removes one document from a knowledge base. The response confirms the `knowledge_document_id` that was deleted. tags: [Knowledge bases] security: - apiKey: [] apiSecret: [] - oauth2: [agents:write] responses: '200': description: Deleted content: application/json: schema: type: object properties: data: type: object properties: knowledge_document_id: type: string format: uuid deleted: type: boolean meta: $ref: '#/components/schemas/Meta' '401': $ref: '#/components/responses/Unauthorized' '403': $ref: '#/components/responses/KnowledgeForbidden' '404': $ref: '#/components/responses/NotFound' '429': $ref: '#/components/responses/TooManyRequests' /user/public/users: get: operationId: listUsers summary: List users description: | Pages with `skip` and `limit` (not `offset`). `limit` defaults to 50 and is capped at 100; a larger value is clamped, not rejected. The total is in `data.total_users`. Deleted users are excluded. tags: [Account] security: - apiKey: [] apiSecret: [] - oauth2: [contacts:read] parameters: - name: search_term in: query description: Matches the user's name or email. schema: type: string - name: skip in: query description: Users to skip. A negative or non-numeric value is treated as 0. schema: type: integer minimum: 0 default: 0 - name: limit in: query description: | Page size, at most 100. A larger value is treated as 100; 0, a negative, non-numeric or empty value is treated as 50. schema: type: integer minimum: 1 maximum: 100 default: 50 responses: '200': description: Team users content: application/json: schema: type: object properties: data: type: object properties: users: type: array items: $ref: '#/components/schemas/User' total_users: type: integer meta: $ref: '#/components/schemas/Meta' '401': $ref: '#/components/responses/Unauthorized' '429': $ref: '#/components/responses/TooManyRequests' /automation/public/brands: get: operationId: listBrands summary: List 10DLC brands description: | Lists your registered brands, each with its texting campaigns. Returns the `brand_id` values accepted by sends, phone lines and agents. This route isn't paged. tags: [Automation] security: - apiKey: [] apiSecret: [] - oauth2: [contacts:read] responses: '200': description: Registered brands content: application/json: schema: type: object properties: data: type: array items: $ref: '#/components/schemas/Brand' meta: $ref: '#/components/schemas/Meta' '401': $ref: '#/components/responses/Unauthorized' '429': $ref: '#/components/responses/TooManyRequests' /automation/public/pools: get: operationId: listPools summary: List number pools description: | Lists your texting (10DLC) campaigns, registered or not. Each one's `pool_id` is the value accepted by `POST /phone/public/numbers/rent`. Without `limit` every campaign is returned; this route has no offset parameter. tags: [Automation] security: - apiKey: [] apiSecret: [] - oauth2: [numbers:read] parameters: - name: limit in: query description: Most campaigns to return. No default. schema: type: integer minimum: 1 - name: search_term in: query description: Case-insensitive match against the campaign name. schema: type: string responses: '200': description: Number pools content: application/json: schema: type: object properties: data: type: array items: $ref: '#/components/schemas/Pool' meta: $ref: '#/components/schemas/Meta' '401': $ref: '#/components/responses/Unauthorized' '429': $ref: '#/components/responses/TooManyRequests' /automation/public/dispositions: get: operationId: listDispositions summary: List disposition codes description: | The fixed list of delivery result codes reported on sends and delivery webhooks. This route isn't paged. tags: [Automation] security: - apiKey: [] apiSecret: [] - oauth2: [contacts:read] responses: '200': description: Disposition codes content: application/json: schema: type: object properties: data: type: array items: $ref: '#/components/schemas/Disposition' meta: $ref: '#/components/schemas/Meta' '401': $ref: '#/components/responses/Unauthorized' '429': $ref: '#/components/responses/TooManyRequests' /automation/public/contacts: post: operationId: createContactSimple summary: Create a contact with basic fields description: > Creates one contact from a phone number and optional name and email, and returns its `contact_id`. For every contact field, use `POST /contact/public/contacts`. tags: [Automation] security: - apiKey: [] apiSecret: [] - oauth2: [contacts:write] requestBody: required: true content: application/json: schema: type: object required: [phone_number] properties: phone_number: type: string description: Phone number in E.164 format pattern: '^\+[1-9]\d{1,14}$' example: '+12125550120' first_name: type: string example: Jane last_name: type: string example: Doe email: type: string format: email example: jane@example.com responses: '201': description: Contact created content: application/json: schema: type: object properties: data: type: object properties: status: type: string example: success contact_id: type: string format: uuid description: The UUID of the newly created contact meta: $ref: '#/components/schemas/Meta' '400': $ref: '#/components/responses/BadRequest' '401': $ref: '#/components/responses/Unauthorized' '429': $ref: '#/components/responses/TooManyRequests' /automation/public/contacts/find-or-create: post: operationId: updateOrCreateContact summary: Find or create a contact description: > Looks up a contact by `phone_number`, then by `email` if you send one, and returns its `contact_id`. If none matches, creates the contact and returns the new `contact_id`. Send `list_id` to also add the contact to a list. Safe to retry. tags: [Automation] security: - apiKey: [] apiSecret: [] - oauth2: [contacts:write] requestBody: required: true content: application/json: schema: type: object required: [phone_number] properties: phone_number: type: string description: Phone number in E.164 format (used for lookup and creation) pattern: '^\+[1-9]\d{1,14}$' example: '+12125550120' email: type: string format: email description: Email address (secondary lookup key, stored on new contacts) first_name: type: string description: First name (used only when creating) last_name: type: string description: Last name (used only when creating) list_id: type: string description: Optional list ID to add the contact to responses: '200': description: Contact found or created content: application/json: schema: type: object properties: data: type: object properties: status: type: string example: success contact_id: type: string format: uuid created: type: boolean description: '`true` if a new contact was created, `false` if an existing contact was found' added_to_list: type: boolean description: '`true` if the contact was added to the specified list' meta: $ref: '#/components/schemas/Meta' '400': $ref: '#/components/responses/BadRequest' '401': $ref: '#/components/responses/Unauthorized' '429': $ref: '#/components/responses/TooManyRequests' /automation/public/contacts/list: put: operationId: addContactToListFlexible summary: Add a contact to a list by phone, email or ID description: > Adds a contact to a list. Identify the contact with `contact_id`, `phone_number` or `email`; `contact_id` is used first when you send more than one. tags: [Automation] security: - apiKey: [] apiSecret: [] - oauth2: [lists:write] requestBody: required: true content: application/json: schema: type: object required: [list_id] properties: list_id: type: string description: The list to add the contact to contact_id: type: string description: Contact UUID. Used before `phone_number` and `email`. phone_number: type: string description: Phone number to look up the contact (used if contact_id not provided) pattern: '^\+[1-9]\d{1,14}$' example: '+13125550142' email: type: string format: email description: Email to look up the contact (used if contact_id and phone not provided) action: type: string enum: [add, remove_list] default: add description: '`add` puts the contact on the list; `remove_list` takes it off. Any other value returns `400`.' responses: '200': description: Contact added to list content: application/json: schema: type: object properties: data: type: object properties: status: type: string example: success meta: $ref: '#/components/schemas/Meta' '400': $ref: '#/components/responses/BadRequest' '401': $ref: '#/components/responses/Unauthorized' '404': $ref: '#/components/responses/NotFound' '429': $ref: '#/components/responses/TooManyRequests' /boards/public/boards: get: operationId: listBoards summary: List boards description: | Lists the non-deleted pipelines the caller can see, newest first. Without `limit` every pipeline is returned. This route has no offset parameter. tags: [Pipelines] security: - apiKey: [] apiSecret: [] - oauth2: [contacts:read] parameters: - name: limit in: query description: Most pipelines to return. No default. schema: type: integer minimum: 1 - name: search_term in: query description: Case-insensitive match against the pipeline name. schema: type: string responses: '200': description: Pipelines content: application/json: schema: type: object properties: data: type: array items: $ref: '#/components/schemas/Board' meta: $ref: '#/components/schemas/Meta' '401': $ref: '#/components/responses/Unauthorized' '429': $ref: '#/components/responses/TooManyRequests' post: operationId: createBoard summary: Create a board description: | Creates a pipeline board. Its stages are contact lists. Send a `name` (it defaults to `New Bucket Board`) and the `lists` in display order, and optionally a `brand_id` to scope the board to one brand. tags: [Pipelines] security: - apiKey: [] apiSecret: [] - oauth2: [contacts:write] requestBody: required: true content: application/json: schema: $ref: '#/components/schemas/CreateBoard' responses: '201': description: Board created content: application/json: schema: type: object properties: data: $ref: '#/components/schemas/Board' meta: $ref: '#/components/schemas/Meta' '400': $ref: '#/components/responses/BadRequest' '401': $ref: '#/components/responses/Unauthorized' '429': $ref: '#/components/responses/TooManyRequests' /boards/public/boards/{board_id}: parameters: - name: board_id in: path required: true schema: type: string format: uuid put: operationId: updateBoard summary: Update a board description: | Changes only the fields you send; `lists` replaces every stage. Returns the pipeline as it is after the change. A `board_id` that isn't one of your pipelines returns `404`. tags: [Pipelines] security: - apiKey: [] apiSecret: [] - oauth2: [contacts:write] requestBody: required: true content: application/json: schema: $ref: '#/components/schemas/UpdateBoard' responses: '200': description: The pipeline content: application/json: schema: type: object properties: data: $ref: '#/components/schemas/Board' meta: $ref: '#/components/schemas/Meta' '400': $ref: '#/components/responses/BadRequest' '401': $ref: '#/components/responses/Unauthorized' '404': $ref: '#/components/responses/NotFound' '429': $ref: '#/components/responses/TooManyRequests' delete: operationId: deleteBoard summary: Delete a board description: | Soft delete: sets `deleted_at` and `deleted_by` and hides the pipeline from the list. The stage lists and their contacts are kept. Returns the deleted pipeline; a `board_id` that isn't one of your pipelines returns `404`. tags: [Pipelines] security: - apiKey: [] apiSecret: [] - oauth2: [contacts:write] responses: '200': description: The pipeline content: application/json: schema: type: object properties: data: $ref: '#/components/schemas/Board' meta: $ref: '#/components/schemas/Meta' '401': $ref: '#/components/responses/Unauthorized' '404': $ref: '#/components/responses/NotFound' '429': $ref: '#/components/responses/TooManyRequests' /boards/public/boards/{board_id}/lists: parameters: - name: board_id in: path required: true schema: type: string format: uuid post: operationId: createBoardList summary: Add a list to a board description: | Adds a stage to the end of the pipeline. A stage is an existing contact list, referenced by `list_id`. Returns the pipeline with the new stage. tags: [Pipelines] security: - apiKey: [] apiSecret: [] - oauth2: [contacts:write] requestBody: required: true content: application/json: schema: $ref: '#/components/schemas/CreateBoardList' responses: '201': description: The pipeline content: application/json: schema: type: object properties: data: $ref: '#/components/schemas/Board' meta: $ref: '#/components/schemas/Meta' '400': $ref: '#/components/responses/BadRequest' '401': $ref: '#/components/responses/Unauthorized' '404': $ref: '#/components/responses/NotFound' '429': $ref: '#/components/responses/TooManyRequests' /boards/public/boards/{board_id}/lists/{list_id}: parameters: - name: board_id in: path required: true schema: type: string format: uuid - name: list_id in: path required: true schema: type: string delete: operationId: deleteBoardList summary: Remove a list from a board description: | Removes the stage from the pipeline. The contact list itself is not deleted. Returns the pipeline without the stage. tags: [Pipelines] security: - apiKey: [] apiSecret: [] - oauth2: [contacts:write] responses: '200': description: The pipeline content: application/json: schema: type: object properties: data: $ref: '#/components/schemas/Board' meta: $ref: '#/components/schemas/Meta' '401': $ref: '#/components/responses/Unauthorized' '404': $ref: '#/components/responses/NotFound' '429': $ref: '#/components/responses/TooManyRequests' /rvm: parameters: - $ref: '#/components/parameters/IdempotencyKey' post: operationId: sendRvm x-webhooks: [contact.rvm.status, contact.rvm.receipt] summary: Send a ringless voicemail description: | Queues one ringless voicemail and answers `202` immediately. The credentials, the payload and the recipient's consent are checked afterwards, so a `202` means "queued", not "accepted". The outcome arrives on `callback_url` and on the `contact.rvm.status` webhook. See [Outcomes](https://www.dropcowboy.com/developers/api/outcomes) for every `reason_code`. Each number counts toward your account's contact frequency limit (default 3 attempts in 3 days per number, across campaigns and dialer calls). Over the limit the send fails with 4013 (Too Many Attempts), and the status webhook carries `frequency_limit`. `max_attempts` and `max_attempt_window_ms` can make the limit stricter for this send, never looser. Test numbers are exempt. Results with `reason_code` 0 (Success), 4001 or 4002 carry a `proof_of_delivery_url`, and `contact.rvm.receipt` fires once the recording behind it can be played. It's available for 7 days. See `GET /campaign/public/receipts/{token}`. tags: [Ringless voicemail] security: - apiKey: [] apiSecret: [] - oauth2: [rvm:send] requestBody: required: true content: application/json: schema: $ref: '#/components/schemas/SendRvm' examples: media: $ref: './examples/send-rvm.request.json' contact: $ref: './examples/send-rvm-contact.request.json' responses: '202': description: Queued. Validation happens next; watch `callback_url` or webhooks for the result. content: application/json: schema: $ref: '#/components/schemas/AsyncResponse' examples: queued: $ref: './examples/send.queued.response.json' '400': $ref: '#/components/responses/SendBadRequest' '401': $ref: '#/components/responses/SendMissingCredentials' '429': $ref: '#/components/responses/TooManyRequests' '500': $ref: '#/components/responses/SendNotQueued' '502': $ref: '#/components/responses/SendQueueNoReceipt' callbacks: sendOutcome: '{$request.body#/callback_url}': post: summary: Send outcome (callback_url) description: One unsigned POST, 10 second timeout, no retries. requestBody: content: application/json: schema: $ref: '#/components/schemas/SendOutcome' examples: delivered: $ref: './examples/callback.rvm-success.json' responses: '200': description: Any 2xx is accepted. The response body is ignored. '4XX': description: Ignored. The callback is not retried. /sms: parameters: - $ref: '#/components/parameters/IdempotencyKey' post: operationId: sendSms x-webhooks: [contact.sms.status] summary: Send a text description: | Queues one SMS, one MMS when `media_urls` or `media_ids` is set, or one RCS message when `template_id` is set, and answers `202` immediately. Validation, consent, media and carrier registration are checked after the `202`. The outcome arrives on `callback_url` and on the `contact.sms.status` webhook. `message_id` only confirms the request was accepted: the message isn't returned by `GET /phone/public/sms/{sms_id}`, so track it through those results. MMS: up to 10 files, 1 MiB each and 5 MiB in total, JPEG, PNG, GIF, WAV or MP3 (judged by the bytes). Each file is copied to `mms.dropcowboy.com`, scanned for malware and sent only when clean; the copies are deleted after 3 days. Billed per file; the caption adds no charge. You can send to any country your account doesn't block. Media problems fail with 3032 to 3039. See [MMS](https://www.dropcowboy.com/developers/api/texts#mms). Outside the contact's allowed hours the message fails with 4011 (TCPA Hours) and is not retried. Numbers over your account's contact frequency limit (default 3 attempts in 3 days) fail with 4013. A missing texting registration fails with 6009 (Unregistered Brand) or a pool error, and a `phone_line_id` whose line has no texting campaign fails with 3029 (Phone Line Has No SMS Campaign); see [Outcomes](https://www.dropcowboy.com/developers/api/outcomes). tags: [Texts] security: - apiKey: [] apiSecret: [] - oauth2: [sms:send] requestBody: required: true content: application/json: schema: $ref: '#/components/schemas/SendSmsMessage' examples: sms: $ref: './examples/send-sms.request.json' mms: $ref: './examples/send-mms.request.json' rcs: $ref: './examples/send-rcs.request.json' responses: '202': description: Queued. Validation happens next; watch `callback_url` or webhooks for the result. content: application/json: schema: $ref: '#/components/schemas/AsyncResponse' examples: queued: $ref: './examples/send.queued.response.json' '400': $ref: '#/components/responses/SendBadRequest' '401': $ref: '#/components/responses/SendMissingCredentials' '429': $ref: '#/components/responses/TooManyRequests' '500': $ref: '#/components/responses/SendNotQueued' '502': $ref: '#/components/responses/SendQueueNoReceipt' callbacks: sendOutcome: '{$request.body#/callback_url}': post: summary: Send outcome (callback_url) description: One unsigned POST, 10 second timeout, no retries. requestBody: content: application/json: schema: $ref: '#/components/schemas/SendOutcome' examples: tcpaHours: $ref: './examples/callback.sms-tcpa-hours.json' responses: '200': description: Any 2xx is accepted. The response body is ignored. '4XX': description: Ignored. The callback is not retried. /voice-broadcast: parameters: - $ref: '#/components/parameters/IdempotencyKey' post: operationId: sendVoiceBroadcast x-webhooks: [contact.rvm.status] summary: Send a voice broadcast description: | Queues one Press-1 call and answers `202` immediately. Validation happens after the `202`. The outcome arrives on `callback_url` and on the `contact.rvm.status` webhook with `campaign_type: voice_broadcast`. Transfer setup errors fail the send before dialing: a `transfer_ivr_id` that is not a valid ID fails with 3030 (Invalid Transfer IVR), and a `transfer_digit` with neither `transfer_ivr_id` nor `transfer_to` fails with 3031 (Transfer Destination Missing). Only the format of `transfer_ivr_id` is checked before dialing, so make sure it names one of your phone lines. tags: [Voice broadcasts and AI calls] security: - apiKey: [] apiSecret: [] - oauth2: [voice:send] requestBody: required: true content: application/json: schema: $ref: '#/components/schemas/SendVoiceBroadcast' examples: tts: $ref: './examples/send-voice-broadcast.request.json' responses: '202': description: Queued. Validation happens next; watch `callback_url` or webhooks for the result. content: application/json: schema: $ref: '#/components/schemas/AsyncResponse' examples: queued: $ref: './examples/send.queued.response.json' '400': $ref: '#/components/responses/SendBadRequest' '401': $ref: '#/components/responses/SendMissingCredentials' '429': $ref: '#/components/responses/TooManyRequests' '500': $ref: '#/components/responses/SendNotQueued' '502': $ref: '#/components/responses/SendQueueNoReceipt' callbacks: sendOutcome: '{$request.body#/callback_url}': post: summary: Send outcome (callback_url) description: One unsigned POST, 10 second timeout, no retries. requestBody: content: application/json: schema: $ref: '#/components/schemas/SendOutcome' responses: '200': description: Any 2xx is accepted. The response body is ignored. '4XX': description: Ignored. The callback is not retried. /ai-broadcast: parameters: - $ref: '#/components/parameters/IdempotencyKey' post: operationId: sendAiBroadcast x-webhooks: [contact.rvm.status, ai_agent.call.started, ai_agent.call.completed, ai_agent.call.failed, ai_agent.outcome.confirm, ai_agent.outcome.opt_out, ai_agent.outcome.transfer, ai_agent.outcome.voicemail, ai_agent.outcome.incomplete, ai_agent.outcome.timeout, ai_agent.outcome.identity_failed] summary: Send an AI call description: | Queues one outbound call handled by a published AI agent and answers `202` immediately. The agent is checked after the `202`. The per-contact outcome arrives on `callback_url` and on `contact.rvm.status` with `campaign_type: ai_broadcast`. The call itself is reported once on `ai_agent.call.completed`, `ai_agent.call.failed` (not answered) or, when the agent recorded an outcome, the matching `ai_agent.outcome.*` event. tags: [Voice broadcasts and AI calls] security: - apiKey: [] apiSecret: [] - oauth2: [voice:send] requestBody: required: true content: application/json: schema: $ref: '#/components/schemas/SendAiBroadcast' examples: agent: $ref: './examples/send-ai-broadcast.request.json' responses: '202': description: Queued. Validation happens next; watch `callback_url` or webhooks for the result. content: application/json: schema: $ref: '#/components/schemas/AsyncResponse' examples: queued: $ref: './examples/send.queued.response.json' '400': $ref: '#/components/responses/SendBadRequest' '401': $ref: '#/components/responses/SendMissingCredentials' '429': $ref: '#/components/responses/TooManyRequests' '500': $ref: '#/components/responses/SendNotQueued' '502': $ref: '#/components/responses/SendQueueNoReceipt' callbacks: sendOutcome: '{$request.body#/callback_url}': post: summary: Send outcome (callback_url) description: One unsigned POST, 10 second timeout, no retries. requestBody: content: application/json: schema: $ref: '#/components/schemas/SendOutcome' responses: '200': description: Any 2xx is accepted. The response body is ignored. '4XX': description: Ignored. The callback is not retried. /contact/public/contacts/{id}/timeline: get: operationId: getContactTimeline summary: 'Get a contact''s timeline' description: | Lists the contact's activity, newest first: calls, texts, notes, tag changes, consent changes, follow-ups, and events added with `POST /register/public/events`. `type` is applied to each page after it is read, so a filtered page can come back short or empty while older entries of that type still exist. `total` counts the entries in this response only. An unknown contact returns an empty page. tags: [Contact details] security: - apiKey: [] apiSecret: [] - oauth2: [contacts:read] parameters: - name: id in: path required: true schema: type: string description: '`contact_id` of the contact.' - name: type in: query schema: type: string example: tag_added description: Only entries of this type, such as `call`, `sms`, `note`, `tag_added`, `consent_granted` or `followup.created`. - name: limit in: query description: Page size. Values above 100 are treated as 100. schema: type: integer default: 50 minimum: 1 maximum: 100 - name: offset in: query schema: type: integer default: 0 responses: '200': description: Timeline entries content: application/json: schema: type: object properties: data: type: object properties: entries: type: array items: $ref: '#/components/schemas/TimelineEntry' total: type: integer description: Entries in this response. meta: $ref: '#/components/schemas/Meta' '400': $ref: '#/components/responses/BadRequest' '401': $ref: '#/components/responses/Unauthorized' '429': $ref: '#/components/responses/TooManyRequests' webhooks: contact.rvm.status: post: operationId: webhookRvmStatus summary: Voicemail or call outcome description: | Final outcome of one ringless voicemail, voice broadcast call or AI broadcast call, whether sent through the API or a campaign. Fires once per contact. `campaign_type` tells the channels apart. Look up `reason_code` in [Outcomes](https://www.dropcowboy.com/developers/api/outcomes). A voicemail sent through the Drop Cowboy integration reports only `team_id`, `user_id`, `contact_id`, `entry_id` and `status` (`success` or `failure`), and only when its outcome changes: a repeated report of the same outcome sends nothing. A voicemail the integration could not send reports `failure`. tags: [Webhooks] requestBody: required: true content: application/json: schema: allOf: - $ref: '#/components/schemas/WebhookEnvelope' - type: object properties: data: $ref: '#/components/schemas/DeliveryStatusData' examples: delivered: $ref: './examples/webhook.rvm-status.json' frequencyLimit: $ref: './examples/webhook.rvm-status-frequency.json' responses: '200': description: Any 2xx acknowledges the delivery. contact.rvm.receipt: post: operationId: webhookRvmReceipt summary: Proof of delivery ready description: | Fires at most once per drop, when the recording behind its `proof_of_delivery_url` has been stored and the link will play. Only drops where a voicemail system took the call can have one: a ringless voicemail that ended with `reason_code` 0 (Success), 4001 (VoiceMail Not Setup) or 4002 (VoiceMail Full), or a voice broadcast or AI call that ended with 0 in a mailbox. Proof of delivery is available for 7 days. `data` is everything `contact.rvm.status` sent for the drop, plus `proof_of_delivery_url` (a new link, valid for 7 days) and `receipt_ready_at`. For a ringless voicemail, all three codes play the call recording, both sides, from the moment the recipient's carrier answered until hangup. For code 0 it includes your message. For 4001 (VoiceMail Not Setup) and 4002 (Mailbox full) it contains the carrier's announcement, which shows the mailbox was reached and why the message couldn't be left. For a voice broadcast or AI call it plays the message left after the beep. tags: [Webhooks] requestBody: required: true content: application/json: schema: allOf: - $ref: '#/components/schemas/WebhookEnvelope' - type: object properties: data: $ref: '#/components/schemas/DeliveryReceiptData' examples: mailboxFull: $ref: './examples/webhook.rvm-receipt.json' responses: '200': description: Any 2xx acknowledges the delivery. contact.sms.status: post: operationId: webhookSmsStatus summary: Text message outcome description: | Final outcome of one SMS, MMS or RCS message. Same `data` as `contact.rvm.status`, with `campaign_type` `sms`, or `mms` for a send with media (a request refused before its media is stored reports `sms`). `success` means sent to the carrier, not received by the handset. Carries no `sms_id` and no media. tags: [Webhooks] requestBody: required: true content: application/json: schema: allOf: - $ref: '#/components/schemas/WebhookEnvelope' - type: object properties: data: $ref: '#/components/schemas/DeliveryStatusData' examples: failed: $ref: './examples/webhook.sms-status.json' responses: '200': description: Any 2xx acknowledges the delivery. contact.email.status: post: operationId: webhookEmailStatus summary: Email outcome description: | Fires when a campaign email is sent or fails, and again for later outcomes on any tracked email: `delivered`, `opened`, `clicked`, `bounced`, `complained`. Unlike the voice and text status events it can fire several times for one email. A hard bounce on a campaign email arrives as `failure` with `reason` `hard_bounce` instead of `bounced`. tags: [Webhooks] requestBody: required: true content: application/json: schema: allOf: - $ref: '#/components/schemas/WebhookEnvelope' - type: object properties: data: $ref: '#/components/schemas/EmailStatusData' examples: opened: $ref: './examples/webhook.email-status.json' responses: '200': description: Any 2xx acknowledges the delivery. ai_agent.call.completed: post: operationId: webhookAiAgentCallCompleted summary: AI agent call finished description: | Exactly one of `ai_agent.call.failed`, an `ai_agent.outcome.*` event, or this one fires when an AI agent call ends: failed when the call did not connect, the outcome event when the agent recorded a matching outcome, otherwise this one. Inbound calls (`direction: inbound`) and outbound AI voice broadcast calls (no `direction`, `drop_id` set) carry different fields; see `AiAgentCallData`. tags: [Webhooks] requestBody: required: true content: application/json: schema: allOf: - $ref: '#/components/schemas/WebhookEnvelope' - type: object properties: data: $ref: '#/components/schemas/AiAgentCallData' examples: completed: $ref: './examples/webhook.ai-agent-call-completed.json' outbound: $ref: './examples/webhook.ai-agent-call-completed-outbound.json' responses: '200': description: Any 2xx acknowledges the delivery. campaign.started: post: operationId: webhookCampaignStarted summary: Campaign started description: | A campaign was started, resumed or retried with the start operation, from the dashboard or the API. It does not fire when the request finds the campaign already running or finished. It also does not fire when a campaign begins sending on its own, such as right after automatic approval at creation or after a compliance review. tags: [Webhooks] requestBody: required: true content: application/json: schema: allOf: - $ref: '#/components/schemas/WebhookEnvelope' - type: object properties: data: $ref: '#/components/schemas/CampaignEventData' examples: started: $ref: './examples/webhook.campaign-started.json' responses: '200': description: Any 2xx acknowledges the delivery. detection.event: post: operationId: webhookDetectionEvent summary: Detection event (Detection WebSocket sessions) description: | Sent only for sessions that asked for a webhook (`webhook` in the welcome, or `webhook_url` on a Twilio stream). Headers: - `X-Signature`: `v1=hmac-sha256,` followed by the hex HMAC-SHA256 of `{X-Timestamp}.{raw body}`, keyed with your Detection API key - `X-Timestamp`: Unix milliseconds. Reject values more than five minutes old - `X-Delivery-Id`: deduplicate on it - `X-Event`: the event name, same as `event` in the body - `X-Source`: `ws` or `twilio` Answer with any `2xx` within 10 seconds. A `5xx` or a timeout is retried with exponential backoff, 4 attempts in about 45 seconds. A `429` is retried after your `Retry-After`. Any other `4xx` is not retried. tags: [Detection] requestBody: required: true content: application/json: schema: $ref: '#/components/schemas/DetectionWebhook' examples: beep: $ref: './examples/detection-webhook.beep.json' responses: '200': description: Any 2xx acknowledges the delivery. contact.msg.received: post: operationId: webhookMessageReceived summary: Inbound text received description: | A contact texted one of your numbers. Replies such as STOP also fire `contact.msg.opt_out`. An MMS lists its files under `data.sms.media` (metadata, no link); read the message with `GET /phone/public/sms/{sms_id}` for a download link once a file's scan is `clean`. tags: [Webhooks] requestBody: required: true content: application/json: schema: allOf: - $ref: '#/components/schemas/WebhookEnvelope' - type: object properties: data: $ref: '#/components/schemas/MessageEventData' examples: mms: $ref: './examples/webhook.msg-received-mms.json' responses: '200': description: Any 2xx acknowledges the delivery. contact.msg.sent: post: operationId: webhookMessageSent summary: Outbound text sent description: | A text or MMS went out from the inbox or `POST /phone/public/sms/reply`. Same `data` shape as `contact.msg.received`, with `sent_at`. Automatic replies to STOP, START and HELP keywords fire it too. Not fired for `POST /sms`; use `contact.sms.status` for those. tags: [Webhooks] requestBody: required: true content: application/json: schema: allOf: - $ref: '#/components/schemas/WebhookEnvelope' - type: object properties: data: $ref: '#/components/schemas/MessageEventData' examples: reply: $ref: './examples/webhook.msg-sent.json' responses: '200': description: Any 2xx acknowledges the delivery. campaign.created: post: operationId: webhookCampaignCreated summary: Campaign created description: | A campaign was created from the dashboard or the API. Saving a draft does not fire it. A campaign that is approved automatically can begin sending immediately, and that start does not fire a separate `campaign.started`. tags: [Webhooks] requestBody: required: true content: application/json: schema: allOf: - $ref: '#/components/schemas/WebhookEnvelope' - type: object properties: data: $ref: '#/components/schemas/CampaignEventData' examples: created: $ref: './examples/webhook.campaign-created.json' responses: '200': description: Any 2xx acknowledges the delivery. campaign.updated: post: operationId: webhookCampaignUpdated summary: Campaign updated description: | A campaign's settings were changed, including its drip rate. Editing a draft does not fire it. tags: [Webhooks] requestBody: required: true content: application/json: schema: allOf: - $ref: '#/components/schemas/WebhookEnvelope' - type: object properties: data: $ref: '#/components/schemas/CampaignEventData' examples: updated: $ref: './examples/webhook.campaign-updated.json' responses: '200': description: Any 2xx acknowledges the delivery. campaign.deleted: post: operationId: webhookCampaignDeleted summary: Campaign deleted description: A campaign was deleted. `data` describes the campaign as it was when it was deleted. tags: [Webhooks] requestBody: required: true content: application/json: schema: allOf: - $ref: '#/components/schemas/WebhookEnvelope' - type: object properties: data: $ref: '#/components/schemas/CampaignEventData' examples: deleted: $ref: './examples/webhook.campaign-deleted.json' responses: '200': description: Any 2xx acknowledges the delivery. campaign.paused: post: operationId: webhookCampaignPaused summary: Campaign paused description: | A running campaign was paused from the dashboard or the API. Resuming it later fires `campaign.started`. tags: [Webhooks] requestBody: required: true content: application/json: schema: allOf: - $ref: '#/components/schemas/WebhookEnvelope' - type: object properties: data: $ref: '#/components/schemas/CampaignEventData' examples: paused: $ref: './examples/webhook.campaign-paused.json' responses: '200': description: Any 2xx acknowledges the delivery. campaign.completed: post: operationId: webhookCampaignCompleted summary: Campaign completed description: | A campaign finished: every contact in the send has been processed. No teammate causes completion, so `data` has no `user_id` or `user`. tags: [Webhooks] requestBody: required: true content: application/json: schema: allOf: - $ref: '#/components/schemas/WebhookEnvelope' - type: object properties: data: $ref: '#/components/schemas/CampaignCompletedEventData' examples: completed: $ref: './examples/webhook.campaign-completed.json' responses: '200': description: Any 2xx acknowledges the delivery. number.provisioned: post: operationId: webhookNumberProvisioned summary: Phone number provisioned description: | A phone number was added to your account by renting it, connecting it from your own carrier, or setting it up during a trial period. One event fires per number. Numbers bought through the number store checkout do not fire it. tags: [Webhooks] requestBody: required: true content: application/json: schema: allOf: - $ref: '#/components/schemas/WebhookEnvelope' - type: object properties: data: $ref: '#/components/schemas/PhoneNumberEventData' examples: provisioned: $ref: './examples/webhook.number-provisioned.json' responses: '200': description: Any 2xx acknowledges the delivery. number.released: post: operationId: webhookNumberReleased summary: Phone number released description: | A phone number was released from your account, for example when you delete the phone line that used it. tags: [Webhooks] requestBody: required: true content: application/json: schema: allOf: - $ref: '#/components/schemas/WebhookEnvelope' - type: object properties: data: $ref: '#/components/schemas/PhoneNumberEventData' examples: released: $ref: './examples/webhook.number-released.json' responses: '200': description: Any 2xx acknowledges the delivery. number.updated: post: operationId: webhookNumberUpdated summary: Phone number updated description: A phone number's settings changed, for example the phone line it routes to. tags: [Webhooks] requestBody: required: true content: application/json: schema: allOf: - $ref: '#/components/schemas/WebhookEnvelope' - type: object properties: data: $ref: '#/components/schemas/PhoneNumberEventData' examples: updated: $ref: './examples/webhook.number-updated.json' responses: '200': description: Any 2xx acknowledges the delivery. number.flagged: post: operationId: webhookNumberFlagged summary: Phone number flagged description: | The carrier network reported a spam or abuse complaint about one of your phone numbers. No teammate is involved, so `data` has no `user_id` or `user`. tags: [Webhooks] requestBody: required: true content: application/json: schema: allOf: - $ref: '#/components/schemas/WebhookEnvelope' - type: object properties: data: $ref: '#/components/schemas/PhoneNumberFlaggedEventData' examples: flagged: $ref: './examples/webhook.number-flagged.json' responses: '200': description: Any 2xx acknowledges the delivery. user.created: post: operationId: webhookUserCreated summary: Teammate added description: A teammate account was added to your team. `user` is the new teammate. tags: [Webhooks] requestBody: required: true content: application/json: schema: allOf: - $ref: '#/components/schemas/WebhookEnvelope' - type: object properties: data: $ref: '#/components/schemas/TeammateEventData' examples: created: $ref: './examples/webhook.user-created.json' responses: '200': description: Any 2xx acknowledges the delivery. user.updated: post: operationId: webhookUserUpdated summary: Teammate updated description: | A teammate's profile or settings were changed. `user_id` and `user` are the teammate who was edited, and `updated_by` is the user id of the teammate who made the change. They are the same when teammates edit their own profile, and differ when an admin edits someone else. tags: [Webhooks] requestBody: required: true content: application/json: schema: allOf: - $ref: '#/components/schemas/WebhookEnvelope' - type: object properties: data: $ref: '#/components/schemas/TeammateEventData' examples: updated: $ref: './examples/webhook.user-updated.json' responses: '200': description: Any 2xx acknowledges the delivery. user.deleted: post: operationId: webhookUserDeleted summary: Teammate removed description: | A teammate was removed from your team. `user` is the removed teammate's record, which is still attached after the removal. tags: [Webhooks] requestBody: required: true content: application/json: schema: allOf: - $ref: '#/components/schemas/WebhookEnvelope' - type: object properties: data: $ref: '#/components/schemas/TeammateEventData' examples: deleted: $ref: './examples/webhook.user-deleted.json' responses: '200': description: Any 2xx acknowledges the delivery. user.login: post: operationId: webhookUserLogin summary: Teammate signed in description: A teammate signed in to Drop Cowboy. tags: [Webhooks] requestBody: required: true content: application/json: schema: allOf: - $ref: '#/components/schemas/WebhookEnvelope' - type: object properties: data: $ref: '#/components/schemas/TeammateEventData' examples: login: $ref: './examples/webhook.user-login.json' responses: '200': description: Any 2xx acknowledges the delivery. user.logout: post: operationId: webhookUserLogout summary: Teammate signed out description: A teammate signed out of Drop Cowboy. A session that simply expires does not fire it. tags: [Webhooks] requestBody: required: true content: application/json: schema: allOf: - $ref: '#/components/schemas/WebhookEnvelope' - type: object properties: data: $ref: '#/components/schemas/TeammateEventData' examples: logout: $ref: './examples/webhook.user-logout.json' responses: '200': description: Any 2xx acknowledges the delivery. subscription.changed: post: operationId: webhookSubscriptionChanged summary: Subscription changed description: | Your plan or add-ons changed, for example after an upgrade, a downgrade or a seat change. `data` identifies only the team; read your account for the new plan. tags: [Webhooks] requestBody: required: true content: application/json: schema: allOf: - $ref: '#/components/schemas/WebhookEnvelope' - type: object properties: data: $ref: '#/components/schemas/SubscriptionEventData' examples: changed: $ref: './examples/webhook.subscription-changed.json' responses: '200': description: Any 2xx acknowledges the delivery. subscription.cancelled: post: operationId: webhookSubscriptionCancelled summary: Subscription cancelled description: Your subscription was cancelled. `data` identifies only the team. tags: [Webhooks] requestBody: required: true content: application/json: schema: allOf: - $ref: '#/components/schemas/WebhookEnvelope' - type: object properties: data: $ref: '#/components/schemas/SubscriptionEventData' examples: cancelled: $ref: './examples/webhook.subscription-cancelled.json' responses: '200': description: Any 2xx acknowledges the delivery. subscription.reactivated: post: operationId: webhookSubscriptionReactivated summary: Subscription reactivated description: A cancelled subscription was reactivated. `data` identifies only the team. tags: [Webhooks] requestBody: required: true content: application/json: schema: allOf: - $ref: '#/components/schemas/WebhookEnvelope' - type: object properties: data: $ref: '#/components/schemas/SubscriptionEventData' examples: reactivated: $ref: './examples/webhook.subscription-reactivated.json' responses: '200': description: Any 2xx acknowledges the delivery. subscription.paused: post: operationId: webhookSubscriptionPaused summary: Subscription paused or resumed description: | Your subscription was paused or resumed. `paused` is `true` on pause and `false` on resume; this one event covers both. tags: [Webhooks] requestBody: required: true content: application/json: schema: allOf: - $ref: '#/components/schemas/WebhookEnvelope' - type: object properties: data: $ref: '#/components/schemas/SubscriptionPausedEventData' examples: paused: $ref: './examples/webhook.subscription-paused.json' responses: '200': description: Any 2xx acknowledges the delivery. subscription.invoiced: post: operationId: webhookSubscriptionInvoiced summary: Invoice created or updated description: | An invoice for your account was created or reached a new status, for example `posted` and later `paid`. One event fires for each status an invoice reaches, so a single invoice usually produces several. No amounts or payment details are included. tags: [Webhooks] requestBody: required: true content: application/json: schema: allOf: - $ref: '#/components/schemas/WebhookEnvelope' - type: object properties: data: $ref: '#/components/schemas/SubscriptionInvoicedEventData' examples: paid: $ref: './examples/webhook.subscription-invoiced.json' responses: '200': description: Any 2xx acknowledges the delivery. archive.complete: post: operationId: webhookArchiveComplete summary: Daily campaign archive written description: | The nightly archive of one day's campaign results was written. `ftp_synced` tells you whether the archive was also copied to your FTP server. tags: [Webhooks] requestBody: required: true content: application/json: schema: allOf: - $ref: '#/components/schemas/WebhookEnvelope' - type: object properties: data: $ref: '#/components/schemas/ArchiveCompleteEventData' examples: complete: $ref: './examples/webhook.archive-complete.json' responses: '200': description: Any 2xx acknowledges the delivery. domain.verification.complete: post: operationId: webhookDomainVerificationComplete summary: Custom email domain verified description: | Your custom email domain finished verification: its CNAME records and then its MX record were found in DNS. tags: [Webhooks] requestBody: required: true content: application/json: schema: allOf: - $ref: '#/components/schemas/WebhookEnvelope' - type: object properties: data: $ref: '#/components/schemas/DomainVerificationCompleteEventData' examples: complete: $ref: './examples/webhook.domain-verification-complete.json' responses: '200': description: Any 2xx acknowledges the delivery. domain.verification.failed: post: operationId: webhookDomainVerificationFailed summary: Custom email domain check failed description: | An automatic check of your custom email domain's DNS records ran into an error. Records that are simply not in place yet do not fire it. Checks keep running, so this can fire again on a later attempt. tags: [Webhooks] requestBody: required: true content: application/json: schema: allOf: - $ref: '#/components/schemas/WebhookEnvelope' - type: object properties: data: $ref: '#/components/schemas/DomainVerificationFailedEventData' examples: failed: $ref: './examples/webhook.domain-verification-failed.json' responses: '200': description: Any 2xx acknowledges the delivery. domain.email.bounce: post: operationId: webhookDomainEmailBounce summary: Email from your domain bounced description: | An email sent from one of your custom sending domains bounced without being a permanent failure (`bounce_type` `Transient` or `Undetermined`). Permanent bounces send `domain.email.bounce_hard` instead. Sent once per bounced recipient. Mail sent from a domain that is not one of your team's sending domains never sends it. `email_id` and `contact_id` are set when the bounced message was sent from Drop Cowboy. tags: [Webhooks] requestBody: required: true content: application/json: schema: allOf: - $ref: '#/components/schemas/WebhookEnvelope' - type: object properties: data: $ref: '#/components/schemas/DomainEmailBounceData' examples: transient: $ref: './examples/webhook.domain-email-bounce.json' responses: '200': description: Any 2xx acknowledges the delivery. domain.email.bounce_hard: post: operationId: webhookDomainEmailBounceHard summary: Email from your domain bounced permanently description: | An email sent from one of your custom sending domains bounced permanently (`bounce_type: Permanent`), usually because the address does not exist. Same `data` as `domain.email.bounce`, sent once per bounced recipient, and only for your team's own sending domains. tags: [Webhooks] requestBody: required: true content: application/json: schema: allOf: - $ref: '#/components/schemas/WebhookEnvelope' - type: object properties: data: $ref: '#/components/schemas/DomainEmailBounceData' examples: permanent: $ref: './examples/webhook.domain-email-bounce-hard.json' responses: '200': description: Any 2xx acknowledges the delivery. domain.email.complaint: post: operationId: webhookDomainEmailComplaint summary: Email from your domain marked as spam description: | A recipient marked an email sent from one of your custom sending domains as spam. Sent once per complaining recipient, and only for your team's own sending domains. Same `email_id` and `contact_id` rules as `domain.email.bounce`. tags: [Webhooks] requestBody: required: true content: application/json: schema: allOf: - $ref: '#/components/schemas/WebhookEnvelope' - type: object properties: data: $ref: '#/components/schemas/DomainEmailComplaintData' examples: abuse: $ref: './examples/webhook.domain-email-complaint.json' responses: '200': description: Any 2xx acknowledges the delivery. task.opened: post: operationId: webhookTaskOpened summary: Task opened description: | A new task was created, by a teammate, through the API, or by an inbound call, message or form that opens one. Reopening a closed task does not fire it. Tasks opened by an inbound email reply do not fire it either. tags: [Webhooks] requestBody: required: true content: application/json: schema: allOf: - $ref: '#/components/schemas/WebhookEnvelope' - type: object properties: data: $ref: '#/components/schemas/TaskEventData' examples: opened: $ref: './examples/webhook.task-opened.json' responses: '200': description: Any 2xx acknowledges the delivery. task.assigned: post: operationId: webhookTaskAssigned summary: Task assigned description: | A task was assigned to a teammate by updating it. It fires on every update that names an assignee, even an unchanged one. Unassigning does not fire it, and neither does setting the assignee when the task is created. tags: [Webhooks] requestBody: required: true content: application/json: schema: allOf: - $ref: '#/components/schemas/WebhookEnvelope' - type: object properties: data: $ref: '#/components/schemas/TaskAssignedEventData' examples: assigned: $ref: './examples/webhook.task-assigned.json' responses: '200': description: Any 2xx acknowledges the delivery. task.closed: post: operationId: webhookTaskClosed summary: Task closed description: | A task was closed by changing its status, one at a time or in bulk, or because the appointment booking it tracked was closed out. tags: [Webhooks] requestBody: required: true content: application/json: schema: allOf: - $ref: '#/components/schemas/WebhookEnvelope' - type: object properties: data: $ref: '#/components/schemas/TaskEventData' examples: closed: $ref: './examples/webhook.task-closed.json' responses: '200': description: Any 2xx acknowledges the delivery. contact.call.ringing: post: operationId: webhookContactCallRinging summary: Call ringing description: | Fires when the power or predictive dialer starts ringing a contact, and when an inbound call reaches a phone-line Forward step and the forwarded number starts ringing. Inbound calls carry `ivr_id`; dialer calls do not. tags: [Webhooks] requestBody: required: true content: application/json: schema: allOf: - $ref: '#/components/schemas/WebhookEnvelope' - type: object properties: data: $ref: '#/components/schemas/CallEventData' examples: dialer: $ref: './examples/webhook.contact-call-ringing.json' responses: '200': description: Any 2xx acknowledges the delivery. contact.call.answered: post: operationId: webhookContactCallAnswered summary: Call answered description: | Fires when a contact picks up a dialer call, and when an inbound call is answered at a phone-line Forward step. Inbound calls answered by a queue, hunt group or AI agent do not fire it. `call_direction` is always present. tags: [Webhooks] requestBody: required: true content: application/json: schema: allOf: - $ref: '#/components/schemas/WebhookEnvelope' - type: object properties: data: $ref: '#/components/schemas/CallEventData' examples: dialer: $ref: './examples/webhook.contact-call-answered.json' responses: '200': description: Any 2xx acknowledges the delivery. contact.call.missed: post: operationId: webhookContactCallMissed summary: Inbound call missed description: | Fires when an inbound call to a phone line ends without being answered, right after `contact.call.hangup`. It also fires when a Forward step's outbound leg ends unanswered, so one inbound call can produce more than one of these. Dialer calls never fire it. tags: [Webhooks] requestBody: required: true content: application/json: schema: allOf: - $ref: '#/components/schemas/WebhookEnvelope' - type: object properties: data: $ref: '#/components/schemas/CallEventData' examples: inbound: $ref: './examples/webhook.contact-call-missed.json' responses: '200': description: Any 2xx acknowledges the delivery. contact.call.hangup: post: operationId: webhookContactCallHangup summary: Call ended description: | Fires when a dialer call or an inbound phone-line call ends, including a dialer call that could not be placed (`hangup_cause` `NORMAL_TEMPORARY_FAILURE`). On dialer calls it is followed by `contact.call.disposition`. `contact.call.hungup` is not sent; this is the only hangup event. tags: [Webhooks] requestBody: required: true content: application/json: schema: allOf: - $ref: '#/components/schemas/WebhookEnvelope' - type: object properties: data: $ref: '#/components/schemas/CallHangupData' examples: dialer: $ref: './examples/webhook.contact-call-hangup.json' responses: '200': description: Any 2xx acknowledges the delivery. contact.call.abandoned: post: operationId: webhookContactCallAbandoned summary: Dialer call abandoned description: | Fires when the predictive dialer connects a contact but no agent is free to take the call, so the dialer drops it. It is followed by `contact.call.disposition` with disposition `abandoned`. tags: [Webhooks] requestBody: required: true content: application/json: schema: allOf: - $ref: '#/components/schemas/WebhookEnvelope' - type: object properties: data: $ref: '#/components/schemas/CallAbandonedData' examples: no_agent: $ref: './examples/webhook.contact-call-abandoned.json' responses: '200': description: Any 2xx acknowledges the delivery. contact.call.queued: post: operationId: webhookContactCallQueued summary: Inbound call queued description: | Fires when an inbound phone-line call is placed in a call queue to wait for an agent. Unlike the other call events it reports direction as `direction`, not `call_direction`. tags: [Webhooks] requestBody: required: true content: application/json: schema: allOf: - $ref: '#/components/schemas/WebhookEnvelope' - type: object properties: data: $ref: '#/components/schemas/CallQueuedData' examples: inbound: $ref: './examples/webhook.contact-call-queued.json' responses: '200': description: Any 2xx acknowledges the delivery. contact.call.disposition: post: operationId: webhookContactCallDisposition summary: Dialer call dispositioned description: | Fires once for each disposition a dialer call gets: when the agent picks one, or when the dialer sets one itself (`abandoned`, `voicemail`). A call that ends without a disposition gets one from how it ended (`no_answer`, `busy`, `rejected`, `network_blocked`, `no_service` or `not_reachable`) and fires once at hangup. A call that hangs up normally with no disposition yet fires nothing at hangup; it fires when the agent picks a disposition. Inbound phone-line calls do not fire it. Editing a past call's disposition sends `contact.call.disposition.changed` instead. tags: [Webhooks] requestBody: required: true content: application/json: schema: allOf: - $ref: '#/components/schemas/WebhookEnvelope' - type: object properties: data: $ref: '#/components/schemas/CallDispositionData' examples: agent: $ref: './examples/webhook.contact-call-disposition.json' responses: '200': description: Any 2xx acknowledges the delivery. contact.call.disposition.changed: post: operationId: webhookContactCallDispositionChanged summary: Call disposition edited description: | Fires when a teammate changes the disposition of a past call from its timeline entry in the app. Same `data` shape as `contact.call.disposition`. `contact_id`, `list_id`, `call_direction` and `call_duration` are the call's own values. tags: [Webhooks] requestBody: required: true content: application/json: schema: allOf: - $ref: '#/components/schemas/WebhookEnvelope' - type: object properties: data: $ref: '#/components/schemas/CallDispositionData' examples: edited: $ref: './examples/webhook.contact-call-disposition-changed.json' responses: '200': description: Any 2xx acknowledges the delivery. contact.call.recording.available: post: operationId: webhookContactCallRecordingAvailable summary: Call recording saved description: | Fires when a call recording or voicemail recording is saved, with the transcript of the contact's side in `preview`. A call recorded in several sessions or transcript parts fires once per part. Fetch the audio with `call_recording_id`. tags: [Webhooks] requestBody: required: true content: application/json: schema: allOf: - $ref: '#/components/schemas/WebhookEnvelope' - type: object properties: data: $ref: '#/components/schemas/CallRecordingAvailableData' examples: call: $ref: './examples/webhook.contact-call-recording-available.json' responses: '200': description: Any 2xx acknowledges the delivery. contact.call.read: post: operationId: webhookContactCallRead summary: Call marked read description: | Fires when a teammate marks a call read (hides it from the unread inbox) in the app. It is sent each time a call is marked read, even if it was already read. tags: [Webhooks] requestBody: required: true content: application/json: schema: allOf: - $ref: '#/components/schemas/WebhookEnvelope' - type: object properties: data: $ref: '#/components/schemas/CallReadData' examples: read: $ref: './examples/webhook.contact-call-read.json' responses: '200': description: Any 2xx acknowledges the delivery. contact.call.unread: post: operationId: webhookContactCallUnread summary: Call marked unread description: | Fires when a teammate marks a call unread in the app. It is sent each time a call is marked unread, even if it was already unread. Other edits to a call, such as notes or state, send neither `contact.call.read` nor `contact.call.unread`. tags: [Webhooks] requestBody: required: true content: application/json: schema: allOf: - $ref: '#/components/schemas/WebhookEnvelope' - type: object properties: data: $ref: '#/components/schemas/CallReadData' examples: unread: $ref: './examples/webhook.contact-call-unread.json' responses: '200': description: Any 2xx acknowledges the delivery. ai_agent.call.started: post: operationId: webhookAiAgentCallStarted summary: AI agent call started description: | Fires when an AI agent is connected to an inbound phone-line call, or when an AI voice broadcast places a call (from a campaign or `POST /ai-broadcast`). The two directions carry different fields: inbound has `call_id`, `from`, `to` and `ivr_id`; outbound has `campaign_id`, `session_id`, `drop_id` and `phone_number`. tags: [Webhooks] requestBody: required: true content: application/json: schema: allOf: - $ref: '#/components/schemas/WebhookEnvelope' - type: object properties: data: $ref: '#/components/schemas/AiAgentCallStartedData' examples: inbound: $ref: './examples/webhook.ai-agent-call-started.json' outbound: $ref: './examples/webhook.ai-agent-call-started-outbound.json' responses: '200': description: Any 2xx acknowledges the delivery. ai_agent.call.failed: post: operationId: webhookAiAgentCallFailed summary: AI agent call did not connect description: | Fires instead of `ai_agent.call.completed` when an inbound AI agent call ended unanswered, or an AI voice broadcast call failed (no answer, busy, carrier intercept, full voicemail and similar; the reason is in `outcome`). tags: [Webhooks] requestBody: required: true content: application/json: schema: allOf: - $ref: '#/components/schemas/WebhookEnvelope' - type: object properties: data: $ref: '#/components/schemas/AiAgentCallData' examples: outbound: $ref: './examples/webhook.ai-agent-call-failed.json' responses: '200': description: Any 2xx acknowledges the delivery. ai_agent.outcome.confirm: post: operationId: webhookAiAgentOutcomeConfirm summary: AI agent call outcome confirm description: | Fires instead of `ai_agent.call.completed` when an AI agent call ends with outcome `confirm`: the contact confirmed what the agent asked for. tags: [Webhooks] requestBody: required: true content: application/json: schema: allOf: - $ref: '#/components/schemas/WebhookEnvelope' - type: object properties: data: $ref: '#/components/schemas/AiAgentCallData' examples: outbound: $ref: './examples/webhook.ai-agent-outcome-confirm.json' responses: '200': description: Any 2xx acknowledges the delivery. ai_agent.outcome.opt_out: post: operationId: webhookAiAgentOutcomeOptOut summary: AI agent call outcome opt-out description: | Fires instead of `ai_agent.call.completed` when an AI agent call ends with outcome `opt_out`: the contact asked not to be called again. On AI voice broadcasts the attached `campaign_session.dispo.dnc` is `true`. tags: [Webhooks] requestBody: required: true content: application/json: schema: allOf: - $ref: '#/components/schemas/WebhookEnvelope' - type: object properties: data: $ref: '#/components/schemas/AiAgentCallData' examples: outbound: $ref: './examples/webhook.ai-agent-outcome-opt-out.json' responses: '200': description: Any 2xx acknowledges the delivery. ai_agent.outcome.transfer: post: operationId: webhookAiAgentOutcomeTransfer summary: AI agent call outcome transfer description: | Fires instead of `ai_agent.call.completed` when an AI agent call ends with outcome `transfer`: the agent transferred the contact to a person. On AI voice broadcasts it fires at the transfer, while the transferred call may still be live. tags: [Webhooks] requestBody: required: true content: application/json: schema: allOf: - $ref: '#/components/schemas/WebhookEnvelope' - type: object properties: data: $ref: '#/components/schemas/AiAgentCallData' examples: outbound: $ref: './examples/webhook.ai-agent-outcome-transfer.json' responses: '200': description: Any 2xx acknowledges the delivery. ai_agent.outcome.voicemail: post: operationId: webhookAiAgentOutcomeVoicemail summary: AI agent call outcome voicemail description: | Fires instead of `ai_agent.call.completed` when an AI agent call ends with outcome `voicemail`: the agent reached voicemail. An AI voice broadcast call whose outcome is `voicemail_delivered` fires `ai_agent.call.completed` instead. tags: [Webhooks] requestBody: required: true content: application/json: schema: allOf: - $ref: '#/components/schemas/WebhookEnvelope' - type: object properties: data: $ref: '#/components/schemas/AiAgentCallData' examples: outbound: $ref: './examples/webhook.ai-agent-outcome-voicemail.json' responses: '200': description: Any 2xx acknowledges the delivery. ai_agent.outcome.incomplete: post: operationId: webhookAiAgentOutcomeIncomplete summary: AI agent call outcome incomplete description: | Fires instead of `ai_agent.call.completed` when an AI agent call ends with outcome `incomplete`: the conversation started but the agent did not reach its goal. tags: [Webhooks] requestBody: required: true content: application/json: schema: allOf: - $ref: '#/components/schemas/WebhookEnvelope' - type: object properties: data: $ref: '#/components/schemas/AiAgentCallData' examples: outbound: $ref: './examples/webhook.ai-agent-outcome-incomplete.json' responses: '200': description: Any 2xx acknowledges the delivery. ai_agent.outcome.timeout: post: operationId: webhookAiAgentOutcomeTimeout summary: AI agent call outcome timeout description: | Fires instead of `ai_agent.call.completed` when an AI agent call ends with outcome `timeout`: the conversation hit its time limit before the agent reached its goal. tags: [Webhooks] requestBody: required: true content: application/json: schema: allOf: - $ref: '#/components/schemas/WebhookEnvelope' - type: object properties: data: $ref: '#/components/schemas/AiAgentCallData' examples: outbound: $ref: './examples/webhook.ai-agent-outcome-timeout.json' responses: '200': description: Any 2xx acknowledges the delivery. ai_agent.outcome.identity_failed: post: operationId: webhookAiAgentOutcomeIdentityFailed summary: AI agent call outcome identity failed description: | Fires instead of `ai_agent.call.completed` when an AI agent call ends with outcome `identity_failed`: the contact could not verify their identity, so the agent did not continue. On AI voice broadcasts `identity_verified` is `false`. tags: [Webhooks] requestBody: required: true content: application/json: schema: allOf: - $ref: '#/components/schemas/WebhookEnvelope' - type: object properties: data: $ref: '#/components/schemas/AiAgentCallData' examples: outbound: $ref: './examples/webhook.ai-agent-outcome-identity-failed.json' responses: '200': description: Any 2xx acknowledges the delivery. contact.created: post: operationId: webhookContactCreated summary: Contact created description: | One or more contacts were created. A contact created on its own (in the app, by a web form, or by an inbound call, text or email from a new number or address) sends its id as a single string in `contact_ids`. Contacts created together by `POST /contact/public/contacts` send every new id in a `contact_ids` array. A CSV import sends one event for the whole import, with up to 25 ids, `total_count` and `operation`; if the import was set to fire webhooks for each contact, it sends one event per contact with `contact_id` instead. Use `source` to tell these apart. tags: [Webhooks] requestBody: required: true content: application/json: schema: allOf: - $ref: '#/components/schemas/WebhookEnvelope' - type: object properties: data: $ref: '#/components/schemas/ContactCreatedData' examples: manual: $ref: './examples/webhook.contact-created.json' api: $ref: './examples/webhook.contact-created-api.json' import: $ref: './examples/webhook.contact-created-import.json' responses: '200': description: Any 2xx acknowledges the delivery. contact.updated: post: operationId: webhookContactUpdated summary: Contact updated description: | A contact's fields changed. An edit in the app or through `PUT /contact/public/contacts/{id}`, an owner change or a disposition change sends `contact_id` with the updated `contact`. Creating a contact that matched an existing one updates that contact instead and sends its id in `contact_ids`. A CSV import that updated existing contacts sends one event for the whole import, with up to 25 ids, `total_count` and `operation`. tags: [Webhooks] requestBody: required: true content: application/json: schema: allOf: - $ref: '#/components/schemas/WebhookEnvelope' - type: object properties: data: $ref: '#/components/schemas/ContactUpdatedData' examples: edited: $ref: './examples/webhook.contact-updated.json' import: $ref: './examples/webhook.contact-updated-import.json' responses: '200': description: Any 2xx acknowledges the delivery. contact.deleted: post: operationId: webhookContactDeleted summary: Contact deleted description: | A contact was deleted, in the app or through `DELETE /contact/public/contacts/{id}`. `contact` still carries the record as it was. Deleting a list together with its contacts does not send this event. tags: [Webhooks] requestBody: required: true content: application/json: schema: allOf: - $ref: '#/components/schemas/WebhookEnvelope' - type: object properties: data: $ref: '#/components/schemas/ContactDeletedData' examples: deleted: $ref: './examples/webhook.contact-deleted.json' responses: '200': description: Any 2xx acknowledges the delivery. contact.assigned: post: operationId: webhookContactAssigned summary: Contact owner changed description: | A contact was given a new owner, in the app or through `PUT /contact/public/contacts/{id}/owner`. `owner` and `previous_owner` are user ids. `contact.updated` is sent for the same change. tags: [Webhooks] requestBody: required: true content: application/json: schema: allOf: - $ref: '#/components/schemas/WebhookEnvelope' - type: object properties: data: $ref: '#/components/schemas/ContactAssignedData' examples: assigned: $ref: './examples/webhook.contact-assigned.json' responses: '200': description: Any 2xx acknowledges the delivery. contact.disposition: post: operationId: webhookContactDisposition summary: Contact disposition set description: | A contact was given a disposition, in the app or through `PUT /contact/public/contacts/{id}/disposition`. `disposition_id` identifies it; list the team's dispositions with `GET /automation/public/dispositions`. It is sent after the contact is saved, and only when a disposition is set: edits to a contact's other fields never send it. tags: [Webhooks] requestBody: required: true content: application/json: schema: allOf: - $ref: '#/components/schemas/WebhookEnvelope' - type: object properties: data: $ref: '#/components/schemas/ContactDispositionData' examples: disposition: $ref: './examples/webhook.contact-disposition.json' responses: '200': description: Any 2xx acknowledges the delivery. contact.forgotten: post: operationId: webhookContactForgotten summary: Contact forgotten description: | A contact was removed in response to a right-to-be-forgotten request. The contact no longer appears in the app, but its record is kept for compliance, so `contact` is still attached. `reason` is the reason given with the request. tags: [Webhooks] requestBody: required: true content: application/json: schema: allOf: - $ref: '#/components/schemas/WebhookEnvelope' - type: object properties: data: $ref: '#/components/schemas/ContactForgottenData' examples: forgotten: $ref: './examples/webhook.contact-forgotten.json' responses: '200': description: Any 2xx acknowledges the delivery. contact.data.deleted: post: operationId: webhookContactDataDeleted summary: Contact tracking data deleted description: | A contact's website tracking data was deleted on request: their recorded web visits were removed and their web tracking consent was revoked. Messages, calls and documents are not affected. `events_deleted` is the number of web visits removed. tags: [Webhooks] requestBody: required: true content: application/json: schema: allOf: - $ref: '#/components/schemas/WebhookEnvelope' - type: object properties: data: $ref: '#/components/schemas/ContactDataDeletedData' examples: deleted: $ref: './examples/webhook.contact-data-deleted.json' responses: '200': description: Any 2xx acknowledges the delivery. contact.consent.granted: post: operationId: webhookContactConsentGranted summary: Consent granted description: | A consent record was saved that grants consent: from the app, from `POST /contact/public/contacts/{id}/consent`, or from a web form submission. Opt-out consent types (`tcpa_optout`, `sms_optout`, `email_optout`) saved through the API do not send this event or `contact.consent.revoked`. tags: [Webhooks] requestBody: required: true content: application/json: schema: allOf: - $ref: '#/components/schemas/WebhookEnvelope' - type: object properties: data: $ref: '#/components/schemas/ContactConsentGrantedData' examples: granted: $ref: './examples/webhook.contact-consent-granted.json' responses: '200': description: Any 2xx acknowledges the delivery. contact.consent.revoked: post: operationId: webhookContactConsentRevoked summary: Consent revoked description: | A contact's consent was revoked: through `PUT /contact/public/contacts/{id}/consent/{cid}/revoke`, in the app, by an email unsubscribe, or by a web form that records an opt-out. A revoke creates a new opt-out record, whose id is `consent_id`; `original_consent_id` is the consent that was revoked. Revoking phone consent also adds the contact's numbers to your Do Not Call list. tags: [Webhooks] requestBody: required: true content: application/json: schema: allOf: - $ref: '#/components/schemas/WebhookEnvelope' - type: object properties: data: $ref: '#/components/schemas/ContactConsentRevokedData' examples: revoked: $ref: './examples/webhook.contact-consent-revoked.json' responses: '200': description: Any 2xx acknowledges the delivery. contact.document.attached: post: operationId: webhookContactDocumentAttached summary: Document attached to a contact description: | A document was uploaded for a contact, in the app or with `POST /document/public/documents` or `POST /document/public/documents/from-url` with a `contact_id`. Inbound faxes saved to a contact also send it. Documents uploaded without a contact do not. tags: [Webhooks] requestBody: required: true content: application/json: schema: allOf: - $ref: '#/components/schemas/WebhookEnvelope' - type: object properties: data: $ref: '#/components/schemas/ContactDocumentData' examples: attached: $ref: './examples/webhook.contact-document-attached.json' responses: '200': description: Any 2xx acknowledges the delivery. contact.document.deleted: post: operationId: webhookContactDocumentDeleted summary: Contact document deleted description: | A document was deleted from a contact's record in the app. `document` still carries the record as it was. Deleting a document with `DELETE /document/public/documents/{id}` does not send this event. tags: [Webhooks] requestBody: required: true content: application/json: schema: allOf: - $ref: '#/components/schemas/WebhookEnvelope' - type: object properties: data: $ref: '#/components/schemas/ContactDocumentData' examples: deleted: $ref: './examples/webhook.contact-document-deleted.json' responses: '200': description: Any 2xx acknowledges the delivery. contact.document.status: post: operationId: webhookContactDocumentStatus summary: Contact document status changed description: | A contact's document was moved to a different status in the app. The new status is not included; read the document with `GET /document/public/documents/{id}`. tags: [Webhooks] requestBody: required: true content: application/json: schema: allOf: - $ref: '#/components/schemas/WebhookEnvelope' - type: object properties: data: $ref: '#/components/schemas/ContactDocumentData' examples: status: $ref: './examples/webhook.contact-document-status.json' responses: '200': description: Any 2xx acknowledges the delivery. contact.note.created: post: operationId: webhookContactNoteCreated summary: Note added to a contact description: | A note was added to a contact, in the app, by an AI agent, or through `POST /contact/public/contacts/{id}/notes`. `note.note` is the note as sanitized HTML. tags: [Webhooks] requestBody: required: true content: application/json: schema: allOf: - $ref: '#/components/schemas/WebhookEnvelope' - type: object properties: data: $ref: '#/components/schemas/ContactNoteData' examples: created: $ref: './examples/webhook.contact-note-created.json' responses: '200': description: Any 2xx acknowledges the delivery. contact.note.updated: post: operationId: webhookContactNoteUpdated summary: Contact note edited description: A note on a contact was edited in the app. `note` carries the edited text. tags: [Webhooks] requestBody: required: true content: application/json: schema: allOf: - $ref: '#/components/schemas/WebhookEnvelope' - type: object properties: data: $ref: '#/components/schemas/ContactNoteData' examples: updated: $ref: './examples/webhook.contact-note-updated.json' responses: '200': description: Any 2xx acknowledges the delivery. contact.note.deleted: post: operationId: webhookContactNoteDeleted summary: Contact note deleted description: | A note on a contact was deleted in the app. The note is already gone when the event is sent, so `note` is `{}`. tags: [Webhooks] requestBody: required: true content: application/json: schema: allOf: - $ref: '#/components/schemas/WebhookEnvelope' - type: object properties: data: $ref: '#/components/schemas/ContactNoteData' examples: deleted: $ref: './examples/webhook.contact-note-deleted.json' responses: '200': description: Any 2xx acknowledges the delivery. contact.reminder.created: post: operationId: webhookContactReminderCreated summary: Follow-up reminder created description: | A follow-up reminder was scheduled for a contact, in the app or through `POST /contact/public/contacts/{id}/follow-ups`. Only `followup_id` identifies the reminder; read it with `GET /contact/public/contacts/{id}/follow-ups`. tags: [Webhooks] requestBody: required: true content: application/json: schema: allOf: - $ref: '#/components/schemas/WebhookEnvelope' - type: object properties: data: $ref: '#/components/schemas/ContactReminderData' examples: created: $ref: './examples/webhook.contact-reminder-created.json' responses: '200': description: Any 2xx acknowledges the delivery. contact.reminder.updated: post: operationId: webhookContactReminderUpdated summary: Follow-up reminder changed description: | A pending follow-up reminder was rescheduled, reassigned or reworded, in the app or through `PUT /contact/public/follow-ups/{id}`. tags: [Webhooks] requestBody: required: true content: application/json: schema: allOf: - $ref: '#/components/schemas/WebhookEnvelope' - type: object properties: data: $ref: '#/components/schemas/ContactReminderData' examples: updated: $ref: './examples/webhook.contact-reminder-updated.json' responses: '200': description: Any 2xx acknowledges the delivery. contact.reminder.deleted: post: operationId: webhookContactReminderDeleted summary: Follow-up reminder cancelled description: | A pending follow-up reminder was cancelled, in the app or through `DELETE /contact/public/follow-ups/{id}`. tags: [Webhooks] requestBody: required: true content: application/json: schema: allOf: - $ref: '#/components/schemas/WebhookEnvelope' - type: object properties: data: $ref: '#/components/schemas/ContactReminderData' examples: cancelled: $ref: './examples/webhook.contact-reminder-deleted.json' responses: '200': description: Any 2xx acknowledges the delivery. contact.reminder.complete: post: operationId: webhookContactReminderComplete summary: Follow-up reminder completed description: A follow-up reminder was marked complete in the app. tags: [Webhooks] requestBody: required: true content: application/json: schema: allOf: - $ref: '#/components/schemas/WebhookEnvelope' - type: object properties: data: $ref: '#/components/schemas/ContactReminderData' examples: completed: $ref: './examples/webhook.contact-reminder-complete.json' responses: '200': description: Any 2xx acknowledges the delivery. contact.tag.added: post: operationId: webhookContactTagAdded summary: Tag added to a contact description: | A tag was added to a contact, in the app or through `POST /contact/public/contacts/{id}/tags/{tag_id}`. The tag's label and colors are in `contact.tags`. tags: [Webhooks] requestBody: required: true content: application/json: schema: allOf: - $ref: '#/components/schemas/WebhookEnvelope' - type: object properties: data: $ref: '#/components/schemas/ContactTagData' examples: added: $ref: './examples/webhook.contact-tag-added.json' responses: '200': description: Any 2xx acknowledges the delivery. contact.tag.removed: post: operationId: webhookContactTagRemoved summary: Tag removed from a contact description: | A tag was removed from a contact, in the app or through `DELETE /contact/public/contacts/{id}/tags/{tag_id}`. Look the tag up with `GET /tag/public/tags`; it is no longer in `contact.tags`. tags: [Webhooks] requestBody: required: true content: application/json: schema: allOf: - $ref: '#/components/schemas/WebhookEnvelope' - type: object properties: data: $ref: '#/components/schemas/ContactTagData' examples: removed: $ref: './examples/webhook.contact-tag-removed.json' responses: '200': description: Any 2xx acknowledges the delivery. contact.export.complete: post: operationId: webhookContactExportComplete summary: Export finished description: | An export requested in the app finished and its file is ready. Despite the name it is sent for every export type (contacts, calls, texts, campaign reports, Do Not Call lists and commissions); `export.export_type` says which. Failed exports send nothing. tags: [Webhooks] requestBody: required: true content: application/json: schema: allOf: - $ref: '#/components/schemas/WebhookEnvelope' - type: object properties: data: $ref: '#/components/schemas/ContactExportCompleteData' examples: complete: $ref: './examples/webhook.contact-export-complete.json' responses: '200': description: Any 2xx acknowledges the delivery. contact.import.complete: post: operationId: webhookContactImportComplete summary: Import finished description: | A CSV import finished successfully. It is sent for contact imports and Do Not Call imports alike; `import.type` says which. The envelope's `operation_id` equals `import_id`, and so does the `operation_id` of every `contact.created`, `contact.updated` and `contact.list.added` event from the same import. Failed imports send nothing. tags: [Webhooks] requestBody: required: true content: application/json: schema: allOf: - $ref: '#/components/schemas/WebhookEnvelope' - type: object properties: data: $ref: '#/components/schemas/ContactImportCompleteData' examples: complete: $ref: './examples/webhook.contact-import-complete.json' responses: '200': description: Any 2xx acknowledges the delivery. contact.list.added: post: operationId: webhookContactListAdded summary: Contacts added to a list description: | Contacts were added to a list. Adding contacts in the app, with `POST /contact/public/contacts/{id}/lists` or `POST /contact/public/contacts/{id}/lists/move`, or by import or list copy sends one event per list with up to 25 ids in `contact_ids`, `total_count` and paging fields. A list copy or move sends no ids, only `total_count`. Imports and list copies set to fire webhooks for each contact send one event per contact with `contact_id` instead. `POST /contact/public/contacts` with `add_list_ids` sends this event only when the request sets `fire_webhook_events`. tags: [Webhooks] requestBody: required: true content: application/json: schema: allOf: - $ref: '#/components/schemas/WebhookEnvelope' - type: object properties: data: $ref: '#/components/schemas/ContactListMembershipData' examples: bulk: $ref: './examples/webhook.contact-list-added.json' perContact: $ref: './examples/webhook.contact-list-added-per-contact.json' listCopy: $ref: './examples/webhook.contact-list-added-copy.json' responses: '200': description: Any 2xx acknowledges the delivery. contact.list.removed: post: operationId: webhookContactListRemoved summary: Contacts removed from a list description: | Contacts were removed from a list, in the app, with `DELETE /contact/public/contacts/{id}/lists/{list_id}`, or by moving them to another list (`POST /contact/public/contacts/{id}/lists/move` or a list move). A move also sends `contact.list.added` for the destination list with the same `operation_id`. Same shape as `contact.list.added`. tags: [Webhooks] requestBody: required: true content: application/json: schema: allOf: - $ref: '#/components/schemas/WebhookEnvelope' - type: object properties: data: $ref: '#/components/schemas/ContactListMembershipData' examples: removed: $ref: './examples/webhook.contact-list-removed.json' responses: '200': description: Any 2xx acknowledges the delivery. contact.timeline.entry: post: operationId: webhookContactTimelineEntry summary: Connected-account voicemail logged description: | Sent only for ringless voicemails sent through a connected Drop Cowboy account integration: once when the voicemail is logged on the contact's timeline, and again when its outcome changes. `timeline_entry.disposition` is `pending` and then `success` or `failure`; a repeated report of the same outcome sends nothing. Other timeline activity does not send this event. tags: [Webhooks] requestBody: required: true content: application/json: schema: allOf: - $ref: '#/components/schemas/WebhookEnvelope' - type: object properties: data: $ref: '#/components/schemas/ContactTimelineEntryData' examples: entry: $ref: './examples/webhook.contact-timeline-entry.json' responses: '200': description: Any 2xx acknowledges the delivery. list.created: post: operationId: webhookListCreated summary: List created description: | A contact list was created, in the app or through `POST /contact/public/lists`. Pipeline stages added with `POST /boards/public/boards/{board_id}/lists` do not send it. tags: [Webhooks] requestBody: required: true content: application/json: schema: allOf: - $ref: '#/components/schemas/WebhookEnvelope' - type: object properties: data: $ref: '#/components/schemas/ListChangeData' examples: created: $ref: './examples/webhook.list-created.json' responses: '200': description: Any 2xx acknowledges the delivery. list.updated: post: operationId: webhookListUpdated summary: List changed description: | A list's settings changed, in the app or through `PUT /contact/public/lists/{id}`. Any edit sends it, including turning `callable` on or off. Adding or removing contacts does not; see `contact.list.added`. tags: [Webhooks] requestBody: required: true content: application/json: schema: allOf: - $ref: '#/components/schemas/WebhookEnvelope' - type: object properties: data: $ref: '#/components/schemas/ListChangeData' examples: updated: $ref: './examples/webhook.list-updated.json' responses: '200': description: Any 2xx acknowledges the delivery. list.deleted: post: operationId: webhookListDeleted summary: List deleted description: | A list was deleted, in the app or through `DELETE /contact/public/lists/{id}`. `list` still carries the record as it was. Contacts deleted along with the list do not each send `contact.deleted`. tags: [Webhooks] requestBody: required: true content: application/json: schema: allOf: - $ref: '#/components/schemas/WebhookEnvelope' - type: object properties: data: $ref: '#/components/schemas/ListChangeData' examples: deleted: $ref: './examples/webhook.list-deleted.json' responses: '200': description: Any 2xx acknowledges the delivery. contact.pipeline.stage.entered: post: operationId: webhookPipelineStageEntered summary: Contacts entered a pipeline stage description: | Contacts were added to a list that is a stage of a pipeline board: in the app (including creating a contact directly in a stage), with `POST /contact/public/contacts/{id}/lists` or `POST /contact/public/contacts/{id}/lists/move`. One event per change, with up to 25 ids and the same paging fields as `contact.list.added`. Imports, list copies and moves, web forms, and `POST /contact/public/contacts` with `add_list_ids` do not send it. tags: [Webhooks] requestBody: required: true content: application/json: schema: allOf: - $ref: '#/components/schemas/WebhookEnvelope' - type: object properties: data: $ref: '#/components/schemas/PipelineStageMoveData' examples: entered: $ref: './examples/webhook.contact-pipeline-stage-entered.json' responses: '200': description: Any 2xx acknowledges the delivery. contact.pipeline.stage.exited: post: operationId: webhookPipelineStageExited summary: Contacts left a pipeline stage description: | Contacts were removed from a pipeline stage, or moved from it to another list, in the app, with `DELETE /contact/public/contacts/{id}/lists/{list_id}` or `POST /contact/public/contacts/{id}/lists/move`. Same shape and limits as `contact.pipeline.stage.entered`. tags: [Webhooks] requestBody: required: true content: application/json: schema: allOf: - $ref: '#/components/schemas/WebhookEnvelope' - type: object properties: data: $ref: '#/components/schemas/PipelineStageMoveData' examples: exited: $ref: './examples/webhook.contact-pipeline-stage-exited.json' responses: '200': description: Any 2xx acknowledges the delivery. contact.pipeline.stage.aged: post: operationId: webhookPipelineStageAged summary: Contact aged in a pipeline stage description: | A contact has been in a pipeline stage for that stage's configured number of days. Sent once per contact per stage entry, by a scan that runs every minute. Only stages with an aging threshold send it. tags: [Webhooks] requestBody: required: true content: application/json: schema: allOf: - $ref: '#/components/schemas/WebhookEnvelope' - type: object properties: data: $ref: '#/components/schemas/PipelineStageAgedData' examples: aged: $ref: './examples/webhook.contact-pipeline-stage-aged.json' responses: '200': description: Any 2xx acknowledges the delivery. contact.inactivity.threshold: post: operationId: webhookContactInactivityThreshold summary: Contact idle in a pipeline stage description: | A contact has stayed in the same pipeline stage for the inactivity period. It measures time since the contact entered the stage, not since its last call or message. Sent once per contact per stage entry. tags: [Webhooks] requestBody: required: true content: application/json: schema: allOf: - $ref: '#/components/schemas/WebhookEnvelope' - type: object properties: data: $ref: '#/components/schemas/ContactInactivityData' examples: inactive: $ref: './examples/webhook.contact-inactivity-threshold.json' responses: '200': description: Any 2xx acknowledges the delivery. contact.pipeline.stage.duration_reached: post: operationId: webhookPipelineStageDurationReached summary: Contact reached a time in list description: | A contact has been in a list for a duration that an automation's "Contact: Time in List" trigger watches. It is sent only while at least one such trigger is enabled for that list and duration, and once per contact per list entry. Works for any list, not only pipeline stages. tags: [Webhooks] requestBody: required: true content: application/json: schema: allOf: - $ref: '#/components/schemas/WebhookEnvelope' - type: object properties: data: $ref: '#/components/schemas/PipelineStageDurationData' examples: reached: $ref: './examples/webhook.contact-pipeline-stage-duration-reached.json' responses: '200': description: Any 2xx acknowledges the delivery. appointment.booked: post: operationId: webhookAppointmentBooked summary: Appointment booked description: | Someone booked an appointment, from the dashboard, a booking page or embed, an AI agent, an automation, or the API. For a group class it fires once for each party that joins an occurrence, so the same `booking_id` can arrive several times; `contact_id` is the person who made that party's booking. Each class attendee also fires `appointment.attendee.booked`. tags: [Webhooks] requestBody: required: true content: application/json: schema: allOf: - $ref: '#/components/schemas/WebhookEnvelope' - type: object properties: data: $ref: '#/components/schemas/AppointmentEventData' examples: booked: $ref: './examples/webhook.appointment-booked.json' responses: '200': description: Any 2xx acknowledges the delivery. appointment.rescheduled: post: operationId: webhookAppointmentRescheduled summary: Appointment rescheduled description: | A one-on-one appointment moved to a new time. `start_at` is the new time and `previous_start_at` the old one. Moving a group class occurrence does not fire this event. tags: [Webhooks] requestBody: required: true content: application/json: schema: allOf: - $ref: '#/components/schemas/WebhookEnvelope' - type: object properties: data: $ref: '#/components/schemas/AppointmentRescheduledData' examples: rescheduled: $ref: './examples/webhook.appointment-rescheduled.json' responses: '200': description: Any 2xx acknowledges the delivery. appointment.cancelled: post: operationId: webhookAppointmentCancelled summary: Appointment cancelled description: | A whole appointment was cancelled. For a group class occurrence `contact_id` is null, `attendee_count` says how many attendees it had, and each attendee also fires `appointment.attendee.cancelled`. When one party leaves a class that is still running, only `appointment.attendee.cancelled` fires. Bookings cancelled because their contact was deleted fire nothing. tags: [Webhooks] requestBody: required: true content: application/json: schema: allOf: - $ref: '#/components/schemas/WebhookEnvelope' - type: object properties: data: $ref: '#/components/schemas/AppointmentCancelledData' examples: cancelled: $ref: './examples/webhook.appointment-cancelled.json' responses: '200': description: Any 2xx acknowledges the delivery. appointment.updated: post: operationId: webhookAppointmentUpdated summary: Appointment updated description: | Details of an existing appointment changed without a change of time. `changed` lists what changed. Today the only change sent is `join_url`: a Google Meet or Microsoft Teams link that could not be created at booking time and was added later. tags: [Webhooks] requestBody: required: true content: application/json: schema: allOf: - $ref: '#/components/schemas/WebhookEnvelope' - type: object properties: data: $ref: '#/components/schemas/AppointmentUpdatedData' examples: join-url-added: $ref: './examples/webhook.appointment-updated.json' responses: '200': description: Any 2xx acknowledges the delivery. appointment.completed: post: operationId: webhookAppointmentCompleted summary: Appointment completed description: | An appointment that was still pending or confirmed was marked completed. This happens automatically a few hours after its scheduled end, so expect a delay. Appointments already marked as a no-show are not completed. For a group class `contact_id` is one of the attendees, not the whole roster. tags: [Webhooks] requestBody: required: true content: application/json: schema: allOf: - $ref: '#/components/schemas/WebhookEnvelope' - type: object properties: data: $ref: '#/components/schemas/AppointmentEventData' examples: completed: $ref: './examples/webhook.appointment-completed.json' responses: '200': description: Any 2xx acknowledges the delivery. appointment.no_show: post: operationId: webhookAppointmentNoShow summary: Appointment no-show description: | An attendee did not show up. A team member can mark it, or it is marked automatically once the booking type's no-show window passes (only when the booking type sets one). For a group class it fires once per absent attendee, identified by `contact_id`. tags: [Webhooks] requestBody: required: true content: application/json: schema: allOf: - $ref: '#/components/schemas/WebhookEnvelope' - type: object properties: data: $ref: '#/components/schemas/AppointmentEventData' examples: no-show: $ref: './examples/webhook.appointment-no-show.json' responses: '200': description: Any 2xx acknowledges the delivery. appointment.waitlist.joined: post: operationId: webhookAppointmentWaitlistJoined summary: Class waitlist joined description: | A party tried to book a full group class occurrence and joined its waitlist. `contact_id` is the person who made the booking and `waitlist_position` their place in line. tags: [Webhooks] requestBody: required: true content: application/json: schema: allOf: - $ref: '#/components/schemas/WebhookEnvelope' - type: object properties: data: $ref: '#/components/schemas/AppointmentWaitlistJoinedData' examples: waitlist-joined: $ref: './examples/webhook.appointment-waitlist-joined.json' responses: '200': description: Any 2xx acknowledges the delivery. appointment.waitlist.promoted: post: operationId: webhookAppointmentWaitlistPromoted summary: Class waitlist promoted description: | Seats opened in a group class occurrence and a waitlisted party was moved into it. `contact_id` is the person who made the party's booking. Each promoted attendee also fires `appointment.attendee.booked`. tags: [Webhooks] requestBody: required: true content: application/json: schema: allOf: - $ref: '#/components/schemas/WebhookEnvelope' - type: object properties: data: $ref: '#/components/schemas/AppointmentWaitlistPromotedData' examples: waitlist-promoted: $ref: './examples/webhook.appointment-waitlist-promoted.json' responses: '200': description: Any 2xx acknowledges the delivery. appointment.attendee.booked: post: operationId: webhookAppointmentAttendeeBooked summary: Class attendee booked description: | One attendee got a seat in a group class occurrence, either by booking, by being added by a team member, or by promotion from the waitlist. It fires once per attendee, so a party of three sends three. tags: [Webhooks] requestBody: required: true content: application/json: schema: allOf: - $ref: '#/components/schemas/WebhookEnvelope' - type: object properties: data: $ref: '#/components/schemas/AppointmentAttendeeBookedData' examples: attendee-booked: $ref: './examples/webhook.appointment-attendee-booked.json' responses: '200': description: Any 2xx acknowledges the delivery. appointment.attendee.cancelled: post: operationId: webhookAppointmentAttendeeCancelled summary: Class attendee cancelled description: | One attendee's seat in a group class occurrence was cancelled, either because their party cancelled or because the whole occurrence was cancelled. It fires once per attendee. Seats cancelled because the contact was deleted fire nothing. tags: [Webhooks] requestBody: required: true content: application/json: schema: allOf: - $ref: '#/components/schemas/WebhookEnvelope' - type: object properties: data: $ref: '#/components/schemas/AppointmentAttendeeCancelledData' examples: attendee-cancelled: $ref: './examples/webhook.appointment-attendee-cancelled.json' responses: '200': description: Any 2xx acknowledges the delivery. review_request.sent: post: operationId: webhookReviewRequestSent summary: Review request sent description: | A review request went out to a contact by text or email. It fires only after the message is sent; requests that are suppressed or fail to send fire nothing. `review_url` is the link the contact received and works as a password: anyone holding it can respond as that contact for 30 days. tags: [Webhooks] requestBody: required: true content: application/json: schema: allOf: - $ref: '#/components/schemas/WebhookEnvelope' - type: object properties: data: $ref: '#/components/schemas/ReviewRequestSentData' examples: sent: $ref: './examples/webhook.review-request-sent.json' responses: '200': description: Any 2xx acknowledges the delivery. review_request.rated: post: operationId: webhookReviewRequestRated summary: Review request rated description: | A contact opened their review link and rated their experience as positive or negative. It fires each time they submit a rating, so the same request can send it more than once; the latest one wins. tags: [Webhooks] requestBody: required: true content: application/json: schema: allOf: - $ref: '#/components/schemas/WebhookEnvelope' - type: object properties: data: $ref: '#/components/schemas/ReviewRequestRatedData' examples: rated: $ref: './examples/webhook.review-request-rated.json' responses: '200': description: Any 2xx acknowledges the delivery. review_request.clicked_out: post: operationId: webhookReviewRequestClickedOut summary: Review request clicked out description: | A contact clicked through from their review page to your Google or Facebook review page. Neither platform reports whether a review was then written, so this is the last step Drop Cowboy can see. It fires on every click. tags: [Webhooks] requestBody: required: true content: application/json: schema: allOf: - $ref: '#/components/schemas/WebhookEnvelope' - type: object properties: data: $ref: '#/components/schemas/ReviewRequestClickedOutData' examples: clicked-out: $ref: './examples/webhook.review-request-clicked-out.json' responses: '200': description: Any 2xx acknowledges the delivery. review_request.feedback_submitted: post: operationId: webhookReviewRequestFeedbackSubmitted summary: Review feedback submitted description: | A contact sent private feedback from their review page instead of posting a public review. It fires each time they submit, and the latest feedback replaces the earlier one. tags: [Webhooks] requestBody: required: true content: application/json: schema: allOf: - $ref: '#/components/schemas/WebhookEnvelope' - type: object properties: data: $ref: '#/components/schemas/ReviewRequestFeedbackSubmittedData' examples: feedback-submitted: $ref: './examples/webhook.review-request-feedback-submitted.json' responses: '200': description: Any 2xx acknowledges the delivery. form.submitted: post: operationId: webhookFormSubmitted summary: Web form submitted description: | A visitor submitted one of your web forms and the submission was saved to a contact. `form_fields` lists each answered field with its label and `fields` has the same answers keyed by field name. Test submissions from the form editor's preview fire it too, against a temporary test contact. tags: [Webhooks] requestBody: required: true content: application/json: schema: allOf: - $ref: '#/components/schemas/WebhookEnvelope' - type: object properties: data: $ref: '#/components/schemas/FormSubmittedData' examples: submitted: $ref: './examples/webhook.form-submitted.json' responses: '200': description: Any 2xx acknowledges the delivery. contact.goal.achieved: post: operationId: webhookContactGoalAchieved summary: Contact goal achieved description: | Website activity from a known contact met one of your active web goals. It fires only for visitors already linked to a contact, and only for goals of type `web`. The goal's configured tag and list actions are included so you can see what was applied. tags: [Webhooks] requestBody: required: true content: application/json: schema: allOf: - $ref: '#/components/schemas/WebhookEnvelope' - type: object properties: data: $ref: '#/components/schemas/ContactGoalAchievedData' examples: goal-achieved: $ref: './examples/webhook.goal-achieved.json' responses: '200': description: Any 2xx acknowledges the delivery. contact.product_interest.captured: post: operationId: webhookContactProductInterestCaptured summary: Product interest captured description: | A known contact showed interest in one of your products. It fires when the tracking script sends a `conversion` or `goal` event with a `product_id` for a visitor linked to a contact, and when product interest a visitor showed while anonymous is attached to them by a form submission (once per new product). tags: [Webhooks] requestBody: required: true content: application/json: schema: allOf: - $ref: '#/components/schemas/WebhookEnvelope' - type: object properties: data: $ref: '#/components/schemas/ProductInterestCapturedData' examples: product-interest: $ref: './examples/webhook.product-interest-captured.json' responses: '200': description: Any 2xx acknowledges the delivery. contact.web_event.linked: post: operationId: webhookContactWebEventLinked summary: Web activity linked to contact description: | A visitor submitted a web form and the website activity they had recorded while anonymous was linked to their contact. `linked_count` is how many events were linked. Nothing fires when there was no earlier activity to link. tags: [Webhooks] requestBody: required: true content: application/json: schema: allOf: - $ref: '#/components/schemas/WebhookEnvelope' - type: object properties: data: $ref: '#/components/schemas/WebEventLinkedData' examples: linked: $ref: './examples/webhook.web-event-linked.json' responses: '200': description: Any 2xx acknowledges the delivery. contact.web_event.tracked: post: operationId: webhookContactWebEventTracked summary: Web events tracked description: | The tracking script on one of your sites recorded a batch of visitor activity. It fires once per batch, not per page view, and carries counts rather than the events themselves. It does not say which contact the visitor is. Expect high volume on busy sites. tags: [Webhooks] requestBody: required: true content: application/json: schema: allOf: - $ref: '#/components/schemas/WebhookEnvelope' - type: object properties: data: $ref: '#/components/schemas/WebEventTrackedData' examples: tracked: $ref: './examples/webhook.web-event-tracked.json' responses: '200': description: Any 2xx acknowledges the delivery. contact.rvm.sent: post: operationId: webhookRvmSent summary: Ringless voicemail sent from the app description: | Fires for a ringless voicemail sent to one contact from the Drop Cowboy app, once the send has finished, whether or not it was delivered. It carries no outcome. A voicemail sent through the Drop Cowboy integration fires it as soon as the request is accepted, with `integration_id` and `entry_id` instead of `rvm_id`. API and campaign voicemails never fire it; use `contact.rvm.status` for those. tags: [Webhooks] requestBody: required: true content: application/json: schema: allOf: - $ref: '#/components/schemas/WebhookEnvelope' - type: object properties: data: $ref: '#/components/schemas/RvmSentData' examples: app: $ref: './examples/webhook.rvm-sent.json' responses: '200': description: Any 2xx acknowledges the delivery. contact.msg.opt_out: post: operationId: webhookMessageOptOut summary: Contact opted out by text description: | A contact texted an opt-out keyword such as STOP to one of your numbers, or a phone-line rule marked the sender do-not-contact. The number is added to your do-not-contact list before this fires. `contact_id` is null when the number matches no contact. The same text also fires `contact.msg.received`. tags: [Webhooks] requestBody: required: true content: application/json: schema: allOf: - $ref: '#/components/schemas/WebhookEnvelope' - type: object properties: data: $ref: '#/components/schemas/MessageOptOutData' examples: stop: $ref: './examples/webhook.msg-opt-out.json' responses: '200': description: Any 2xx acknowledges the delivery. contact.msg.disposition.changed: post: operationId: webhookMessageDispositionChanged summary: Text disposition changed description: | A teammate set or changed the disposition of a text conversation in the Drop Cowboy inbox. `disposition` is the new value; the previous one is not sent. `contact_id` and `list_id` are the text's own. tags: [Webhooks] requestBody: required: true content: application/json: schema: allOf: - $ref: '#/components/schemas/WebhookEnvelope' - type: object properties: data: $ref: '#/components/schemas/MessageDispositionChangedData' examples: followUp: $ref: './examples/webhook.msg-disposition-changed.json' responses: '200': description: Any 2xx acknowledges the delivery. contact.email.sent: post: operationId: webhookEmailSent summary: Outbound email recorded description: | Fires when an outbound email to a contact, from the inbox, a campaign, an automation or the API, is handed to the email provider. An email that is blocked or that the provider rejects doesn't fire it. Use `contact.email.status` for delivery, opens and failures. tags: [Webhooks] requestBody: required: true content: application/json: schema: allOf: - $ref: '#/components/schemas/WebhookEnvelope' - type: object properties: data: $ref: '#/components/schemas/EmailSentData' examples: sent: $ref: './examples/webhook.email-sent.json' responses: '200': description: Any 2xx acknowledges the delivery. contact.email.received: post: operationId: webhookEmailReceived summary: Inbound email received description: | An email arrived in one of your team's mailboxes. `user_id` is the teammate the mailbox belongs to and `mailbox_id` the mailbox; both are null for a shared inbound address. tags: [Webhooks] requestBody: required: true content: application/json: schema: allOf: - $ref: '#/components/schemas/WebhookEnvelope' - type: object properties: data: $ref: '#/components/schemas/EmailReceivedData' examples: received: $ref: './examples/webhook.email-received.json' responses: '200': description: Any 2xx acknowledges the delivery. contact.email.link_clicked: post: operationId: webhookEmailLinkClicked summary: Link opened in a received email description: | A teammate opened a link inside an email a contact sent you, from the Drop Cowboy inbox. Links in inbound email are checked for threats when clicked: `verdict` is `safe`, or `suspicious` when a warning page was shown. Proceeding past the warning fires it again with `user_proceeded` true. Blocked links fire `contact.link.blocked` instead. For clicks by recipients of emails you send, use `contact.email.status` with `clicked`. tags: [Webhooks] requestBody: required: true content: application/json: schema: allOf: - $ref: '#/components/schemas/WebhookEnvelope' - type: object properties: data: $ref: '#/components/schemas/EmailLinkClickedData' examples: safe: $ref: './examples/webhook.email-link-clicked.json' responses: '200': description: Any 2xx acknowledges the delivery. contact.fax.sent: post: operationId: webhookFaxSent summary: Fax submitted description: | A teammate sent a fax from the Drop Cowboy app. It fires when the fax is submitted, before it is transmitted, so `fax.status` is `pending`, or `failed` when it could not be queued. No event reports the final transmission result. tags: [Webhooks] requestBody: required: true content: application/json: schema: allOf: - $ref: '#/components/schemas/WebhookEnvelope' - type: object properties: data: $ref: '#/components/schemas/FaxEventData' examples: pending: $ref: './examples/webhook.fax-sent.json' responses: '200': description: Any 2xx acknowledges the delivery. contact.fax.received: post: operationId: webhookFaxReceived summary: Inbound fax received description: | A fax arrived on one of your fax numbers. Fires once per fax. It carries no `user_id`, and `contact_id` is null when the sending number matches no contact. tags: [Webhooks] requestBody: required: true content: application/json: schema: allOf: - $ref: '#/components/schemas/WebhookEnvelope' - type: object properties: data: $ref: '#/components/schemas/FaxEventData' examples: received: $ref: './examples/webhook.fax-received.json' responses: '200': description: Any 2xx acknowledges the delivery. contact.fax.deleted: post: operationId: webhookFaxDeleted summary: Fax deleted description: | A teammate deleted a fax in the Drop Cowboy app. `contact_id` and `contact` are always null; `fax` still describes the deleted fax. tags: [Webhooks] requestBody: required: true content: application/json: schema: allOf: - $ref: '#/components/schemas/WebhookEnvelope' - type: object properties: data: $ref: '#/components/schemas/FaxEventData' examples: deleted: $ref: './examples/webhook.fax-deleted.json' responses: '200': description: Any 2xx acknowledges the delivery. contact.voicemail.received: post: operationId: webhookVoicemailReceived summary: Voicemail received description: | A caller left a voicemail on one of your phone lines. `call_recording` has the length and `ivr_id` is the line that was dialed. An empty recording does not fire it. tags: [Webhooks] requestBody: required: true content: application/json: schema: allOf: - $ref: '#/components/schemas/WebhookEnvelope' - type: object properties: data: $ref: '#/components/schemas/VoicemailReceivedData' examples: received: $ref: './examples/webhook.voicemail-received.json' responses: '200': description: Any 2xx acknowledges the delivery. contact.voicemail.read: post: operationId: webhookVoicemailRead summary: Voicemail marked read description: | A teammate marked a voicemail as read in the Drop Cowboy app. `user_id` is the teammate. The event does not carry the contact or call; read them from `call_recording.call_id`. It is sent only when the update changed the voicemail. Marking a voicemail read records a new read time, so marking an already-read voicemail read again also sends it. tags: [Webhooks] requestBody: required: true content: application/json: schema: allOf: - $ref: '#/components/schemas/WebhookEnvelope' - type: object properties: data: $ref: '#/components/schemas/VoicemailStateData' examples: read: $ref: './examples/webhook.voicemail-read.json' responses: '200': description: Any 2xx acknowledges the delivery. contact.voicemail.unread: post: operationId: webhookVoicemailUnread summary: Voicemail marked unread description: | A teammate marked a voicemail as unread in the Drop Cowboy app. Same `data` as `contact.voicemail.read`. Marking a voicemail unread that is already unread sends nothing. tags: [Webhooks] requestBody: required: true content: application/json: schema: allOf: - $ref: '#/components/schemas/WebhookEnvelope' - type: object properties: data: $ref: '#/components/schemas/VoicemailStateData' examples: unread: $ref: './examples/webhook.voicemail-unread.json' responses: '200': description: Any 2xx acknowledges the delivery. contact.chat.session_started: post: operationId: webhookChatSessionStarted summary: Chat started description: | Fires once per chat conversation, the first time the website visitor is tied to a contact: usually their first message, or earlier when they fill in the pre-chat form, give their email, or return as a known contact. Opening the chat widget alone does not fire it. tags: [Webhooks] requestBody: required: true content: application/json: schema: allOf: - $ref: '#/components/schemas/WebhookEnvelope' - type: object properties: data: $ref: '#/components/schemas/ChatSessionData' examples: started: $ref: './examples/webhook.chat-session-started.json' responses: '200': description: Any 2xx acknowledges the delivery. contact.chat.identified: post: operationId: webhookChatIdentified summary: Chat visitor identified description: | A chat visitor gave their email address in the chat and was matched to a contact, or saved as a new one; `is_new` tells which, and a new contact also fires `contact.created`. Fires each time the visitor identifies, not only the first. tags: [Webhooks] requestBody: required: true content: application/json: schema: allOf: - $ref: '#/components/schemas/WebhookEnvelope' - type: object properties: data: $ref: '#/components/schemas/ChatIdentifiedData' examples: newContact: $ref: './examples/webhook.chat-identified.json' responses: '200': description: Any 2xx acknowledges the delivery. contact.chat.received: post: operationId: webhookChatReceived summary: Chat message received description: | A website visitor sent a message in a chat conversation. The message text is not included; `message_id` and `conversation_id` identify it. tags: [Webhooks] requestBody: required: true content: application/json: schema: allOf: - $ref: '#/components/schemas/WebhookEnvelope' - type: object properties: data: $ref: '#/components/schemas/ChatMessageData' examples: received: $ref: './examples/webhook.chat-received.json' responses: '200': description: Any 2xx acknowledges the delivery. contact.chat.sent: post: operationId: webhookChatSent summary: Chat reply sent by a teammate description: | A teammate replied in a chat conversation from the Drop Cowboy inbox. Replies sent with `POST /chat/public/reply`, automated replies and AI agent replies do not fire it. The message text and the teammate are not included. tags: [Webhooks] requestBody: required: true content: application/json: schema: allOf: - $ref: '#/components/schemas/WebhookEnvelope' - type: object properties: data: $ref: '#/components/schemas/ChatMessageData' examples: sent: $ref: './examples/webhook.chat-sent.json' responses: '200': description: Any 2xx acknowledges the delivery. contact.chat.conversation_closed: post: operationId: webhookChatConversationClosed summary: Chat conversation closed description: | A teammate closed a chat conversation in the Drop Cowboy inbox. It carries only the team and `conversation_id`: no contact, and `chat_site_id` is null. tags: [Webhooks] requestBody: required: true content: application/json: schema: allOf: - $ref: '#/components/schemas/WebhookEnvelope' - type: object properties: data: $ref: '#/components/schemas/ChatConversationClosedData' examples: closed: $ref: './examples/webhook.chat-conversation-closed.json' responses: '200': description: Any 2xx acknowledges the delivery. revenue.subscription.started: post: operationId: webhookRevenueSubscriptionStarted summary: Subscription started description: > A Chargebee subscription became paid for one of your contacts, from a new signup or from a trial that converted. `data.revenue_event.is_new_customer` is true on the contact's first revenue event. A trial that converts also sends `customer.trial.converted` with the same `data`. Fires only for a Chargebee customer linked to a contact; activity for an unlinked customer is held and delivered once you link it. tags: [Webhooks] requestBody: required: true content: application/json: schema: allOf: - $ref: '#/components/schemas/WebhookEnvelope' - type: object properties: data: $ref: '#/components/schemas/RevenueEventData' examples: started: $ref: './examples/webhook.revenue-subscription-started.json' responses: '200': description: Any 2xx acknowledges the delivery. revenue.subscription.renewed: post: operationId: webhookRevenueSubscriptionRenewed summary: Subscription renewed description: > A contact's Chargebee subscription renewed for another billing period. `data.amount` is the renewal invoice total and `data.mrr_impact` is 0, because recurring revenue did not change. tags: [Webhooks] requestBody: required: true content: application/json: schema: allOf: - $ref: '#/components/schemas/WebhookEnvelope' - type: object properties: data: $ref: '#/components/schemas/RevenueEventData' examples: renewed: $ref: './examples/webhook.revenue-subscription-renewed.json' responses: '200': description: Any 2xx acknowledges the delivery. revenue.subscription.upgraded: post: operationId: webhookRevenueSubscriptionUpgraded summary: Subscription upgraded description: > A contact changed to a Chargebee plan that raised their monthly recurring revenue. `data.mrr_impact` is the positive change, `is_expansion` is true, and `previous_plan_id` names the plan they left. A plan change that leaves recurring revenue unchanged sends no event. tags: [Webhooks] requestBody: required: true content: application/json: schema: allOf: - $ref: '#/components/schemas/WebhookEnvelope' - type: object properties: data: $ref: '#/components/schemas/RevenueEventData' examples: upgraded: $ref: './examples/webhook.revenue-subscription-upgraded.json' responses: '200': description: Any 2xx acknowledges the delivery. revenue.subscription.downgraded: post: operationId: webhookRevenueSubscriptionDowngraded summary: Subscription downgraded description: > A contact changed to a Chargebee plan that lowered their monthly recurring revenue. `data.mrr_impact` is the negative change, `is_contraction` is true, and `previous_plan_id` names the plan they left. tags: [Webhooks] requestBody: required: true content: application/json: schema: allOf: - $ref: '#/components/schemas/WebhookEnvelope' - type: object properties: data: $ref: '#/components/schemas/RevenueEventData' examples: downgraded: $ref: './examples/webhook.revenue-subscription-downgraded.json' responses: '200': description: Any 2xx acknowledges the delivery. revenue.subscription.canceled: post: operationId: webhookRevenueSubscriptionCanceled summary: Subscription canceled description: > A contact's Chargebee subscription was canceled or deleted. `data.mrr_impact` is the full monthly recurring revenue lost, as a negative number, or 0 when the contact canceled during a trial. `customer_tenure_days` and `churn_reason` are filled in when Chargebee provides them. tags: [Webhooks] requestBody: required: true content: application/json: schema: allOf: - $ref: '#/components/schemas/WebhookEnvelope' - type: object properties: data: $ref: '#/components/schemas/RevenueEventData' examples: canceled: $ref: './examples/webhook.revenue-subscription-canceled.json' responses: '200': description: Any 2xx acknowledges the delivery. revenue.refund.created: post: operationId: webhookRevenueRefundCreated summary: Refund issued description: > Chargebee issued a refund to one of your contacts. `data.amount` is negative and `revenue_event.chargebee_invoice_id` is the refunded invoice. A refund of 0 sends no event. tags: [Webhooks] requestBody: required: true content: application/json: schema: allOf: - $ref: '#/components/schemas/WebhookEnvelope' - type: object properties: data: $ref: '#/components/schemas/RevenueEventData' examples: refund: $ref: './examples/webhook.revenue-refund-created.json' responses: '200': description: Any 2xx acknowledges the delivery. customer.trial.converted: post: operationId: webhookCustomerTrialConverted summary: Trial converted to paid description: > A contact's Chargebee trial turned into a paid subscription. It is sent together with `revenue.subscription.started` and carries the same `data`, with `revenue_event.is_trial_conversion` set to true. Sent only when the conversion arrives straight from Chargebee, not when held activity is delivered after you link a customer. tags: [Webhooks] requestBody: required: true content: application/json: schema: allOf: - $ref: '#/components/schemas/WebhookEnvelope' - type: object properties: data: $ref: '#/components/schemas/RevenueEventData' examples: converted: $ref: './examples/webhook.customer-trial-converted.json' responses: '200': description: Any 2xx acknowledges the delivery. customer.trial.expiring: post: operationId: webhookCustomerTrialExpiring summary: Trial ending soon description: > A contact's Chargebee trial ends within the next 7 days. A daily check sends this once per trial; extending the trial or starting a new one makes it fire again. `data.days_remaining` counts whole days left, rounded up. tags: [Webhooks] requestBody: required: true content: application/json: schema: allOf: - $ref: '#/components/schemas/WebhookEnvelope' - type: object properties: data: $ref: '#/components/schemas/CustomerTrialExpiringData' examples: expiring: $ref: './examples/webhook.customer-trial-expiring.json' responses: '200': description: Any 2xx acknowledges the delivery. disposition.completed: post: operationId: webhookDispositionCompleted summary: Call disposition evaluated description: | Sent once for every disposition a call gets, whether it comes from the dialer (`contact.call.disposition`) or from editing a past call (`contact.call.disposition.changed`), after the disposition has been checked against your active call and disposition goals. It is sent whether or not any goal matched; `goal_ids` lists the goals it achieved. Calls with no contact are not evaluated and send nothing. Each goal achieved also sends `goal.triggered` and `disposition.goal.triggered`. tags: [Webhooks] requestBody: required: true content: application/json: schema: allOf: - $ref: '#/components/schemas/WebhookEnvelope' - type: object properties: data: $ref: '#/components/schemas/DispositionCompletedData' examples: sold: $ref: './examples/webhook.disposition-completed.json' responses: '200': description: Any 2xx acknowledges the delivery. disposition.goal.triggered: post: operationId: webhookDispositionGoalTriggered summary: Disposition achieved a goal description: | A call disposition achieved one of your call or disposition goals. Sent once per goal achieved, alongside `goal.triggered` for the same goal; this event carries the disposition and call details in a nested `disposition` object. tags: [Webhooks] requestBody: required: true content: application/json: schema: allOf: - $ref: '#/components/schemas/WebhookEnvelope' - type: object properties: data: $ref: '#/components/schemas/DispositionGoalTriggeredData' examples: sold: $ref: './examples/webhook.disposition-goal-triggered.json' responses: '200': description: Any 2xx acknowledges the delivery. disposition.sale.closed: post: operationId: webhookDispositionSaleClosed summary: Sale closed on a call description: | A call was dispositioned `sold` and achieved a goal whose category is `revenue` or `sales`. Sent once per such goal. When the goal pays a commission, `deal_value`, `commission_id` and `commission_amount` come from that commission. tags: [Webhooks] requestBody: required: true content: application/json: schema: allOf: - $ref: '#/components/schemas/WebhookEnvelope' - type: object properties: data: $ref: '#/components/schemas/DispositionSaleClosedData' examples: sold: $ref: './examples/webhook.disposition-sale-closed.json' responses: '200': description: Any 2xx acknowledges the delivery. disposition.dnc.requested: post: operationId: webhookDispositionDncRequested summary: Do-not-call requested on a call description: | A call was dispositioned `dnc` and achieved one of your call or disposition goals. Sent once per goal achieved, so it is only sent if you have a goal that matches the `dnc` disposition. Use it to keep other systems' suppression lists in step. tags: [Webhooks] requestBody: required: true content: application/json: schema: allOf: - $ref: '#/components/schemas/WebhookEnvelope' - type: object properties: data: $ref: '#/components/schemas/DispositionDncRequestedData' examples: dnc: $ref: './examples/webhook.disposition-dnc-requested.json' responses: '200': description: Any 2xx acknowledges the delivery. commission.pending: post: operationId: webhookCommissionPending summary: Commission created description: > A new commission was created and is awaiting approval. It is sent when a call disposition achieves a goal that pays a commission (`commission.source_event_type: disposition`), and when a recurring commission agreement earns its next payment because the customer's subscription renewed (`commission.source_event_type: revenue`). Renewal commissions are created by a scheduled check, so they can arrive some time after the renewal. tags: [Webhooks] requestBody: required: true content: application/json: schema: allOf: - $ref: '#/components/schemas/WebhookEnvelope' - type: object properties: data: $ref: '#/components/schemas/CommissionEventData' examples: disposition: $ref: './examples/webhook.commission-pending.json' renewal: $ref: './examples/webhook.commission-pending-renewal.json' responses: '200': description: Any 2xx acknowledges the delivery. commission.approved: post: operationId: webhookCommissionApproved summary: Commission approved description: > A teammate approved a pending commission. `data.approved_by` is the approver's user id. Bulk approval sends one event per commission. tags: [Webhooks] requestBody: required: true content: application/json: schema: allOf: - $ref: '#/components/schemas/WebhookEnvelope' - type: object properties: data: $ref: '#/components/schemas/CommissionEventData' examples: approved: $ref: './examples/webhook.commission-approved.json' responses: '200': description: Any 2xx acknowledges the delivery. commission.rejected: post: operationId: webhookCommissionRejected summary: Commission rejected description: > A pending or approved commission was rejected. When a teammate rejects it, `data.rejected_by` is their user id and `data.reason` is the reason they gave, if any. It is also sent when a refund for the contact is recorded in your connected Chargebee site, which rejects that contact's pending and approved commissions automatically: `data.rejected_by` is then null and `data.reason` describes the refund. Commissions already paid are only flagged for clawback and send no event. tags: [Webhooks] requestBody: required: true content: application/json: schema: allOf: - $ref: '#/components/schemas/WebhookEnvelope' - type: object properties: data: $ref: '#/components/schemas/CommissionEventData' examples: rejected: $ref: './examples/webhook.commission-rejected.json' refund: $ref: './examples/webhook.commission-rejected-refund.json' responses: '200': description: Any 2xx acknowledges the delivery. commission.paid: post: operationId: webhookCommissionPaid summary: Commission paid description: > A teammate marked a commission paid. `data.paid_by` and `data.paid_at` record who and when. Paying a commission that was never approved also stamps the approval fields with the same teammate and time. Bulk payment sends one event per commission. tags: [Webhooks] requestBody: required: true content: application/json: schema: allOf: - $ref: '#/components/schemas/WebhookEnvelope' - type: object properties: data: $ref: '#/components/schemas/CommissionEventData' examples: paid: $ref: './examples/webhook.commission-paid.json' responses: '200': description: Any 2xx acknowledges the delivery. goal.triggered: post: operationId: webhookGoalTriggered summary: Goal achieved description: > One of your goals was achieved. Sent once per goal achieved, for two kinds of goal: a web goal achieved by a tracked website event (`goal_type: web`), and a call or disposition goal achieved by a call disposition (`goal_type` `call` or `disposition`, with `source_type: disposition`). The two carry different fields; see the schema. A disposition that achieves a goal also sends `disposition.goal.triggered`. Email, text and chat goals do not send this event. tags: [Webhooks] requestBody: required: true content: application/json: schema: allOf: - $ref: '#/components/schemas/WebhookEnvelope' - type: object properties: data: $ref: '#/components/schemas/GoalTriggeredData' examples: web: $ref: './examples/webhook.goal-triggered.json' disposition: $ref: './examples/webhook.goal-triggered-disposition.json' responses: '200': description: Any 2xx acknowledges the delivery. components: securitySchemes: apiKey: type: apiKey in: header name: x-key description: | API key. Always sent together with `x-secret`. Keys created on the Developers page or with `POST /register/public/apikeys` carry every scope. Keys created for Connect AI (MCP) clients are capped by the creating user's AI access level. apiSecret: type: apiKey in: header name: x-secret description: API secret, shown once when the key is created. oauth2: type: oauth2 description: | OAuth 2.0 client credentials. Request a token with `audience=https://api-v2.dropcowboy.com`. Machine clients are provisioned by Drop Cowboy support and mapped to one team. An operation accepts a token that carries ANY ONE of the scopes it lists. Tokens must also carry `mcp:access` today (see the changelog). The four send routes check the token after answering `202`, like an API key. flows: clientCredentials: tokenUrl: https://login.dropcowboy.com/oauth/token scopes: contacts:read: Read contacts, tasks, boards, dashboards and call history contacts:write: Create and update contacts, tasks and boards lists:read: Read contact lists and their members lists:write: Create and change contact lists webforms:read: Read web forms campaigns:read: Read campaigns, stats and insights campaigns:write: Create, update and delete campaigns campaigns:send: Start and pause campaigns rvm:send: Send ringless voicemail sms:send: Send SMS and RCS messages voice:send: Place voice broadcast and AI broadcast calls, synthesize speech email:send: Send email email:read: Read email history and sending mailboxes media:read: Read media, voices and transcriptions media:write: Upload media; clone, design and delete voices webhooks:read: Read webhook subscriptions and signing secrets webhooks:write: Create and delete webhook subscriptions, post timeline events balance:read: Read account, balance, usage and API keys account:write: Manage API keys and account settings agents:read: Read AI agents and templates agents:write: Create, update, publish and delete AI agents consent:read: Read consent records consent:write: Record and revoke consent dnc:read: Read the do-not-contact list dnc:write: Change the do-not-contact list numbers:read: Read phone numbers, lines and IVRs numbers:write: Rent numbers and change lines chat:read: Read chat sites and conversations chat:write: Reply to chats appointments:read: Read appointments and booking types appointments:write: Book, cancel and reschedule appointments mcp:access: Required on every machine token today detectionApiKey: type: http scheme: bearer description: | Detection API key, from `POST /register/public/detection-keys`. Send it as `Authorization: Bearer ` or `?api_key=` on the WebSocket upgrade. On the Twilio route, pass it as a `` on the `` instead. The same key is the HMAC secret for detection webhooks. portalJwt: type: http scheme: bearer bearerFormat: JWT description: Your dashboard session token. Used only by `POST /phone/embed/playground-token`. embedJwt: type: http scheme: bearer bearerFormat: JWT description: 'Site token from `POST /phone/public/embed/token`. Send it as `Authorization: Bearer`, or give it to a widget with `init({ token })`. Never put the token in a URL.' parameters: IdempotencyKey: name: Idempotency-Key in: header required: false description: | Makes a retry safe to send twice. 1 to 255 printable ASCII characters, unique per request; a random UUID is the easy choice. Keys are scoped to your account and remembered for 24 hours from first use. The route is part of what is compared, so reusing a key on a different route counts as a different body. On `/rvm`, `/sms`, `/voice-broadcast` and `/ai-broadcast` the response is always `202` once queued, never `409`; the key is checked after queueing. The same key with the same body skips the duplicate, which then produces no webhook and no `callback_url` post. The same key with a different body sends nothing and fails the contact with 3027 (Idempotency Key Conflict); an invalid key fails it with 3028 (Invalid Idempotency Key). If the first request failed before sending (validation, credentials, balance), the key is released and you can retry with it. If it failed while being handed off for sending, the failure is still reported on `callback_url` and the status webhook, but the key is kept: a retry with it within 24 hours is a duplicate (nothing sent, no callback, no webhook), so use a new key. When we retry a send ourselves, each recipient still gets at most one delivery, the key stays held, and only the final result arrives on the delivery events and `callback_url`. Keys are compared exactly as sent. An empty or whitespace-only header counts as no header, on every route. On `POST /email/public/email` a repeat of a successful send replays the stored response with `Idempotent-Replayed: true`. See that operation for its `409` and `400` answers. schema: type: string minLength: 1 maxLength: 255 example: 7c9e6679-7425-40de-944b-e07fc1f90ae7 LimitParam: name: limit in: query schema: type: integer minimum: 1 maximum: 100 default: 50 ContactPageLimitParam: name: limit in: query description: Page size. schema: type: integer minimum: 1 default: 25 OffsetParam: name: offset in: query schema: type: integer minimum: 0 default: 0 KnowledgeLimit: name: limit in: query schema: type: integer minimum: 1 maximum: 100 default: 25 KnowledgeOffset: name: offset in: query schema: type: integer minimum: 0 default: 0 KnowledgeBaseId: name: knowledge_base_id in: path required: true schema: type: string format: uuid KnowledgeDocumentId: name: knowledge_document_id in: path required: true schema: type: string format: uuid SkipParam: name: skip in: query description: Number of records to skip. schema: type: integer minimum: 0 default: 0 CampaignIdParam: name: id in: path required: true description: '`campaign_id` from `GET /campaign/public/campaigns`.' schema: type: string format: uuid AfterIdParam: name: after_id in: query description: Cursor. Pass the previous page's `next_cursor`; omit for the first page. schema: type: string schemas: Meta: type: object properties: request_id: type: string format: uuid PaginatedMeta: type: object description: | Pagination for offset-based lists. Page with `limit` and the offset parameter the operation lists (`offset` or `skip`) until the offset plus `limit` reaches `total`. Cursor-based lists such as `GET /contact/public/lists/{id}/contacts` return `next_cursor` and `has_more` inside `data` instead. properties: total: type: integer limit: type: integer offset: type: integer request_id: type: string format: uuid ContactsPage: type: object required: [contacts] properties: contacts: type: array items: $ref: '#/components/schemas/Contact' total_contacts: type: integer description: Matching contacts across all pages. next_cursor: type: [object, 'null'] description: Position of the last contact on a full page. This route doesn't accept it back; page with `offset`. additionalProperties: true ContactListsPage: type: object required: [lists] properties: lists: type: array items: $ref: '#/components/schemas/ContactList' next_cursor: type: [object, 'null'] description: Position of the last list on a full page. This route doesn't accept it back; page with `offset`. additionalProperties: true PhoneNumbersPage: type: object required: [limits, numbers] properties: limits: type: object properties: used: type: integer description: Numbers on the account. sms_enabled: type: integer description: Numbers that can send SMS. team_limit: type: [integer, string] description: Most numbers the account may hold. Can arrive as a numeric string. numbers: type: array items: $ref: '#/components/schemas/PhoneNumber' MediaPage: type: object required: [medias] properties: medias: type: array items: $ref: '#/components/schemas/MediaListItem' total: type: integer description: Media files across all pages. MediaListItem: type: object properties: media_id: type: string format: uuid team_id: type: string name: type: string created_at: type: integer description: Epoch milliseconds. requested_at: type: integer description: Epoch milliseconds. Set when API use was requested for this file. approved_at: type: integer description: Epoch milliseconds. Set when the file is approved for API sends. api_allowed: type: boolean description: True once `approved_at` is set. CampaignsPage: type: object required: [campaigns] properties: campaigns: type: array items: $ref: '#/components/schemas/Campaign' counts: type: object description: Campaigns per status across the account, not just this page. properties: draft: type: integer active: type: integer scheduled: type: integer paused: type: integer complete: type: integer next_cursor: type: [string, 'null'] VoicesPage: type: object required: [voices] properties: voices: type: array items: $ref: '#/components/schemas/Voice' total: type: [integer, 'null'] description: Your account's voices; platform catalog voices are not counted. DocumentsPage: type: object required: [documents] properties: documents: type: array items: $ref: '#/components/schemas/Document' total_documents: type: integer description: Only with `get_total`. TasksPage: type: object required: [tasks] properties: tasks: type: array items: $ref: '#/components/schemas/Task' TemplatesPage: type: object required: [templates] properties: templates: type: array items: $ref: '#/components/schemas/Template' totalItems: type: integer description: Templates across all pages. AgentTemplatesPage: type: object required: [templates] properties: templates: type: array items: $ref: '#/components/schemas/AgentTemplate' Error: type: object description: | RFC 9457 problem details. Branch on `status` (it always equals the HTTP status), not on `type`: many routes report every failure as `.../errors/server-error`. Quote `request_id` when contacting support. required: [type, title, status] properties: type: type: string format: uri description: '`https://api-v2.dropcowboy.com/errors/{code}`, for example `validation-error`, `unauthorized`, `insufficient-scope`, `forbidden`, `not-found`, `conflict`, `server-error`.' title: type: string description: Humanized form of the code in `type`. status: type: integer detail: type: string description: Human-readable explanation. Wording can change; do not parse it. instance: type: string description: The request path. request_id: type: string description: Same value as the `X-Request-Id` response header. details: description: | Optional machine-readable detail, present on some validation errors. Some routes put `{ code }` here with a stable snake_case reason, for example the site-token mint (see `EmbedMintError`). ThrottleError: type: object description: Body of a `429` response. It is not a problem-details object. properties: message: type: string example: Too Many Requests PlanGateError: type: object description: | Body of the `403` a plan gate returns on email sends and on campaign create and start. It is not a problem-details object; their `402` is. properties: error: type: string description: Stable code such as `feature_not_available` or `trial_feature_blocked`. message: type: string feature: type: string description: Present on plan gates. AsyncResponse: type: object description: | We queued the request. `message_id` confirms only that, is not a delivery ID, and does not appear on later webhooks or callbacks. `callback_url` echoes your `foreign_id`; status webhooks do not, so match those on `to` or `contact_id`. required: [message_id, status] properties: message_id: type: string format: uuid status: type: string enum: [queued] AsyncError: type: object description: | We couldn't queue the request and nothing was sent. Retry with backoff and the same `Idempotency-Key`. properties: status: type: string enum: [error] ContactTag: type: object description: A tag on a contact. Tag details such as the label come from `GET /tag/public/tags`. properties: tag_id: type: string format: uuid tagged_at: type: integer description: Epoch milliseconds. tagged_by: type: [string, 'null'] description: User id that added the tag. deleted_at: type: [integer, 'null'] deleted_by: type: [string, 'null'] ContactField: type: object description: One field value on a contact. properties: field_id: type: string format: uuid description: Identifies this value on this contact. Send it back to change the value. type: type: string description: Field type, such as `first_name`, `email`, `main_phone` or `custom_field`. example: first_name display_name: type: string example: First Name value: description: The value. Usually a string. example: Dana ContactDisposition: type: [object, 'null'] description: The contact's latest disposition, or `null`. properties: id: type: string description: Disposition id. Contact: type: object description: | A contact as returned by list and search. Standard field values are flattened onto the contact by type, such as `first_name`, `last_name`, `email`, `main_phone`, `company` or `city`. additionalProperties: true properties: contact_id: type: string format: uuid brand_id: type: [string, 'null'] first_name: type: string last_name: type: string email: type: string main_phone: type: string example: '+13125550142' phone_numbers: type: array items: type: string example: '+13125550142' gravatar_id: type: [string, 'null'] list_ids: type: array items: type: string list_entered_at: type: object description: When the contact joined each list, keyed by `list_id`, in epoch milliseconds. additionalProperties: type: integer tags: type: array items: $ref: '#/components/schemas/ContactTag' owner: type: [string, 'null'] description: User id of the owner. disposition: $ref: '#/components/schemas/ContactDisposition' location: type: object description: Where the contact's phone number is from. additionalProperties: true call_info: type: object description: Call activity summary. additionalProperties: true message_info: type: object description: Text message activity summary. additionalProperties: true email_info: type: object description: Email activity summary. additionalProperties: true sms_count: type: integer description: Text messages with the contact. Present when `offset` is above 0. dnc: type: boolean description: The contact's phone numbers are on your do-not-contact list. email_dnc: type: boolean email_issue_reason: type: [string, 'null'] enum: [hard_bounce, spam_complaint, unsubscribed, null] has_tcpa_consent: type: boolean sold: description: Sale status of the contact. pricing_interest: type: array description: Pricing pages the contact viewed. items: {} first_touch: type: [object, 'null'] description: Attribution of the contact's first visit. additionalProperties: true last_touch: type: [object, 'null'] description: Attribution of the contact's latest visit. additionalProperties: true is_test: type: boolean created_at: type: integer description: Epoch milliseconds. modified_at: type: integer description: Epoch milliseconds. ContactDetail: type: object description: | One contact in full. Standard field values are also flattened onto the contact by type, such as `first_name` or `email`. additionalProperties: true properties: contact_id: type: string format: uuid brand_id: type: [string, 'null'] field_data: type: array items: $ref: '#/components/schemas/ContactField' phone_numbers: type: array items: type: string example: '+13125550142' gravatar_id: type: [string, 'null'] location: type: object additionalProperties: true list_ids: type: array items: type: string notes: type: array description: Notes pinned to the contact. items: $ref: '#/components/schemas/Note' pinned_notes_ids: type: array items: type: string documents: type: array items: {} tags: type: array items: $ref: '#/components/schemas/ContactTag' owner: type: [string, 'null'] dnc: type: boolean litigator: type: boolean call_info: type: object additionalProperties: true message_info: type: object additionalProperties: true email_info: type: object additionalProperties: true consents: type: object description: | The latest record for each consent type, keyed by type such as `tcpa_optin`. Each holds `consent_id`, `status`, and when it was granted or revoked. `null` for a type with no record. additionalProperties: true has_esign_consent: type: boolean has_tcpa_consent: type: boolean tcpa_opted_out_at: type: [integer, 'null'] tcpa_consent_date: description: When TCPA consent was given. tcpa_consent_id: type: [string, 'null'] trustedform_cert_url: type: [string, 'null'] jornaya_lead_id: type: [string, 'null'] has_sms_consent: type: boolean sms_opted_out_at: type: [integer, 'null'] has_email_consent: type: boolean email_opted_out_at: type: [integer, 'null'] email_dnc: type: boolean email_issue_reason: type: [string, 'null'] enum: [hard_bounce, spam_complaint, unsubscribed, null] rnd_status: type: string description: Reassigned Numbers Database check result. `unchecked` until checked. rnd_checked_at: type: [integer, 'null'] rnd_safe_harbor: type: boolean litigator_status: type: string description: Litigator scrub result. `unchecked` until checked. litigator_checked_at: type: [integer, 'null'] litigator_source: type: [string, 'null'] timezone_info: type: object additionalProperties: true first_touch: type: [object, 'null'] additionalProperties: true last_touch: type: [object, 'null'] additionalProperties: true visitor_id: type: [string, 'null'] description: Web tracking visitor linked to the contact. pricing_interest: type: array items: {} website_safe_url: type: [string, 'null'] created_at: type: integer ContactDetailResult: type: object properties: status: type: integer enum: [200] contact: oneOf: - $ref: '#/components/schemas/ContactDetail' - type: 'null' description: '`null` when no contact has that id.' ContactRecord: type: object description: A contact as stored, with every stored field. additionalProperties: true properties: contact_id: type: string format: uuid team_id: type: string brand_id: type: [string, 'null'] record_id: type: [string, 'null'] description: Your own id for the contact. field_data: type: array items: $ref: '#/components/schemas/ContactField' main_phone: type: [string, 'null'] example: '+13125550142' phone_numbers: type: array items: type: string gravatar_id: type: [string, 'null'] location: type: object additionalProperties: true list_ids: type: array items: type: string tags: type: array items: $ref: '#/components/schemas/ContactTag' owner: type: [string, 'null'] disposition: $ref: '#/components/schemas/ContactDisposition' call_info: type: object additionalProperties: true dnc: type: boolean litigator: type: boolean source: type: [string, 'null'] description: How the contact was created, such as `api`. consents: type: object additionalProperties: true has_esign_consent: type: boolean has_tcpa_consent: type: boolean has_sms_consent: type: boolean has_email_consent: type: boolean tcpa_opted_out_at: type: [integer, 'null'] sms_opted_out_at: type: [integer, 'null'] email_opted_out_at: type: [integer, 'null'] email_dnc: type: boolean email_issue_reason: type: [string, 'null'] rnd_status: type: string litigator_status: type: string created_at: type: integer created_by: type: [string, 'null'] modified_at: type: integer modified_by: type: [string, 'null'] deleted_at: type: [integer, 'null'] deleted_by: type: [string, 'null'] CreateContact: type: object required: [fields, values] properties: fields: type: array description: One mapping per column. items: type: object required: [type] properties: type: type: string description: | Field type, such as `first_name`, `last_name`, `email`, `main_phone`, `mobile_phone`, `company`, `city`, `state`, `zip_code`, `record_id`, `tcpa_consent` or `custom_field`. example: first_name field_id: type: string description: For `custom_field`, the `custom_field_id` from `GET /contact/public/fields`. values: type: array description: One array per contact, with values in the same order as `fields`. items: type: array items: type: string example: - ['Dana', 'Reyes', '+13125550142', 'dana@example.com'] add_list_ids: type: array description: Lists to add every contact in this call to. items: type: string add_tag_ids: type: array description: Tags to add to every contact in this call. items: type: string conflict_mode: type: string enum: [append, overwrite, replace] default: append description: | How to treat a row that matches an existing contact. `append` keeps existing values and fills only empty fields. `overwrite` replaces the values of the fields you send and leaves the rest alone. `replace` makes the row's values win and keeps existing values only where the row has none. owner: type: string description: User id that owns the contacts. region: type: string default: US description: Two-letter country code for reading phone numbers that don't start with `+`. return_counts: type: boolean default: false description: Return totals instead of per-row results. fire_webhook_events: type: boolean default: false description: Send `contact.created`, `contact.updated` and list webhooks. fire_automation_events: type: boolean default: false description: Start automations triggered by these contacts. ContactsCreated: type: object description: Per-row results. `index` is the row's position in `values`. properties: inserted: type: array items: $ref: '#/components/schemas/ContactRowResult' updated: type: array items: $ref: '#/components/schemas/ContactRowResult' rejected: type: array items: type: object properties: index: type: integer reasons: type: array description: Why the row was rejected. items: {} ContactRowResult: type: object properties: index: type: integer contact_id: type: string format: uuid ContactsCreatedCounts: type: object description: Totals, returned when `return_counts` is true. properties: accepted_count: type: integer rejected_count: type: integer inserted_count: type: integer updated_count: type: integer ContactStandardFields: type: object description: | Standard contact fields by name. Each one updates the contact's `field_data` entry of that type, or adds one if the contact doesn't have it. Send `null` or `""` to clear a field. Phone numbers are stored in E.164; one that can't be read is left out and the stored number stays. properties: first_name: { type: [string, 'null'] } last_name: { type: [string, 'null'] } email: { type: [string, 'null'] } main_phone: { type: [string, 'null'], description: 'E.164 recommended, such as `+13125550142`.' } mobile_phone: { type: [string, 'null'] } home_phone: { type: [string, 'null'] } office_phone: { type: [string, 'null'] } other_phone: { type: [string, 'null'] } company: { type: [string, 'null'] } website: { type: [string, 'null'] } address: { type: [string, 'null'] } city: { type: [string, 'null'] } state: { type: [string, 'null'] } zip_code: { type: [string, number, 'null'] } country: { type: [string, 'null'] } lead_source: { type: [string, 'null'] } ContactFieldDataUpdate: type: array description: | Field values to change. An entry wins over a standard field of the same type sent in the same request. items: type: object required: [value] properties: field_id: type: string format: uuid description: | From the contact's `field_data`. Leave it out on a standard type and the contact's existing field of that type is used, or a new one is added. A new UUID adds a field. type: type: string description: Field type, such as `first_name` or `custom_field`. Needed without `field_id`. custom_field_id: type: string format: uuid description: For `custom_field` entries. value: description: The new value. UpdateContact: description: | Send at least one standard field, at the top level or inside `contact`, or at least one `contact.field_data` entry. A standard field inside `contact` wins over the same field at the top level. Other keys are ignored, and a body with nothing to update is a `400`. allOf: - $ref: '#/components/schemas/ContactStandardFields' - type: object properties: contact: allOf: - $ref: '#/components/schemas/ContactStandardFields' - type: object properties: field_data: $ref: '#/components/schemas/ContactFieldDataUpdate' ListMembershipResult: type: object properties: success: type: boolean enum: [true] list_id: type: string description: The list the contact was removed from, or moved to. contact_ids: type: array items: type: string skipped: type: integer description: Contacts left out because the list belongs to a different brand. skipped_brand_mismatch: type: array description: Ids of the skipped contacts. items: type: string ContactList: type: object properties: list_id: type: string format: uuid list_name: type: string team_id: type: string brand_id: type: [string, 'null'] shared_across_brands: type: boolean description: Usable by every brand. owner: type: [string, 'null'] description: User or user-group id that owns the list. `null` means everyone can see it. visibility: type: object description: Who can see the list. Set from `owner`. properties: type: type: string enum: [everyone, users, teams] user_ids: type: array items: type: string user_group_id: type: string callable: type: [boolean, 'null'] compliance: type: [object, 'null'] description: Compliance acknowledgement. properties: user_id: type: string timestamp: type: integer ip: type: string recordings: {} outbound_scripts: type: array items: {} is_seed: type: boolean system_type: type: [string, 'null'] description: Set on built-in lists, such as `unlisted`. delete_protected: type: boolean contact_count: type: integer description: Contacts on the list. In `GET /contact/public/lists` only. created_at: type: integer created_by: type: [string, 'null'] modified_at: type: integer modified_by: type: [string, 'null'] deleted_at: type: [integer, 'null'] deleted_by: type: [string, 'null'] ContactListDetail: allOf: - $ref: '#/components/schemas/ContactList' - type: object properties: import: type: object properties: status: type: string accepted_count: type: integer description: Contacts on the list. total_phone_numbers: type: integer wireless_count: type: integer landline_count: type: integer voip_count: type: integer tollfree_count: type: integer invalid_count: type: integer unknown_count: type: integer email_capable_count: type: integer non_callable_contacts: type: integer description: Contacts that can't be called, such as those on your do-not-contact list. available_to_dial: description: When the list can next be dialed. `Now` when it can be dialed now. last_dial: description: When the list was last dialed. top_carriers: type: array items: {} region_stats: type: array items: {} area_code_distribution: type: array items: {} tollfree_distribution: type: array items: {} timezone_distribution: type: object additionalProperties: true stats_dirty: type: boolean description: The counts are being recalculated. CreateContactList: type: object properties: list_name: type: string default: My List description: Must be unique on your account. compliance: type: boolean description: Send `true` to store a compliance acknowledgement with your user id, the time and your IP address. callable: type: boolean UpdateContactList: type: object properties: list_name: type: string description: Must be unique on your account. recordings: {} callable: type: boolean ContactListDeleted: type: object properties: list_id: type: string format: uuid contacts_deleted: type: integer description: Contacts deleted with the list. 0 unless you sent `delete_contacts`. ContactListMember: type: object properties: _id: type: string description: Send the last one as `after_id` to read the next page. contact_id: type: string format: uuid team_id: type: string field_data: type: array items: $ref: '#/components/schemas/ContactField' phone_numbers: type: array items: type: string main_phone: type: [string, 'null'] example: '+13125550142' location: type: object additionalProperties: true list_ids: type: array items: type: string dnc: type: boolean tags: type: array items: $ref: '#/components/schemas/ContactTag' created_at: type: integer modified_at: type: integer Note: type: object properties: note_id: type: string format: uuid team_id: type: string user_id: type: string description: Author. contact_id: type: string format: uuid call_id: type: [string, 'null'] sms_id: type: [string, 'null'] task_id: type: [string, 'null'] type: type: string default: contact note: type: string description: The note as sanitized HTML. note_text: type: [string, 'null'] description: Plain-text version. ai_source: type: [object, 'null'] additionalProperties: true attachments: type: array items: {} attachment_history: type: array items: {} mentioned: type: array items: type: string created_at: type: integer created_by: type: string modified_at: type: integer modified_by: type: string deleted_at: type: [integer, 'null'] deleted_by: type: [string, 'null'] CreateNote: type: object required: [note] properties: note: type: string description: The note. Basic HTML is allowed. example: '

Asked for a quote on the annual plan.

' note_text: type: string description: Plain-text version. Built from `note` when you leave it out. type: type: string default: contact FollowUp: type: object properties: followup_id: type: string format: uuid team_id: type: string contact_id: type: string format: uuid scheduled_at: type: integer description: When it's due, in epoch milliseconds. status: type: string enum: [pending, triggered, completed, cancelled] description: '`triggered` means its Inbox task has been created.' description: type: [string, 'null'] description: Sanitized HTML. description_text: type: [string, 'null'] assigned_to: type: [string, 'null'] description: User or team id. assigned_type: type: [string, 'null'] enum: [user, team, null] task_id: type: [string, 'null'] description: The Inbox task, once triggered. triggered_at: type: [integer, 'null'] cancelled_at: type: [integer, 'null'] cancelled_by: type: [string, 'null'] created_at: type: integer created_by: type: [string, 'null'] updated_at: type: integer updated_by: type: [string, 'null'] CreateFollowUp: type: object required: [scheduled_at] properties: scheduled_at: type: [integer, string] description: When it's due. Epoch milliseconds, epoch seconds or an ISO 8601 string. Must be in the future. example: '2026-10-04T15:00:00Z' description: type: string description: What to do. Basic HTML is allowed. assigned_to: type: string description: User or team id to assign it to. Defaults to you. assigned_type: type: string enum: [user, team] default: user UpdateFollowUp: type: object properties: scheduled_at: type: [integer, string] description: New due time. Must be in the future. description: type: string description: New description. An empty string clears it. assigned_to: type: string assigned_type: type: string enum: [user, team] FollowUpCancelled: type: object properties: followup_id: type: string format: uuid cancelled: type: boolean enum: [true] message: type: string example: Follow-up cancelled successfully ConsentType: type: string enum: [esign, tcpa_optin, tcpa_optout, sms_optin, sms_optout, sms_optin_confirmed, web_tracking, email_optin, email_optout] ConsentRecord: type: object properties: consent_id: type: string format: uuid team_id: type: string contact_id: type: [string, 'null'] lead_id: type: [string, 'null'] phone_number: type: [string, 'null'] example: '+13125550142' email: type: [string, 'null'] site_id: type: [string, 'null'] page_url: type: [string, 'null'] webform_id: type: [string, 'null'] consent_type: $ref: '#/components/schemas/ConsentType' consent_status: type: string description: '`granted` unless you sent another value. Revocations are `revoked`.' consent_version: type: string default: 1.0.0 consent_method: type: string default: api consent_text: type: [string, 'null'] consent_text_hash: type: [string, 'null'] description: SHA-256 of `consent_text`, prefixed `sha256:`. ip_address: type: [string, 'null'] user_agent: type: [string, 'null'] fingerprint: {} session: {} form_interaction: {} device: {} network: {} third_party: type: object properties: trustedform_cert_url: type: [string, 'null'] trustedform_token: type: [string, 'null'] jornaya_lead_id: type: [string, 'null'] custom_verification_id: type: [string, 'null'] custom_verification_url: type: [string, 'null'] has_screenshot: type: boolean screenshot_ext: type: [string, 'null'] granted_at: type: [integer, 'null'] revoked_at: type: [integer, 'null'] created_at: type: integer ConsentCreated: type: object properties: consent_id: type: string format: uuid consent: $ref: '#/components/schemas/ConsentRecord' CreateConsent: type: object required: [consent_type] properties: consent_type: $ref: '#/components/schemas/ConsentType' phone_number: type: string example: '+13125550142' description: Send this, `email`, or both. email: type: string format: email consent_status: type: string default: granted consent_version: type: string default: 1.0.0 consent_method: type: string default: api description: How consent was collected. consent_text: type: string description: The wording the contact agreed to. ip_address: type: string description: The contact's IP address. Defaults to the caller's. user_agent: type: string description: The contact's browser user agent. Defaults to the caller's. trustedform_cert_url: type: string description: TrustedForm certificate URL. trustedform_token: type: string jornaya_lead_id: type: string CustomField: type: object properties: custom_field_id: type: string format: uuid team_id: type: string brand_id: type: [string, 'null'] type: type: string enum: [string, text, list] display_name: type: string example: Membership tier slug: type: string example: membership_tier sort_index: type: [integer, 'null'] list_items: type: array description: For a `list` field, its options. Send an item's `value` or `display_name` when you create contacts. items: type: object properties: item_id: type: string format: uuid display_name: type: string value: type: string search_slot: type: integer created_at: type: integer created_by: type: [string, 'null'] modified_at: type: integer modified_by: type: [string, 'null'] deleted_at: type: [integer, 'null'] deleted_by: type: [string, 'null'] DncEntry: type: object description: One do-not-contact entry. Each entry holds a phone number or an email, not both. properties: _id: type: string team_id: type: string brand_id: type: [string, 'null'] description: Brand the entry applies to. Null means every brand. phone_number: type: [string, 'null'] example: '+13125550142' email: type: [string, 'null'] description: Stored lowercase. record_id: type: [string, 'null'] description: Your own reference, set by CSV imports. method: type: string description: How the entry was added, for example `api`, `sms` or `voice`. reason: type: string example: api self_added: type: boolean description: True when the recipient opted out themselves. These entries can't be removed through the API. block_inbound: type: boolean description: True when inbound messages from this contact are hidden from the inbox. created_at: type: integer description: Unix time in milliseconds. created_by: type: [string, 'null'] updated_at: type: integer description: Unix time in milliseconds. updated_by: type: [string, 'null'] DncLookup: type: object properties: on_dnc: type: boolean description: True when any number or email sent is listed. phone_numbers: type: array description: The numbers checked, de-duplicated. items: type: string emails: type: array description: The emails checked, lowercased and de-duplicated. items: type: string matched: type: array description: The numbers and emails that are listed. items: type: string data: type: array description: The matching entries. items: $ref: '#/components/schemas/DncEntry' CreateDnc: type: object description: Send `phone_number`, `email` or both. properties: phone_number: description: A number or a list of numbers in E.164 format. Stored as sent. oneOf: - type: string example: '+13125550142' - type: array items: type: string email: description: An email or a list of emails. oneOf: - type: string example: jordan@example.com - type: array items: type: string brand_id: type: string description: Limit the entries to one brand. Without it they apply to every brand. reason: type: string default: api method: type: string default: api description: How the entry was collected. record_id: type: string description: Your own reference for the entry. DncAddResult: type: object properties: added: type: array description: Numbers and emails that weren't listed before this call. items: type: string example: ['+13125550142'] already_listed: type: array description: Numbers and emails that were already listed for this brand. Their entries were updated. items: type: string example: ['+13125550187'] DncRemovedEntry: type: object description: | The entry that was removed. `deleted_at` and `deleted_by` are `null` when nothing was listed to remove. properties: phone_number: type: [string, 'null'] example: '+13125550187' email: type: [string, 'null'] brand_id: type: [string, 'null'] description: '`null` means the entry blocked every brand.' method: type: [string, 'null'] reason: type: [string, 'null'] record_id: type: [string, 'null'] created_at: type: [integer, 'null'] description: Epoch milliseconds. updated_at: type: [integer, 'null'] description: Epoch milliseconds. deleted_at: type: [integer, 'null'] description: Epoch milliseconds when it was removed. deleted_by: type: [string, 'null'] description: The user who made the call. BulkDeleteDnc: type: object description: Send `phone_numbers`, `emails` or both. properties: phone_numbers: type: array items: type: string example: '+13125550142' emails: type: array items: type: string example: jordan@example.com brand_id: type: string description: Remove only this brand's entries. Without it, entries for every brand are removed. DncBulkDeleteResult: type: object properties: deleted: type: array description: Numbers and emails removed. items: type: string skipped: type: array description: Numbers and emails kept because the recipient opted out themselves. items: type: object properties: phone_number: type: [string, 'null'] email: type: [string, 'null'] reason: type: string enum: [self_added_opt_out] PhoneNumber: type: object description: A number on the account. Fields that were never set are omitted. properties: phone_number: type: string example: '+13125550142' team_id: type: string name: type: string status: type: string description: '`ready`, or `pending_payment` until a purchase is paid.' example: ready voice_ivr_id: type: [string, 'null'] description: The phone line the number belongs to. brand_id: type: [string, 'null'] description: Brand of the number's phone line. Null means the account's default brand. campaign_id: type: [string, 'null'] description: The texting campaign (`pool_id`) the number sends under. pool_id: type: [string, 'null'] description: The carrier pool for numbers on your own carrier (BYOC). dial_in: type: boolean voice_enabled: type: boolean sms_enabled: type: boolean rcs_enabled: type: boolean fax: type: boolean is_byoc: type: boolean description: True for numbers on your own connected carrier. features: description: Feature flags set on the number. dni: type: object description: Call-tracking tags for the number. country_iso: type: string example: US area_code: type: string example: '312' state_iso: type: [string, 'null'] city: type: [string, 'null'] geo: type: array description: Longitude and latitude of the number's area. items: type: number tz_offset: type: number description: Hours from UTC for the number's area. tz_offset_dst: type: number healthy: type: boolean description: False when the carrier's health check flags the number. health_scores: type: [object, 'null'] health_check_at: type: integer complaint_at: type: [integer, 'null'] fcc_complaint_count: type: integer description: FCC consumer complaints on file. Returned by the list route only. fcc_complaints: type: array description: The FCC consumer complaints themselves. Returned by the list route only. items: type: object tcr_campaign_id: type: [string, 'null'] description: Campaign registry id of the texting campaign. tcr_status: type: string e911_location_id: type: [string, 'null'] e911_status: type: [string, 'null'] last_used_at: type: integer total_voice: type: integer total_sms: type: integer pending_purchase_at: type: [integer, 'null'] created_at: type: integer description: Unix time in milliseconds. created_by: type: [string, 'null'] RentedPhoneNumbers: type: object properties: numbers: type: array description: The numbers rented. items: type: string example: '+13125550142' PhoneNumberLineAssignment: type: object properties: e911: type: object description: How the number's 911 address changed with the move. properties: old_location_id: type: [string, 'null'] new_location_id: type: [string, 'null'] rebound: type: boolean Call: type: object description: A call record. Fields that were never set are omitted. properties: call_id: type: string root_call_id: type: string description: The first call in a chain of transfers or forwards. leg_id: type: string team_id: type: string user_id: type: [string, 'null'] contact_id: type: [string, 'null'] list_id: type: [string, 'null'] call_direction: type: string enum: [inbound, outbound] call_type: type: string from: type: string example: '+13125550142' to: type: string example: '+13125550187' contact_number: type: string from_country_iso: type: string to_country_iso: type: string ivr_id: type: [string, 'null'] description: The phone line that handled the call. conference_id: type: [string, 'null'] conference_role: type: [string, 'null'] carrier: type: [string, 'null'] record_call: type: boolean recordings: type: array items: type: object created_at: type: integer description: Unix time in milliseconds. started_at: type: [integer, 'null'] answered: type: boolean answered_at: type: [integer, 'null'] abandoned: type: boolean abandoned_at: type: [integer, 'null'] failed: type: boolean completed_at: type: [integer, 'null'] duration: type: number description: Seconds. disposition: description: Outcome code set when the call ended. disposition_at: type: [integer, 'null'] post_call_action: description: What happened after the call, when one was set. seen_at: type: [integer, 'null'] seen_by: type: [string, 'null'] read_at: type: [integer, 'null'] read_by: type: [string, 'null'] CallRecordingUrl: type: object properties: url: type: string format: uri description: Signed download link, valid for five days. recording_id: type: string recording_type: type: string extension: type: string example: .ogg duration: type: number recording_unavailable_reason: type: string enum: [unfunded_at_call_time, negative_balance] description: | Present instead of the other fields when the recording can't be played. `negative_balance`: the call was recorded but your balance is below zero now; add funds and retry. `unfunded_at_call_time`: the balance was too low to record when the call happened, so there is no recording. SmsMessage: type: object description: | A stored message: an inbound text, or one sent from the inbox or with `POST /phone/public/sms/reply`. Sends made with `POST /sms` are not stored here. Fields that were never set are omitted. properties: sms_id: type: string sms_type: type: string enum: [inbound, outbound] team_id: type: string format: uuid user_id: type: [string, 'null'] contact_id: type: [string, 'null'] list_id: type: [string, 'null'] note_id: type: [string, 'null'] description: Thread reads only. from: type: string to: type: string sms_body: type: [string, 'null'] description: Text, or the caption of an MMS. mms_media: type: [array, 'null'] items: $ref: '#/components/schemas/SmsMediaRow' reason: type: [string, 'null'] spam: type: [boolean, 'null'] created_at: type: integer description: Epoch milliseconds. read_at: type: [integer, 'null'] read_by: type: [string, 'null'] seen_at: type: [integer, 'null'] description: When the message was first seen, in epoch milliseconds. seen_by: type: [string, 'null'] description: The user who saw it; `null` when it was seen through Get SMS Thread. hidden_at: type: [integer, 'null'] hidden_by: type: [string, 'null'] SmsMediaRow: type: object description: One file attached to a stored message. properties: media_id: type: string format: uuid ext: type: [string, 'null'] description: File extension without the dot, for example `png`. content_type: type: [string, 'null'] size: type: [integer, 'null'] description: Bytes. width: type: [integer, 'null'] height: type: [integer, 'null'] has_audio: type: [boolean, 'null'] duration_sec: type: [number, 'null'] scan_status: type: string enum: [pending, clean, infected] description: Malware scan result. Inbound files start `pending`. scanned_at: type: [integer, 'null'] url: type: string format: uri description: | Present only when `scan_status` is `clean`. Inbound media gets a signed link that works for 24 hours from the read. Media sent with a reply keeps its `https://mms.dropcowboy.com/...` copy, which is deleted 3 days after sending. SmsReplyResult: description: | The stored outbound row. `delivered: true` and `reason: delivered` record that the carrier accepted the message; they do not confirm the handset received it. allOf: - $ref: '#/components/schemas/SmsMessage' - type: object properties: parts: type: [integer, 'null'] media_urls: type: [array, 'null'] items: type: string format: uri description: CDN copies of the attached files, deleted 3 days after sending. delivered: type: boolean action: type: string campaign_id: type: [string, 'null'] ivr_id: type: [string, 'null'] entry_id: type: [string, 'null'] SendSmsReply: type: object description: | Sends before answering. Give `sms_body`, media, or both; with media, `sms_body` is the caption and is sent as written. required: [phone_number, caller_id] anyOf: - required: [sms_body] - required: [media_urls] properties: media_urls: minItems: 1 - required: [media_ids] properties: media_ids: minItems: 1 properties: phone_number: type: string pattern: '^\+[1-9]\d{1,14}$' description: Recipient. A contact is created if none has this number. example: '+13125550142' caller_id: type: string pattern: '^\+[1-9]\d{1,14}$' description: Sender. Must be an SMS-enabled number on your account. example: '+12125550100' sms_body: type: string maxLength: 1600 description: Text, or the caption for media. `body` and `message` are not accepted here. media_urls: $ref: '#/components/schemas/MmsMediaUrls' media_ids: $ref: '#/components/schemas/MmsMediaIds' contact_id: type: string description: Contact to record the message against. MmsMediaUrls: type: array maxItems: 10 description: | `https` URLs of files to send as MMS. Up to 10 files across `media_urls` and `media_ids`. Each file is fetched once with no redirects followed, must be 1 MiB or smaller (5 MiB for the whole send) and JPEG, PNG, GIF, WAV or MP3 by its bytes. Private hosts and URLs with credentials are refused. items: type: string format: uri pattern: '^https://' maxLength: 2048 MmsMediaIds: type: array maxItems: 10 description: | `media_id` values from the Media API to send as MMS. The Media API stores MP3 and WAV only, so this carries audio; send images with `media_urls`. Up to 10 files across both fields, 1 MiB each. items: type: string format: uuid WebhookSmsMedia: type: object description: File metadata only. Webhooks never carry a link. properties: media_id: type: string format: uuid content_type: type: [string, 'null'] size: type: [integer, 'null'] width: type: [integer, 'null'] height: type: [integer, 'null'] duration_sec: type: [number, 'null'] scan_status: type: string enum: [pending, clean, infected] description: | Inbound files are scanned after they arrive, so `contact.msg.received` usually shows `pending`. No event fires when the scan finishes. MessageEventData: type: object description: | `data` of `contact.msg.received` and `contact.msg.sent`. Each id is sent with the record it points to next to it (`team`, `user`, `contact`, `sms`). required: [sms_id, team_id] properties: sms_id: type: string format: uuid sms: $ref: '#/components/schemas/WebhookSms' team_id: type: string format: uuid team: $ref: '#/components/schemas/WebhookTeam' user_id: type: [string, 'null'] description: >- On `contact.msg.sent`, the teammate who sent the text. On `contact.msg.received`, the teammate the text is routed to: usually whoever last texted the contact from that number, otherwise the number's or the team's owner. user: $ref: '#/components/schemas/WebhookUser' contact_id: type: [string, 'null'] description: Absent on texts sent by the Shopify integration. contact: $ref: '#/components/schemas/WebhookContact' received_at: type: integer description: '`contact.msg.received` only. Epoch milliseconds.' sent_at: type: integer description: '`contact.msg.sent` only. Epoch milliseconds.' ivr_id: type: [string, 'null'] description: '`contact.msg.received` only. The phone line that received the text.' MintEmbedToken: type: object properties: site_id: type: string format: uuid description: | A UUID you choose once per deployed app and keep stable. It partitions the embed inbox: texts and calls placed with this token are stamped with it, and `GET /phone/embed/inbox` lists only that site's conversations. Any UUID version, any case. Optional; a value that is not a UUID is rejected with 400 `invalid_site_id`. It is not a chat site id and is not created in the dashboard. example: 3f8a2c1e-9b4d-4e7a-8c1f-2d3e4f5a6b7c sub: type: string description: Your own identifier for the end user, carried in the token. scope: description: Space-separated string or array. Defaults to `dialer:webrtc`. oneOf: - type: string - type: array items: type: string example: dialer:webrtc ttl_seconds: type: integer maximum: 3600 default: 3600 test: type: boolean description: Marks the token as a test token. EmbedMintError: description: | Problem details from `POST /phone/public/embed/token`. When the mint refuses a request on purpose, `details.code` names the reason; `type` stays the generic status code. allOf: - $ref: '#/components/schemas/Error' - type: object properties: details: type: object required: [code] properties: code: type: string enum: [invalid_site_id, insufficient_balance, byoc_required, site_token_not_allowed] EmbedToken: type: object properties: token: type: string description: RS256 JWT. Never put it in a URL. expires_at: type: integer description: Epoch milliseconds. jti: type: string format: uuid pool_id: type: string format: uuid DetectionWelcome: type: object required: [type, payload] properties: type: const: welcome payload: type: object properties: call_id: type: string description: Your identifier, echoed on every event and webhook. audio_encoding: type: string enum: [pcm16, mulaw] default: pcm16 sample_rate_hz: type: integer enum: [8000, 16000] default: 8000 strategy: type: string enum: [standard, beep_only, live_check] default: standard description: | `standard` gives live-person / voicemail verdicts with refinements and beep timing. `beep_only` reports the voicemail beep only. `live_check` is a faster live-person check without transcript-based refinement. detection_timeout_sec: type: integer default: 45 keep_alive: type: boolean default: false description: | Keep the socket open after `stop` so you can pool it and send the next welcome on it. Without it the server closes the socket with 1000 `stop`. A pooled socket is closed with 1001 `idle` if no welcome arrives within 60 seconds of `stopped`. webhook: type: object required: [url] properties: url: type: string format: uri description: Public HTTPS URL. Private and loopback addresses are refused. events: type: array description: Event names to deliver. Empty means all. items: type: string headers: type: object additionalProperties: type: string DetectionStop: type: object required: [command] properties: command: const: stop DetectionEvent: type: object properties: type: const: detection event: type: string enum: [ready, detection, beep, screening, transcript, speech_start, speech_end, silence, timeout, error, stopped, close] payload: type: object description: | Every payload has `session_id` and `timestamp_ms`, plus `call_id` if you sent one. Per event: - `detection`: `type` (`live_person`, `voicemail`, `silence`, or another value you should treat as no result), `confidence` 0 to 1, `beep_detected`, `is_final`, `is_refinement`, optional `transcript` and `scores`. - `beep`: `timestamp_ms`, `frequency_hz`, `duration_ms`, `confidence`, `is_final`. Start your voicemail message now. - `screening`: a call screener answered; `screening_type`, `intent`, `suggested_action`, `confidence`, optional `transcript`, `prompts` and `suggested_dtmf`. - `transcript`, `speech_start`, `speech_end`, `silence`: progress signals while the call is being classified. Safe to ignore. - `timeout`: no verdict within `detection_timeout_sec`. - `error`: `error`, `code`, `recoverable`. - `close`: `reason`, `duration_ms`. properties: session_id: type: string call_id: type: string timestamp_ms: type: integer type: type: string confidence: type: number is_final: type: boolean is_refinement: type: boolean DetectionWebhook: type: object description: Body of a detection webhook. The event payload is in `data`. properties: delivery_id: type: string format: uuid event: type: string emitted_at: type: string format: date-time session_id: type: string call_id: type: string source: type: string enum: [ws, twilio] twilio_call_sid: type: string data: type: object WebhookEnvelope: type: object description: | Every webhook body. Headers: `X-Signature` (`sha256=` plus the hex HMAC-SHA256 of `{X-Timestamp}.{raw body}`, keyed with the subscription's signing secret), `X-Timestamp` (Unix seconds), `X-Signature-Version: v1`, `X-Event-Id` and `X-Attempt`. Reject timestamps more than 300 seconds old and deduplicate on `event_id`. required: [event_id, event, event_at, data] properties: event_id: type: string format: uuid description: Same on every attempt and for every subscription that receives the event. event: type: string event_at: type: integer description: Epoch milliseconds. operation_id: type: string format: uuid description: >- Only on events from a bulk change (an import, or adding many contacts to a list). Every event from that one change carries the same value. data: type: object description: | The event's fields. Wherever `data` carries one of the ids below, the matching record is attached next to it under the name shown, so you rarely need a follow-up request. The id itself stays in `data`. Records are looked up only within the event's team; a `user` is also looked up in the parent account when the team is a sub-account. When the id is null, the record is null too. A record that cannot be found is `{}`: for example one removed for good, one still unsaved, one that belongs to another team, or an id that is not a string or not well formed. A record that was deleted but is still kept, such as a deleted contact or list, arrives in full. | id | record | |---|---| | `team_id` | `team` (`WebhookTeam`) | | `user_id` | `user` (`WebhookUser`) | | `contact_id` | `contact` (`WebhookContact`) | | `list_id` | `list` (`WebhookList`) | | `call_id` | `call` (`WebhookCall`) | | `sms_id` | `sms` (`WebhookSms`) | | `email_id` | `email` (`WebhookEmail`) | | `fax_id` | `fax` (`WebhookFax`) | | `note_id` | `note` (`WebhookNote`) | | `document_id` | `document` (`WebhookDocument`) | | `call_recording_id` | `call_recording` (`WebhookCallRecording`) | | `entry_id` | `timeline_entry` (`WebhookTimelineEntry`) | | `import_id` | `import` (`WebhookImport`) | | `export_id` | `export` (`WebhookExport`) | | `integration_id` | `integration` (`WebhookIntegration`) | | `drop_id` | `campaign_session` (`WebhookCampaignSession`) | Fields whose names start with `__` or end in `_token` or `_secret` are never sent. WebhookTeam: type: [object, 'null'] description: Attached to `data` as `team` when `data.team_id` is set. properties: team_id: type: string format: uuid name: type: string WebhookUser: type: [object, 'null'] description: Attached to `data` as `user` when `data.user_id` is set. The teammate who acted or owns the record. properties: user_id: type: string format: uuid username: type: string first_name: type: [string, 'null'] last_name: type: [string, 'null'] email: type: [string, 'null'] WebhookTag: type: object properties: tag_id: type: string format: uuid label: type: string color: type: [string, 'null'] text_color: type: [string, 'null'] WebhookContact: type: [object, 'null'] description: | Attached to `data` as `contact` when `data.contact_id` is set. Each standard field the contact has a value for appears at the top level under its field type (`first_name`, `email`, `mobile_phone`, ...). Fields with no value are omitted. properties: contact_id: type: string format: uuid uid: type: [string, 'null'] description: Your own id for the contact, if you set one on import or create. main_phone: type: [string, 'null'] description: E.164. first_name: type: string last_name: type: string email: type: string company: type: string website: type: string mobile_phone: type: string secondary_phone: type: string home_phone: type: string office_phone: type: string other_phone: type: string fax_number: type: string address: type: string suite/apt: type: string city: type: string state: type: string zip_code: type: string country: type: string lead_source: type: string dob: type: string gender: type: string preferred_language: type: string notes: type: string custom: type: object additionalProperties: true description: Custom field values keyed by the custom field's slug. tags: type: array items: $ref: '#/components/schemas/WebhookTag' list_ids: type: array items: type: string format: uuid description: Every list the contact is on when the event is sent. WebhookList: type: [object, 'null'] description: Attached to `data` as `list` when `data.list_id` is set. properties: list_id: type: string format: uuid list_name: type: string callable: type: boolean description: Whether the list's contacts may be called. WebhookCall: type: [object, 'null'] description: Attached to `data` as `call` when `data.call_id` is set. properties: call_id: type: string call_type: type: [string, 'null'] from: type: [string, 'null'] to: type: [string, 'null'] call_direction: type: [string, 'null'] enum: [inbound, outbound, null] disposition: type: [object, 'null'] description: The disposition set on the call, if any. properties: id: type: string sentiment: type: [string, 'null'] ringing_at: type: [integer, 'null'] description: Epoch milliseconds. answered_at: type: [integer, 'null'] description: Epoch milliseconds. hangup_at: type: [integer, 'null'] description: Epoch milliseconds. answered: type: [boolean, 'null'] raw_duration: type: [integer, 'null'] description: The call's duration in seconds, rounded to the nearest whole second. Null when unknown. WebhookSms: type: [object, 'null'] description: | Attached to `data` as `sms` when `data.sms_id` is set. Media is metadata only, with no link; read the message with `GET /phone/public/sms/{sms_id}` for a download link. properties: sms_id: type: string to: type: [string, 'null'] from: type: [string, 'null'] delivered: type: [boolean, 'null'] sms_body: type: [string, 'null'] sms_type: type: [string, 'null'] dnc: type: [boolean, 'null'] media: type: array description: Present only on an MMS. items: $ref: '#/components/schemas/WebhookSmsMedia' WebhookEmail: type: [object, 'null'] description: Attached to `data` as `email` when `data.email_id` is set. properties: email_id: type: string to: $ref: '#/components/schemas/WebhookEmailAddresses' from: $ref: '#/components/schemas/WebhookEmailAddresses' subject: type: [string, 'null'] preview: type: [string, 'null'] WebhookEmailAddresses: type: [string, array, 'null'] description: A single address, or for received mail a list of `{address, name}`. items: type: object properties: address: type: string name: type: [string, 'null'] WebhookFax: type: [object, 'null'] description: Attached to `data` as `fax` when `data.fax_id` is set. properties: fax_id: type: string message: type: [string, 'null'] status: type: [string, 'null'] subject: type: [string, 'null'] WebhookNote: type: [object, 'null'] description: Attached to `data` as `note` when `data.note_id` is set. properties: note_id: type: string type: type: [string, 'null'] note: type: [string, 'null'] WebhookDocument: type: [object, 'null'] description: Attached to `data` as `document` when `data.document_id` is set. properties: document_id: type: string document_type: type: [string, 'null'] filename: type: [string, 'null'] WebhookCallRecording: type: [object, 'null'] description: Attached to `data` as `call_recording` when `data.call_recording_id` is set. properties: call_recording_id: type: string call_id: type: [string, 'null'] recording_type: type: [string, 'null'] from: type: [string, 'null'] to: type: [string, 'null'] duration: type: [integer, 'null'] description: Seconds. WebhookTimelineEntry: type: [object, 'null'] description: Attached to `data` as `timeline_entry` when `data.entry_id` is set. properties: entry_id: type: string type: type: [string, 'null'] media_id: type: [string, 'null'] tp: type: [integer, 'null'] description: When the entry happened, epoch milliseconds. disposition: type: [object, string, 'null'] WebhookImport: type: [object, 'null'] description: Attached to `data` as `import` when `data.import_id` is set. properties: import_id: type: string type: type: [string, 'null'] status: type: [string, 'null'] WebhookExport: type: [object, 'null'] description: Attached to `data` as `export` when `data.export_id` is set. properties: export_id: type: string export_type: type: [string, 'null'] status: type: [string, 'null'] WebhookIntegration: type: [object, 'null'] description: Attached to `data` as `integration` when `data.integration_id` is set. properties: integration_id: type: string integration_type: type: [string, 'null'] WebhookCampaignSession: type: [object, 'null'] description: Attached to `data` as `campaign_session` when `data.drop_id` is set. The send behind the event. properties: team_id: type: string user_id: type: [string, 'null'] campaign_id: type: [string, 'null'] contact_id: type: [string, 'null'] list_id: type: [string, 'null'] phone_number: type: [string, 'null'] caller_id: type: [string, 'null'] product_code: type: [string, 'null'] dispo: type: [object, 'null'] properties: status: type: [string, 'null'] reason: type: [string, 'null'] delivered_at: type: [integer, 'null'] description: Epoch milliseconds. dnc: type: [boolean, 'null'] DeliveryStatusData: type: object description: | `data` for `contact.rvm.status` and `contact.sms.status`. A voicemail sent through the Drop Cowboy integration carries only `team_id`, `user_id`, `contact_id`, `entry_id` and `status`. required: [team_id, status] properties: team_id: type: string format: uuid team: $ref: '#/components/schemas/WebhookTeam' user_id: type: [string, 'null'] user: $ref: '#/components/schemas/WebhookUser' contact_id: type: [string, 'null'] contact: $ref: '#/components/schemas/WebhookContact' drop_id: type: [string, 'null'] description: >- The send, a 24-character hex id. Null when the request was refused before anything was sent, for example `3027` (Idempotency Key Conflict). campaign_session: $ref: '#/components/schemas/WebhookCampaignSession' campaign_id: type: [string, 'null'] description: >- The campaign the send belongs to. For API sends this is your team's API campaign, which holds every API send, so it is the same on each one; match events to requests with `drop_id`. list_id: type: [string, 'null'] list: $ref: '#/components/schemas/WebhookList' campaign_type: type: [string, 'null'] enum: [rvm, sms, mms, voice_broadcast, ai_broadcast, null] description: '`mms` for a text send with media; RCS reports `sms`.' status: type: string enum: [success, failure] reason: type: string reason_code: type: [integer, 'null'] description: '`0` when delivered. See [Outcomes](https://www.dropcowboy.com/developers/api/outcomes).' to: type: [string, 'null'] description: Null when a refused request named no number, or more than the limit. from: type: [string, 'null'] frequency_limit: $ref: '#/components/schemas/FrequencyLimitHit' proof_of_delivery_url: type: string format: uri description: | Present when a voicemail system took the call: a ringless voicemail with `reason_code` 0, 4001 or 4002, or a voice broadcast or AI call that ended with 0 in a mailbox. The link can be sent before the recording is ready; until then it answers `404` with `Retry-After`. `contact.rvm.receipt` tells you when it is ready. See `GET /campaign/public/receipts/{token}`. entry_id: type: string description: Drop Cowboy integration sends only. The contact timeline entry for the voicemail. timeline_entry: $ref: '#/components/schemas/WebhookTimelineEntry' FrequencyLimitHit: type: object description: | Present only on 4013 (Too Many Attempts) failures: the frequency rule the number hit. Not included in the `callback_url` body. properties: max_attempts: type: integer description: Attempts allowed per number in the window. window_days: type: number description: Window length in days. attempts_in_window: type: integer description: Attempts already made to this number within the window. DeliveryReceiptData: description: | Everything `contact.rvm.status` carried for the drop (including `frequency_limit` if it had one), plus `proof_of_delivery_url` and `receipt_ready_at`. The URL is a new link, not the one on the status event; both play the same recording. allOf: - $ref: '#/components/schemas/DeliveryStatusData' - type: object required: [drop_id, proof_of_delivery_url, receipt_ready_at] properties: receipt_ready_at: type: string format: date-time description: When the recording behind the link was stored. DeliveryReceiptLink: type: object properties: drop_id: type: string reason_code: type: integer enum: [0, 4001, 4002] proof_of_delivery_url: type: string format: uri expires_at: type: string format: date-time description: When this link stops working (7 days from now, or the end of the drop's 7-day window if that is sooner). receipt_ready_at: type: [string, 'null'] format: date-time description: When the recording was stored; null while it is not ready. DeliveryReceiptLinkError: type: object properties: error: type: string enum: [not_ready, not_found, expired, server_error] message: type: string EmailStatusData: type: object description: '`data` for `contact.email.status`.' required: [team_id, status] properties: team_id: type: string format: uuid team: $ref: '#/components/schemas/WebhookTeam' contact_id: type: [string, 'null'] contact: $ref: '#/components/schemas/WebhookContact' campaign_id: type: [string, 'null'] session_id: type: [string, 'null'] description: The campaign send behind the email, when it came from a campaign. email_id: type: [string, 'null'] email: $ref: '#/components/schemas/WebhookEmail' to: type: [string, 'null'] description: The recipient's address. from: type: [string, 'null'] description: The sending address. status: type: string description: '`success`, `failure`, `delivered`, `opened`, `clicked`, `bounced` or `complained`.' reason: type: [string, 'null'] description: Set on failures, bounces and complaints, for example `hard_bounce`, `soft_bounce` or `complaint`. reason_code: type: [integer, 'null'] AiAgentCallData: type: object description: | `data` for `ai_agent.call.completed`, `ai_agent.call.failed` and every `ai_agent.outcome.*` event. Inbound calls (`direction: inbound`) carry `agent_id`, `call_id`, `contact_id`, `ivr_id`, `answered` and `duration`. Outbound AI voice broadcast calls carry no `direction`, `agent_id` or `call_id`; they carry `campaign_id`, `session_id`, `drop_id`, `phone_number` and the AI usage fields, and the contact is on `campaign_session.contact_id`. required: [team_id] properties: team_id: type: string format: uuid team: $ref: '#/components/schemas/WebhookTeam' outcome: type: [string, 'null'] description: | The outcome the call ended with. On outbound calls one of `confirm`, `voicemail`, `voicemail_delivered`, `no_response`, `opt_out`, `transfer`, `incomplete`, `timeout`, `identity_failed`, or a failure reason such as `no_answer`, `busy`, `voicemail_full`, `voicemail_not_setup`, `carrier_intercept`, `ivr`, `call_screening`, `spam`, `silence`, `canceled` or `failed`. On inbound calls it is the label the agent recorded, if any, and is otherwise null. direction: type: string enum: [inbound] description: Inbound calls only. agent_id: type: [string, 'null'] description: Inbound calls only. The AI agent that took the call. call_id: type: [string, 'null'] description: Inbound calls only. call: $ref: '#/components/schemas/WebhookCall' contact_id: type: [string, 'null'] description: Inbound calls only. Null when the caller matches no contact. contact: $ref: '#/components/schemas/WebhookContact' ivr_id: type: [string, 'null'] description: Inbound calls only. The phone line that was dialed. answered: type: boolean description: Inbound calls only. False exactly when the event is `ai_agent.call.failed`. duration: type: number description: Inbound calls only. Seconds from answer to hangup, fractional; 0 if unanswered. campaign_id: type: [string, 'null'] description: Outbound only. session_id: type: [string, 'null'] description: Outbound only. The campaign run. drop_id: type: string description: Outbound only. The send (a 24-character hex id), the same `drop_id` as on `contact.rvm.status`. campaign_session: $ref: '#/components/schemas/WebhookCampaignSession' phone_number: type: [string, 'null'] description: Outbound only. The number called, E.164. ai_duration_sec: type: number description: Outbound only. Seconds the AI agent was on the call; 0 if it never spoke. ai_cost: type: number description: Outbound only. AI usage charge in US dollars, billed per started minute of `ai_duration_sec`. transcript_length: type: integer description: Outbound only. Number of turns in the conversation transcript. function_calls_count: type: integer description: Outbound only. Number of tool calls the agent made. identity_verified: type: [boolean, 'null'] description: Outbound only. Whether the contact passed identity verification. Absent when the agent did not verify identity. PhoneLine: type: object description: A phone line. Its `ivr_id` is the `phone_line_id` used elsewhere. properties: ivr_id: type: string format: uuid team_id: type: string format: uuid name: type: string type: type: string default: type: boolean availability: $ref: '#/components/schemas/LineAvailability' rules: type: array items: $ref: '#/components/schemas/LineRule' after_hour_rules: type: array items: $ref: '#/components/schemas/LineRule' campaign_id: type: [string, 'null'] description: Registered texting campaign this line sends under. rcs_enabled: type: boolean sms_enabled_count: type: integer description: Only with `statistics=true`. rcs_enabled_count: type: integer description: Only with `statistics=true`. LineAvailability: type: object properties: tz: type: string example: America/Denver days: type: array minItems: 7 maxItems: 7 description: Sunday first. items: type: object properties: open: type: boolean start: type: string example: '9.0' end: type: string example: '17.0' LineRule: type: object properties: start_rule: type: boolean description: Runs when the call arrives. end_rule: type: boolean description: Runs when the caller presses no key. user_input: type: string description: Key the caller presses to reach this step in a menu. action: type: string enum: [Queue, AI Agent, Forward To, Forward + Voicemail, Voicemail, Play, Say, Hang-up, Sub-IVR, Trigger Automation] action_data: type: object description: | Settings for the action. `AI Agent` takes `agent_id` (a published agent from `GET /agents/public/agents`). `Say` and `Voicemail` take `say`. `Forward To` takes `route_to`. properties: agent_id: type: string format: uuid say: type: string route_to: type: string description: The number to forward to, in E.164. example: '+13125550118' CreatePhoneLine: type: object properties: name: type: string type: type: string rules: type: array items: $ref: '#/components/schemas/LineRule' after_hour_rules: type: array items: $ref: '#/components/schemas/LineRule' default: type: boolean availability: $ref: '#/components/schemas/LineAvailability' sms_rules: type: array items: type: object campaign_id: type: string description: Registered texting campaign to send under. brand_id: type: string format: uuid rcs_enabled: type: boolean rcs_sender_name: type: string fax: type: boolean RentPhoneNumbers: type: object required: [numbers] properties: numbers: type: array description: Numbers to rent, in E.164. items: type: string pattern: '^\+[1-9]\d{1,14}$' example: '+13125550142' example: ['+13125550120'] voice_ivr_id: type: string description: Phone line to put the rented numbers on. Without it they join the default line. provider: type: string services: type: string example: voice,sms pool_id: type: string format: uuid description: Carrier pool to rent from. Ignored on Building Blocks accounts, which always use the account's default pool. IntegrationReadiness: type: object properties: force_byoc: type: boolean building_blocks_enabled: type: boolean description: > Building Blocks is on. It is on for every team billed for its own carrier (BYOC), so it matches `force_byoc`. embed_resolve_contact_consent: type: boolean description: > Whether embedded texting and callbacks may use the consent already on a contact when the request sends `contact_id` without `consent_id`. Off unless you turn it on with `POST /register/public/building-blocks/contact-consent`. usage_plan: type: [string, 'null'] byoc: $ref: '#/components/schemas/ByocStatus' pool_id: type: [string, 'null'] format: uuid example: 8f3e1a9c-2d4b-4e7a-9c1f-5b6a7c8d9e0f numbers: type: object voices: type: object properties: count: type: integer agents: type: object properties: count: type: integer published: type: integer funds: type: object properties: available: type: number balance: type: number reserved: type: number funds_ok: type: boolean allotment: type: object description: > Included usage left this period, keyed by product. Only the Builder plan includes metered usage; on other plans every product except `email` reports a `cap` of 0, and usage is paid from prepaid funds. additionalProperties: type: object properties: cap: type: integer remaining: type: integer embed_ready: type: boolean next_actions: type: array items: type: string EnableBuildingBlocksResult: type: object properties: success: type: boolean building_blocks_enabled: type: boolean plan_switched: type: boolean plan_id: type: [string, 'null'] pool_id: type: [string, 'null'] format: uuid EmbedContactConsentSettingResult: type: object properties: success: type: boolean embed_resolve_contact_consent: type: boolean ByocStatus: type: object properties: connected: type: boolean providers: type: array items: type: object properties: provider: type: string integration_type: type: string integration_id: type: [string, 'null'] format: uuid enabled: type: boolean default: type: boolean pool_id: type: [string, 'null'] format: uuid pool_id: type: [string, 'null'] format: uuid ByocConnected: type: object properties: connected: type: boolean enum: [true] provider: type: string integration_type: type: string description: Same value as `provider`. pool_id: type: [string, 'null'] format: uuid public_data: type: object properties: channels: type: [integer, 'null'] cps: type: [integer, 'null'] enabled: type: [boolean, 'null'] default: type: [boolean, 'null'] pool_id: type: [string, 'null'] format: uuid use_edge: type: [boolean, 'null'] ByocDisconnected: type: object properties: disconnected: type: boolean enum: [true] provider: type: string integration_type: type: string description: Same value as `provider`. ByocConnect: type: object required: [provider, credentials] properties: provider: type: string enum: [twilio, thinq, telnyx, signalwire, plivo, bandwidth, vonage, sinch, flowroute, custom] credentials: type: object additionalProperties: true channels: type: integer cps: type: integer enabled: type: boolean default: type: boolean PlaygroundToken: type: object required: [token, expires_at, jti] properties: token: type: string expires_at: type: integer description: Unix epoch milliseconds jti: type: string format: uuid example: d1f3a8e2-7c4b-4f9a-9d22-9c1e2f3a4b5c pool_id: type: [string, 'null'] format: uuid example: 8f3e1a9c-2d4b-4e7a-9c1f-5b6a7c8d9e0f Agent: type: object properties: agent_id: type: string format: uuid team_id: type: string format: uuid name: type: string description: type: [string, 'null'] active: type: boolean is_draft: type: boolean description: Drafts cannot take calls. Agents created through the API are not drafts unless you ask. voice_id: type: [string, 'null'] format: uuid language: type: [string, 'null'] think: $ref: '#/components/schemas/AgentThink' defaults: type: object description: Behaviour defaults such as `mode`. published_version: type: [integer, 'null'] published_at: type: [integer, 'null'] description: Epoch milliseconds of the last publish. created_at: type: integer AgentTemplate: type: object properties: template_id: type: string description: Pass to `POST /agents/public/agents/from-template`. example: inbound-after-hours name: type: string description: type: string direction: type: string enum: [inbound, outbound] category: type: string KnowledgeBase: type: object properties: knowledge_base_id: type: string format: uuid name: type: string description: type: string ai_audience: type: string enum: [internal, public] description: | `public` content may be read to customers. `internal` is for agents that help your staff. status: type: string enum: [active, archived] stats: type: object properties: document_count: type: integer ready_count: type: integer failed_count: type: integer chunk_count: type: integer created_at: type: integer description: Epoch milliseconds. updated_at: type: integer description: Epoch milliseconds. CreateKnowledgeBase: type: object required: [name] properties: name: type: string maxLength: 200 description: type: string maxLength: 2000 ai_audience: type: string enum: [internal, public] UpdateKnowledgeBase: type: object properties: name: type: string maxLength: 200 description: type: string maxLength: 2000 ai_audience: type: string enum: [internal, public] status: type: string enum: [active, archived] KnowledgeDocument: type: object properties: knowledge_document_id: type: string format: uuid knowledge_base_id: type: string format: uuid title: type: string filename: type: [string, 'null'] mime_type: type: [string, 'null'] byte_size: type: [integer, 'null'] source: type: string description: How the document was added, for example `upload` or `compose` (inline markdown). ai_audience: type: string enum: [internal, public] upload_confirmed: type: boolean ingest_status: type: string enum: [pending, processing, ready, failed] ingest_error: type: [string, 'null'] description: Why processing failed. Null unless `ingest_status` is `failed`. chunk_count: type: integer created_at: type: integer description: Epoch milliseconds. updated_at: type: integer description: Epoch milliseconds. AddKnowledgeDocument: type: object description: Send one of `markdown`, `source_url` or `filename`. properties: title: type: string description: Required with `markdown`. Defaults to the file name otherwise. markdown: type: string description: Inline document text, up to 1 MB. source_url: type: string format: uri maxLength: 2048 description: Public http(s) URL to fetch, up to 25 MB. filename: type: string maxLength: 255 description: Plain file name. Its extension must match `mime_type`. mime_type: type: string enum: - application/pdf - application/msword - application/vnd.openxmlformats-officedocument.wordprocessingml.document - application/vnd.ms-excel - application/vnd.openxmlformats-officedocument.spreadsheetml.sheet - application/vnd.ms-powerpoint - application/vnd.openxmlformats-officedocument.presentationml.presentation - application/rtf - text/rtf - application/vnd.oasis.opendocument.text - text/html - text/plain - text/markdown - text/csv - application/json description: Required with `filename`. Optional with `source_url`, where the server's own type wins. byte_size: type: integer maximum: 26214400 description: Required with `filename`. KnowledgeUploadTicket: type: object required: [knowledge_document_id, upload] properties: knowledge_document_id: type: string format: uuid knowledge_document: $ref: '#/components/schemas/KnowledgeDocument' upload: type: object properties: method: const: PUT url: type: string format: uri description: Presigned URL. It expires, so upload promptly. headers: type: object description: Send exactly these headers with the PUT. additionalProperties: type: string max_bytes: type: integer KnowledgeQueryResponse: type: object properties: query: type: string results: type: array items: type: object properties: chunk_id: type: [string, 'null'] knowledge_base_id: type: string format: uuid knowledge_document_id: type: string format: uuid chunk_index: type: [integer, 'null'] text: type: string score: type: [number, 'null'] heading: type: [string, 'null'] section_path: type: [string, 'null'] page: type: [integer, 'null'] citation: type: object properties: url: type: [string, 'null'] article_title: type: [string, 'null'] section_title: type: [string, 'null'] AgentThink: type: object description: How the agent reasons and what it may do. Unlisted keys are stored as-is. properties: llm_instructions: type: string description: | System prompt. `{{contact.first_name}}` style placeholders are filled per call. Generated for you when `prompt_spec` is set. prompt_spec: type: object description: Structured prompt inputs compiled into `llm_instructions`. properties: role: type: string tone: type: string objectives: type: array items: type: string intake_fields: type: array items: type: string prohibitions: type: array items: type: string business_context: type: string additional_instructions: type: string llm_functions: type: array description: HTTP tools the agent can call during a conversation. items: type: object properties: name: type: string description: type: string url: type: string format: uri method: type: string headers: type: object parameters: type: object description: JSON Schema of the arguments. mcp_servers: type: array items: type: object properties: name: type: string transport: type: string enum: [streamable-http, sse] url: type: string format: uri headers: type: object call_tools: type: object description: Enable built-in call controls such as `hangup` and `send_dtmf`. knowledge: type: object description: | Knowledge bases the agent searches while it talks. With `mode: selected` (the default when `knowledge_base_ids` is set) only those are searched; `all` searches every knowledge base on the account with the same audience. Publishing checks the selected knowledge bases and fails with `400` if their content reads like instructions for staff rather than answers for callers. properties: mode: type: string enum: [selected, all] knowledge_base_ids: type: array items: type: string format: uuid audience: type: string enum: [public, internal] default: public description: Match the `ai_audience` of the knowledge bases. Use `public` for agents that talk to customers. CreateAgent: type: object required: [name] properties: name: type: string description: type: string voice_id: type: string format: uuid description: See `GET /voice/public/voices`. language: type: string example: en mode: type: string description: Stored as `defaults.mode`. think: $ref: '#/components/schemas/AgentThink' identity: type: object description: Caller identity verification settings. defaults: type: object contact_tools: type: object description: Which contact fields the agent may read and write. routing_targets: type: array description: Where the agent may transfer calls. items: type: object active: type: boolean default: true UpdateAgent: type: object description: Send only the fields you want to change. properties: name: type: string description: type: string voice_id: type: string format: uuid language: type: string mode: type: string think: description: | Replaces the whole `think` object, not individual keys. Read the agent first and send `think` back with your changes, or you will drop its prompt, tools and knowledge settings. allOf: - $ref: '#/components/schemas/AgentThink' identity: type: object defaults: type: object contact_tools: type: object routing_targets: type: array items: type: object active: type: boolean asr_enabled: type: boolean description: | Turns the live call transcript on or off. Leave it out to keep the current setting. Only JSON `true` turns it on; any other value, including the string `"true"`, turns it off. post_call_automation_flow_ids: type: array description: Automation flows to run after each call. items: type: string CreateAgentFromTemplate: type: object required: [template_id, name] properties: template_id: type: string description: From `GET /agents/public/templates`. name: type: string description: type: string active: type: boolean Media: type: object properties: _id: type: string media_id: type: string format: uuid team_id: type: string name: type: [string, 'null'] type: type: [string, 'null'] description: Free-form label you set on create, for example `rvm`. media_exists: type: boolean description: True once the audio is stored and the entry is playable. src_url: type: [string, 'null'] description: The `url` the audio was fetched from, when created from a URL. requested_at: type: integer description: Epoch milliseconds. Set when API use was requested for this file. approved_at: type: integer description: Epoch milliseconds. Set when the file is approved for API sends. created_at: type: integer description: Epoch milliseconds. created_by: type: string description: User who created the entry. modified_at: type: integer description: Epoch milliseconds. modified_by: type: string deleted_at: type: [integer, 'null'] description: Epoch milliseconds. Set when the entry was deleted. deleted_by: type: [string, 'null'] MediaUpdated: type: object properties: media_id: type: string format: uuid team_id: type: string name: type: string description: Present when sent. type: type: string description: Present when sent. modified_at: type: integer description: Epoch milliseconds. modified_by: type: string UpdateMedia: type: object properties: name: type: string type: type: string description: Free-form label. CreateMedia: type: object description: | Send `url` or `signed_upload: true`. With neither, only the entry is created (`media_exists: false`, hidden from the list) and the response is just `media_id`. properties: name: type: string type: type: string description: Free-form label, for example `rvm`. url: type: string format: uri description: Public audio URL to fetch. ext: type: string description: Stored file extension when using `url`, `.wav` (default) or `.mp3`. signed_upload: type: boolean description: Return signed upload URLs instead of fetching a `url`. MediaUploadTarget: type: object properties: url: type: string format: uri description: Signed `PUT` URL. Short-lived; if the `PUT` is rejected as expired, get fresh URLs from `GET /media/public/media/{media_id}/policy`. content_type: type: string description: Send this exact `Content-Type` with the `PUT`. example: audio/mpeg MediaUploadPolicy: type: object properties: mp3: $ref: '#/components/schemas/MediaUploadTarget' wav: $ref: '#/components/schemas/MediaUploadTarget' MediaCreated: type: object properties: media_id: type: string format: uuid name: type: [string, 'null'] type: type: [string, 'null'] media_exists: type: boolean description: True when created from a `url`; false until a signed upload is completed. approved_at: type: integer description: Epoch milliseconds. Present when the audio is stored. api_allowed: type: boolean signed_upload: type: boolean upload: $ref: '#/components/schemas/MediaUploadPolicy' MediaUploadCompleted: type: object properties: media_id: type: string format: uuid media_exists: type: boolean approved_at: type: integer description: Epoch milliseconds. api_allowed: type: boolean Campaign: type: object properties: campaign_id: type: string format: uuid team_id: type: string format: uuid type: type: string enum: [rvm, sms, email, voice_broadcast, ai_broadcast] campaign_data: description: | The settings you sent, plus `status`: `not_started` after create, then `active`, `paused`, `complete` or `insufficient_credit`. allOf: - $ref: '#/components/schemas/CampaignData' approved: type: boolean description: False while the campaign waits for compliance review. approved_at: type: [integer, 'null'] delivery_type: type: string description: Delivery pacing, from `campaign_data.method`. `immediate` by default. deliver_at: type: [integer, 'null'] description: | Epoch milliseconds of the scheduled start. Unscheduled campaigns carry 9007199254740991. created_at: type: integer modified_at: type: integer CampaignData: type: object description: | Campaign settings. Which fields apply depends on `type`; unknown fields are stored as-is. Every `*_id` comes from a lookup route: lists from `GET /contact/public/lists`, media from `GET /media/public/media`, voices from `GET /voice/public/voices`, phone lines from `GET /phone/public/lines`, brands from `GET /automation/public/brands`, agents from `GET /agents/public/agents`, mailboxes from `GET /domain/public/mailboxes`, templates from `GET /template/public/templates`. properties: name: type: string list_ids: type: array items: type: string format: uuid phone_line_id: type: string format: uuid description: Voice and messaging campaigns. Sender numbers and return-call routing. brand_id: type: string format: uuid media_id: type: string format: uuid description: Ringless voicemail audio. voice_id: type: string format: uuid description: Voice for `tts_body` or `tts_on_*`. tts_body: type: string description: Ringless voicemail text to speech. sms_body: type: string description: SMS text. rcs_content: type: object description: RCS card content, usually copied from an RCS template. media_on_speech: type: string format: uuid description: Voice broadcast. Media for a live answer (see `SendVoiceBroadcast` for the full set). media_on_beep: type: string format: uuid tts_on_speech: type: string tts_on_beep: type: string ai_agent: type: object description: AI broadcast. properties: agent_id: type: string format: uuid voice_ids: type: array items: type: string format: uuid voice_rotation: type: string enum: [sticky, round_robin] email_subject: type: string email_html: type: string email_preheader: type: string email_from_mailbox_id: type: string format: uuid email_from: type: string format: email email_from_name: type: string email_reply_to: type: string format: email email_template_id: type: string format: uuid method: type: string description: Delivery pacing, for example `drip`. drip: type: object properties: rate: type: integer timezone: type: string CreateCampaign: type: object required: [campaign_data] properties: type: type: string enum: [rvm, sms, email, voice_broadcast, ai_broadcast] description: | Channel. When omitted it is inferred from `campaign_data`: `sms_body` or `rcs_content` means sms, `email_subject` or `email_html` means email, `ai_agent` means ai_broadcast, `media_on_*` or `tts_on_*` means voice_broadcast, anything else is rvm. Send it explicitly. campaign_data: $ref: '#/components/schemas/CampaignData' UpdateCampaign: type: object properties: campaign_data: $ref: '#/components/schemas/CampaignData' deliver_at: type: integer description: Epoch milliseconds to start at. CampaignStats: type: object description: Totals for the campaign over the chosen buckets. properties: sent: type: integer delivered: type: integer failed: type: integer pending: type: integer read: type: integer description: Read receipts. Only RCS and some carriers report them, so treat this as a floor. billable: type: integer non_billable: type: integer delivery_rate: type: number description: '`delivered / sent`, 0 to 1.' failure_rate: type: number description: '`failed / sent`, 0 to 1.' read_rate: type: number description: '`read / sent`, 0 to 1.' opened: type: integer description: Email campaigns only. clicked: type: integer description: Email campaigns only. bounced: type: integer description: Email campaigns only. complained: type: integer description: Email campaigns only. unsubscribed: type: integer description: Email campaigns only. Contacts who unsubscribed from one of the campaign's emails, each counted once. open_rate: type: number description: Email campaigns only. `opened / delivered`, 0 to 1. click_rate: type: number description: Email campaigns only. `clicked / delivered`, 0 to 1. CampaignCreated: type: object properties: campaign_id: type: string format: uuid approved: type: boolean description: False while the campaign waits for compliance review. You can't start it until it's approved. started: type: boolean description: True when the campaign was approved on create and `campaign_data.method` is `immediate`, so it's already sending. Balance: type: object description: | Account balance in US dollars. One balance covers every channel. properties: balance: type: number description: Current balance. reserved: type: number description: Amount reserved against in-flight sends that haven't settled yet. available: type: number description: Spendable balance (balance minus reserved). recharge_amount: type: number description: Amount auto-charged when balance drops below recharge_threshold. recharge_threshold: type: number description: Balance floor that triggers an auto-recharge. nag_no_funds_at: type: [integer, 'null'] description: Epoch ms timestamp of the last insufficient-funds notice, or null if none. Tag: type: object description: > The tag as Update Tag and Delete Tag return it. Here the id is `id` and `updated_at` is an ISO 8601 string; List Tags and Create Tag use `tag_id` and epoch milliseconds. properties: id: type: string description: The tag ID. label: type: string color: type: string description: Background color as a hex value, for example `#3498db`. text_color: type: string description: Text color as a hex value, for example `#ffffff`. updated_at: type: string format: date-time TagDocument: type: object description: A tag as List Tags returns it. Timestamps are epoch milliseconds. properties: team_id: type: string tag_id: type: string description: The tag ID. label: type: string color: type: string description: Background color as a hex value, for example `#3498db`. text_color: type: string description: Text color as a hex value, for example `#ffffff`. created_at: type: integer description: Epoch ms. created_by: type: string description: user_id of the creator. updated_at: type: integer description: Epoch ms. updated_by: type: string description: user_id of the last updater. TagCreated: description: A tag as Create Tag returns it. allOf: - $ref: '#/components/schemas/TagDocument' - type: object properties: is_seed: type: boolean deleted_at: type: [integer, 'null'] deleted_by: type: [string, 'null'] CreateTag: type: object required: [label] properties: label: type: string description: Display name for the tag. color: type: string description: Background color as a hex value, for example `#3498db`. text_color: type: string description: Text color as a hex value, for example `#ffffff`. UpdateTag: type: object required: [label] description: Send `label` on every update, even when you only change a color. properties: label: type: string description: Updated display name. color: type: string description: Updated background color (hex). text_color: type: string description: Updated text color (hex). Webhook: type: object description: A subscription of one URL to one event type. properties: webhook_id: type: string format: uuid hook_type: type: string description: Event type, for example `contact.rvm.status`. hook_url: type: string format: uri description: The endpoint deliveries are POSTed to, as sent in `hook_url` when subscribing. team_id: type: string format: uuid created_at: type: integer description: Epoch milliseconds. created_by: type: [string, 'null'] description: '`user_id` (an Auth0 subject, not a uuid) of the user who created the webhook.' signing_secret_created_at: type: integer description: Epoch milliseconds. When the current signing secret was issued. IntegrationWebhook: type: object description: One URL subscribed to a set of event types. properties: integration_id: type: string format: uuid integration_type: type: string enum: [webhook] team_id: type: string format: uuid public_data: $ref: '#/components/schemas/IntegrationWebhookData' created_at: type: integer description: Epoch milliseconds. signing_secret_created_at: type: [integer, 'null'] description: Epoch milliseconds. When the current signing secret was issued. IntegrationWebhookData: type: object properties: webhook_name: type: [string, 'null'] webhook_url: type: string format: uri event_types: type: array items: type: string description: Event types this URL receives. `["*"]` means every event. use_auth: type: [boolean, 'null'] username: type: [string, 'null'] IntegrationWebhookCreated: type: object properties: integration_id: type: string format: uuid integration_type: type: string enum: [webhook] team_id: type: string format: uuid signing_secret: type: string description: Verifies `X-Signature` on deliveries to this URL. Returned only here, so store it. public_data: $ref: '#/components/schemas/IntegrationWebhookData' unknown_event_types: type: array items: type: string description: Event types you sent that aren't in the catalog. Present only when there are some. CreateIntegrationWebhook: type: object required: [webhook_url, event_types] properties: webhook_name: type: string description: A label for the subscription. webhook_url: type: string format: uri description: HTTPS endpoint that answers 2xx within 5 seconds. event_types: type: array items: type: string description: Event types from `GET /register/public/events`. Send `["*"]` to receive every event. example: [contact.rvm.status, contact.sms.status] use_auth: type: boolean description: Send `username` and `password` as HTTP Basic auth on every delivery. username: type: string password: type: string writeOnly: true CreateWebhook: type: object required: [hook_type, hook_url] properties: hook_type: type: string description: Event type from `GET /register/public/events`, for example `contact.rvm.status`. hook_url: type: string format: uri description: HTTPS endpoint that answers 2xx within 5 seconds. WebhookCreated: type: object properties: webhook_id: type: string format: uuid signing_secret: type: string description: Verifies `X-Signature` on deliveries for this event type. Account: type: object properties: team_id: type: string format: uuid owner: type: [object, 'null'] description: The account owner; null if the account has none. properties: user_id: type: string description: 'Auth0 subject of the user, for example `auth0|65f1c2d3e4a5b6c7d8e9f0a1` or `google-oauth2|103456789012345678901`. Not a uuid; treat it as an opaque string.' first_name: type: [string, 'null'] last_name: type: [string, 'null'] email: type: [string, 'null'] format: email delivery_limits: type: object properties: frequency: $ref: '#/components/schemas/FrequencyLimitSetting' FrequencyLimitSetting: type: object description: | The contact frequency limit: at most `max_attempts` ringless voicemails, texts, broadcast calls and dialer calls to one phone number within the rolling window. A send over it fails with 4013 (Too Many Attempts). The default is 3 in 3 days. Change it in the dashboard (for example 7 in 7 days for debt collection); it cannot be changed through the API. A single send can only make it stricter, with `max_attempts` / `max_attempt_window_ms`. properties: max_attempts: type: integer window_days: type: number description: The window in days, rounded to two decimals. window_ms: type: integer description: The window in milliseconds. source: type: string enum: [team, default] description: '`team` when your account has its own setting, `default` when the platform default applies.' ApiKey: type: object properties: _id: type: string description: Pass this to `DELETE /register/public/apikeys/{key_id}`. name: type: [string, 'null'] key: type: string format: uuid description: The `x-key` credential. Never pass this as `key_id`. type: type: string team_id: type: string format: uuid created_at: type: integer description: Epoch milliseconds. expires_at: type: [integer, 'null'] description: > Epoch milliseconds when the key expires, or `null` if it never does. An expired key is deleted permanently, usually within two minutes, and then drops out of this list. last_used_at: type: [integer, 'null'] description: Epoch milliseconds of the most recent authenticated request. request_count: type: integer scopes: type: [array, 'null'] items: type: string description: Scopes the key holds. `null` for keys created before scopes were recorded. mcp_client: type: [string, 'null'] description: Connect AI client the key was created for, such as `cursor`. ApiKeyCreated: type: object properties: _id: type: string name: type: [string, 'null'] key: type: string format: uuid description: Send as `x-key`. secret: type: string format: uuid description: Send as `x-secret`. Returned only in this response. type: type: string team_id: type: string format: uuid scopes: type: array items: type: string description: Scopes the key holds. mcp_client: type: [string, 'null'] created_at: type: integer expires_at: type: [integer, 'null'] description: > Epoch milliseconds when the key expires, from `expires_in_seconds`. `null` when the key never expires. DetectionKey: type: object properties: key_id: type: string format: uuid description: Pass this to `DELETE /register/public/detection-keys/{key_id}`. name: type: [string, 'null'] key_hint: type: [string, 'null'] description: Last four characters of the key. status: type: string enum: [active, suspended, expired] description: > `suspended` means the balance is empty or Detection is switched off; the key works again when that clears. created_at: type: [integer, 'null'] description: Epoch milliseconds. expires_at: type: [integer, 'null'] description: Epoch milliseconds, or `null` when the key never expires. DetectionKeyCreated: type: object properties: key_id: type: string format: uuid name: type: string api_key: type: string description: > The Detection API key. Returned only in this response; Drop Cowboy stores a one-way hash of it. key_hint: type: string created_at: type: [integer, 'null'] description: Epoch milliseconds. expires_at: type: [integer, 'null'] description: Epoch milliseconds, or `null` when the key never expires. EventType: type: string description: Event type name you can pass as `hook_type`. example: contact.rvm.status Voice: type: object properties: voice_id: type: string format: uuid team_id: type: [string, 'null'] format: uuid description: Null for platform catalog voices. name: type: string type: type: [string, 'null'] description: '`designed` for voices saved from voice design. Usually null for clones.' gender: type: [string, 'null'] status: type: string enum: [ready, processing, failed, pending_payment] description: | `ready`: synthesize and send with it. `processing`: a clone that is still being built; poll until `ready`. `failed`: the clone did not finish and cannot be used. `pending_payment`: the voice slot must be purchased in the dashboard first. pro_voice: type: boolean url: type: [string, 'null'] format: uri description: Sample audio. Only with `include_urls=true`. created_at: type: [integer, 'null'] failed_at: type: [integer, 'null'] SynthesizeRequest: type: object required: [voice_id, text] properties: voice_id: type: string format: uuid description: One of your account's voices. See `GET /voice/public/voices`. text: type: string description: Text to speak. language: type: string description: Language hint, for example `en`. SynthesizeResult: type: object properties: audio_url: type: string format: uri description: Short-lived download URL, valid for about an hour. Copy the file if you need it after `expires_at`. expires_at: type: [string, integer, 'null'] description: Epoch milliseconds. tts_characters: type: integer description: Billed characters. content_type: type: string example: audio/mpeg CloneVoiceRequest: type: object anyOf: - required: [sample_url] - required: [media_id] - required: [voice_id] properties: name: type: string description: Defaults to "My Voice". sample_url: type: string format: uri description: Public URL of a clean recording. media_id: type: string format: uuid description: Recording already in your media library. See `GET /media/public/media`. voice_id: type: string format: uuid description: Re-clone an existing voice of yours instead of creating a new one. gender: type: string ext: type: string description: Optional. File extension of a `media_id` sample, for example `.mp3`. Detected from the original upload when omitted. speed: type: number minimum: 0.25 maximum: 4 instructions: type: string description: Style guidance for the cloned voice. CloneVoiceResult: type: object properties: voice_id: type: string format: uuid long_job_id: type: string format: uuid pending_payment: type: boolean DesignVoiceRequest: type: object required: [instructions, text] properties: instructions: type: string maxLength: 500 description: What the voice should sound like. text: type: string description: Line the preview reads. language: type: string name: type: string speed: type: number minimum: 0.25 maximum: 4 TranscribeRequest: type: object anyOf: - required: [url] - required: [media_id] properties: url: type: string format: uri description: Public URL of a WAV file (PCM 16-bit, at most 10 MB). media_id: type: string format: uuid description: An MP3 or WAV in your media library. ext: type: string description: Optional. Extension the `media_id` file was uploaded with, `.wav` or `.mp3`. Detected when omitted. TranscribeResult: type: object properties: text: type: string language: type: string EmailAddress: type: object required: [address] properties: address: type: string format: email name: type: string EmailRecord: type: object properties: email_id: type: string format: uuid contact_id: type: [string, 'null'] format: uuid mailbox_id: type: [string, 'null'] format: uuid template_id: type: [string, 'null'] format: uuid direction: type: string enum: [inbound, outbound] state: type: string send_type: type: string enum: [transactional, bulk] to: type: array items: $ref: '#/components/schemas/EmailAddress' from: type: array items: $ref: '#/components/schemas/EmailAddress' subject: type: string created_at: type: integer sent_at: type: [integer, 'null'] SendEmail: type: object description: Give exactly one of `to` or `contact_id`, and one of `mailbox_id` or `from`. required: [subject, html] anyOf: - required: [to] - required: [contact_id] properties: to: type: array minItems: 1 items: $ref: '#/components/schemas/EmailAddress' contact_id: type: string format: uuid description: Send to the contact's primary email. mailbox_id: type: string format: uuid description: Sending mailbox. Preferred over `from`. See `GET /domain/public/mailboxes`. from: type: array maxItems: 1 items: $ref: '#/components/schemas/EmailAddress' description: Sender on one of your verified domains, when not using `mailbox_id`. cc: type: array items: $ref: '#/components/schemas/EmailAddress' bcc: type: array items: $ref: '#/components/schemas/EmailAddress' subject: type: string html: type: string text: type: string description: Plain-text alternative. preview: type: string description: Preheader text. send_type: type: string enum: [transactional, bulk] default: transactional description: | `bulk` for marketing. Decides which email frequency limit applies and adds unsubscribe handling. A sending domain set to one purpose overrides this. template_id: type: string format: uuid description: Record which template this email came from. SendMergedTemplateEmail: type: object required: [template_id] anyOf: - required: [to] - required: [contact_id] properties: template_id: type: string format: uuid description: Email template. See `GET /template/public/templates?type=email`. contact_id: type: string format: uuid to: type: array minItems: 1 items: $ref: '#/components/schemas/EmailAddress' mailbox_id: type: string format: uuid from: type: array maxItems: 1 items: $ref: '#/components/schemas/EmailAddress' cc: type: array items: $ref: '#/components/schemas/EmailAddress' bcc: type: array items: $ref: '#/components/schemas/EmailAddress' subject_override: type: string description: Use this subject instead of the template's. merged_user_id: type: string description: User whose fields fill sender merge fields, as the `user_id` (an Auth0 subject, not a uuid) from `GET /user/public/users`. Defaults to the key's user. preview: type: string send_type: type: string enum: [transactional, bulk] EmailSendResult: type: object properties: success: type: boolean error: type: string description: Present when `success` is false. Mailbox: type: object properties: mailbox_id: type: string format: uuid title: type: string address: type: string format: email domain: type: string display_name: type: [string, 'null'] is_personal: type: boolean send_purpose: type: [string, 'null'] enum: [marketing, transactional, null] description: '`marketing`, `transactional`, or null when the domain has no fixed purpose.' Document: type: object properties: _id: type: string document_id: type: string format: uuid team_id: type: string document_type: type: string description: '`fax`, `attachment` or `general`.' parent: type: [string, 'null'] description: '`contact` or `team`.' filename: type: string ext: type: [string, 'null'] description: File extension without the dot, for example `pdf`. content_type: type: [string, 'null'] file_size: type: [integer, 'null'] description: Bytes. meta: type: [object, 'null'] description: Metadata read from the file, such as page count. owner_id: type: [string, 'null'] description: User who created the document. state_id: type: [string, 'null'] state_history: type: [array, 'null'] items: type: object properties: state_id: type: string changed_at: type: integer description: Epoch milliseconds. changed_by: type: [string, 'null'] visibility: type: [string, 'null'] hidden: type: boolean read_at: type: [integer, 'null'] read_by: type: [string, 'null'] created_at: type: integer description: Epoch milliseconds. created_by: type: [string, 'null'] modified_at: type: integer description: Epoch milliseconds. modified_by: type: [string, 'null'] updated_at: type: integer description: Epoch milliseconds. Set once the document has been edited. updated_by: type: string deleted_at: type: [integer, 'null'] deleted_by: type: [string, 'null'] CreateDocument: type: object required: [document_type, filename] properties: document_type: type: string description: '`fax`, `attachment` or `general`.' filename: type: string description: File name including the extension, for example `contract.pdf`. content_type: type: string file_size: type: integer description: Bytes. contact_id: type: string description: Attach the document to this contact. parent: type: string description: '`contact` or `team`.' DocumentCreated: type: object properties: document_id: type: string format: uuid policy: type: object properties: url: type: string format: uri description: Signed `PUT` URL for the file bytes. uploaded: type: boolean description: True when the file was fetched from `source_url`; false when you still need to `PUT` it to `policy.url`. TaskType: type: string enum: [inbound_call, outbound_call, missed_call, fax, sms, voicemail, email, note, follow_up, chat, permission_request, web_form, appointment, human_review] TaskPreviewInfo: type: object description: What the inbox card shows. properties: task_date: type: integer description: Epoch milliseconds. The reminder or appointment time when there is one, otherwise when the task opened. task_contact: type: string description: Contact display name, email or phone, or `Unknown Contact`. task_subject: type: string task_content: type: string description: Latest message or preview text. subject: type: [string, 'null'] description: Conversation title, set once when the task opens. Task: type: object properties: task_id: type: string format: uuid team_id: type: string contact_id: type: [string, 'null'] conversation_id: type: [string, 'null'] description: Chat conversation, for `chat` tasks. task_type: $ref: '#/components/schemas/TaskType' status: type: string enum: [open, snoozed, closed] unread: type: boolean urgent: type: boolean priority: description: Whatever was sent on create; `0` when none was. mentioned: type: array description: Users mentioned on the task. items: type: string direction: type: [string, 'null'] description: '`inbound` or `outbound`.' assigned_to: type: [string, 'null'] description: User or user group the task is assigned to. assigned_type: type: [string, 'null'] description: '`user` or `user_group`.' assigned_at: type: [integer, 'null'] description: Epoch milliseconds. call_id: type: [string, 'null'] fax_id: type: [string, 'null'] sms_id: type: [string, 'null'] call_recording_id: type: [string, 'null'] followup_id: type: [string, 'null'] booking_id: type: [string, 'null'] appointment_start_at: type: integer description: Epoch milliseconds. Appointment tasks only. followup_scheduled_at: type: integer description: Epoch milliseconds. Follow-up tasks only. last_note: description: Most recent note on the task. human_review: type: object description: Recording, transcript and analysis for `human_review` tasks. preview_info: $ref: '#/components/schemas/TaskPreviewInfo' created_at: type: integer description: Epoch milliseconds. contact: type: object description: The contact's `contact_id`, `gravatar_id` and `phone_numbers`, plus its standard and `custom_*` fields flattened to the top level (for example `first_name`, `email`, `main_phone`). properties: contact_id: type: string gravatar_id: type: [string, 'null'] phone_numbers: type: array items: type: object additionalProperties: true call: type: object description: The linked call record, for call tasks. sms: type: object description: The linked SMS record, for SMS tasks. fax: type: object description: The linked fax record, for fax tasks. call_recording: type: object description: The linked recording, for voicemail tasks. last_record: type: object description: The newest conversation entry on the task, formatted as in the inbox. TaskCreated: type: object description: The stored task. properties: _id: type: string task_id: type: string format: uuid team_id: type: string contact_id: type: [string, 'null'] conversation_id: type: [string, 'null'] task_type: $ref: '#/components/schemas/TaskType' status: type: string enum: [open] unread: type: boolean enum: [true] urgent: type: boolean priority: description: Whatever was sent; `0` when none was. mentioned: type: array items: type: string direction: type: [string, 'null'] assigned_to: type: [string, 'null'] assigned_type: type: [string, 'null'] assigned_at: type: [integer, 'null'] call_id: type: [string, 'null'] fax_id: type: [string, 'null'] sms_id: type: [string, 'null'] note_id: type: [string, 'null'] call_recording_id: type: [string, 'null'] followup_id: type: [string, 'null'] booking_id: type: [string, 'null'] snoozed_until: type: 'null' snoozed_by: type: 'null' closed_at: type: 'null' closed_by: type: 'null' deleted_at: type: 'null' deleted_by: type: 'null' preview_info: $ref: '#/components/schemas/TaskPreviewInfo' actions: type: [array, 'null'] items: type: object action_payload: type: [object, 'null'] action_history: type: array items: type: object created_at: type: integer description: Epoch milliseconds. created_by: type: string updated_at: type: integer description: Epoch milliseconds. CreateTask: type: object required: [contact_id, task_type] properties: contact_id: type: string task_type: $ref: '#/components/schemas/TaskType' assigned_to: type: string description: User or user group to assign the task to. assigned_type: type: string description: '`user` or `user_group`.' urgent: type: boolean priority: description: Stored as sent. UpdateTask: type: object properties: status: type: string enum: [open, snoozed, closed] snoozed_until: type: [string, 'null'] format: date-time description: When a snoozed task reopens. Required when `status` is `snoozed`. task_type: $ref: '#/components/schemas/TaskType' urgent: type: boolean assigned_to: type: [string, 'null'] description: User or user group to assign the task to; `null` unassigns. assigned_type: type: string description: '`user` or `user_group`.' TaskUpdated: type: object description: The fields you sent, plus these identifiers. properties: task_id: type: string team_id: type: string user_id: type: string description: The caller. status: type: string snoozed_until: type: [string, 'null'] task_type: type: string urgent: type: boolean assigned_to: type: [string, 'null'] assigned_type: type: string Template: type: object properties: _id: type: string template_id: type: string format: uuid team_id: type: string user_id: type: string description: User who created the template. name: type: string description: type: [string, 'null'] type: type: string enum: [email, sms, script, chat, macro, rcs] status: type: [string, 'null'] description: '`draft` or `active`. Only `active` templates appear in send pickers. Older email, macro and script templates have no status.' subject: type: string description: Email subject. Can contain merge fields. html_text: type: string description: HTML body. Empty for RCS templates. text: type: string description: Plain-text body. preview: type: string description: First 64 characters of the plain text. channels: type: array description: Where the template can be inserted, for example `sms`, `email`, `chat`, `note`, `rcs`. items: type: string available_for: type: [string, 'null'] description: 'Who can use it: `everyone` (default), `managers`, `group` or `user`.' user_group_id: type: [string, 'null'] target_user_id: type: [string, 'null'] description: The one user it is shared with when `available_for` is `user`. template_group_id: type: [string, 'null'] related_template_ids: type: [array, 'null'] items: type: string attachments: type: array description: Files attached to the template. Inline email images carry a signed `download_url`. items: type: object rcs_type: type: [string, 'null'] rcs_content: type: [object, 'null'] description: Structured RCS message content. rcs_layout_id: type: [string, 'null'] fallback_sms_text: type: [string, 'null'] description: SMS text sent when RCS is unavailable. default_send_type: type: [string, 'null'] enum: [marketing, transactional, null] approval: type: [object, 'null'] created_at: type: integer description: Epoch milliseconds. created_by: type: string updated_at: type: integer description: Epoch milliseconds. updated_by: type: string deleted_at: type: [integer, 'null'] deleted_by: type: [string, 'null'] MergeTemplate: type: object required: [merged_contact_id] properties: merged_contact_id: type: string description: Contact whose fields fill the merge fields. merged_user_id: type: string description: User whose fields fill the user merge fields. Defaults to the caller. MergedTemplate: type: object description: For RCS templates `html_text` and `subject` are empty and the RCS fields are set. properties: html_text: type: string subject: type: string attachments: type: array items: type: object rcs_type: type: [string, 'null'] description: RCS templates only. rcs_content: type: [object, 'null'] description: RCS templates only. fallback_sms_text: type: string description: RCS templates only. Board: type: object description: A pipeline. Its stages are contact lists. properties: _id: type: string board_id: type: string format: uuid team_id: type: string name: type: string lists: type: array description: Stages, in display order. items: $ref: '#/components/schemas/BoardList' brand_id: type: [string, 'null'] description: Brand whose deals the pipeline holds. `null` is the account's default brand. shared_across_brands: type: boolean description: True when the pipeline accepts contacts from every brand. visible_for_user_id: type: [string, 'null'] description: When set, only this user sees the pipeline. visible_for_user_group_id: type: [string, 'null'] description: When set, only members of this user group see the pipeline. is_seed: type: boolean description: True for the starter pipeline created with the account. created_at: type: integer description: Epoch milliseconds. created_by: type: string updated_at: type: [integer, 'null'] description: Epoch milliseconds. updated_by: type: [string, 'null'] deleted_at: type: [integer, 'null'] deleted_by: type: [string, 'null'] BoardList: type: object description: A pipeline stage. properties: list_id: type: string description: The contact list that holds the stage's contacts. The stage takes its name from the list. sort: type: string description: Card order within the stage, for example `newestFirst` or `oldestFirst`. order: type: number firstLoading: type: boolean description: Display hint used by the dashboard. win_probability: type: [number, 'null'] minimum: 0 maximum: 100 description: Percent chance a deal in this stage closes, for the weighted forecast. `null` leaves the stage out of the forecast. stage_type: type: [string, 'null'] enum: [open, won, lost, null] description: '`null` is treated as `open`.' stale_after_days: type: [integer, 'null'] minimum: 1 maximum: 365 description: Days after which a deal sitting in the stage is flagged stale. `null` turns this off. BoardListInput: type: object required: [list_id] properties: list_id: type: string description: An existing contact list. sort: type: string description: Card order within the stage, for example `newestFirst` or `oldestFirst`. order: type: number win_probability: type: [number, 'null'] description: 0 to 100. Out-of-range values are clamped; anything non-numeric is stored as `null`. stage_type: type: [string, 'null'] enum: [open, won, lost, null] stale_after_days: type: [integer, 'null'] description: 1 to 365. Larger values are capped at 365; zero or less is stored as `null`. CreateBoard: type: object properties: name: type: string description: Defaults to `New Bucket Board`. lists: type: array description: Stages, in display order. items: $ref: '#/components/schemas/BoardListInput' brand_id: type: [string, 'null'] description: Brand whose deals the pipeline holds. Omit for the account's default brand. shared_across_brands: type: boolean default: false UpdateBoard: type: object properties: name: type: string lists: type: array description: Replaces every stage, in display order. items: $ref: '#/components/schemas/BoardListInput' brand_id: type: [string, 'null'] description: '`null` or an empty string sets the account''s default brand.' shared_across_brands: type: boolean CreateBoardList: type: object required: [list] properties: list: $ref: '#/components/schemas/BoardListInput' User: type: object description: | Exactly these fields are returned. A field the user does not have comes back as `null`. required: [_id, user_id, username, team_id, role_id, status, owner, identity, created_at] additionalProperties: false properties: _id: type: [string, 'null'] user_id: type: [string, 'null'] description: 'Auth0 subject of the user, for example `auth0|65f1c2d3e4a5b6c7d8e9f0a1` or `google-oauth2|103456789012345678901`. Not a uuid; treat it as an opaque string. Use this where other routes ask for a user or owner id.' username: type: [string, 'null'] team_id: type: [string, 'null'] format: uuid role_id: type: [string, 'null'] status: type: [string, 'null'] owner: type: [boolean, 'null'] description: True for the account owner. identity: type: object required: [first_name, last_name, email] additionalProperties: false properties: first_name: type: [string, 'null'] last_name: type: [string, 'null'] email: type: [string, 'null'] format: email created_at: type: [integer, 'null'] description: Epoch milliseconds. Brand: type: object properties: brand_id: type: string team_id: type: string company_name: type: string dba_name: type: [string, 'null'] registered: type: boolean api_allowed: type: boolean description: Same as `registered`. identity_status: type: [string, 'null'] description: Identity verification status from the campaign registry, for example `VETTED_VERIFIED` or `UNVERIFIED`. opt_in_flow: description: How recipients opt in, as entered at registration. is_default: type: boolean description: True for the brand a send uses when none is given. ucaas_campaigns: type: array description: Campaigns with a `UCAAS_LOW` or `UCAAS_HIGH` use case. items: $ref: '#/components/schemas/BrandCampaign' bulk_campaigns: type: array description: All other campaigns. items: $ref: '#/components/schemas/BrandCampaign' BrandCampaign: type: object properties: pool_id: type: string name: type: string use_case: type: string description: Campaign registry use case, for example `MIXED` or `UCAAS_LOW`. unified_mno_data: type: object description: Carrier approval data for the campaign. registered: type: boolean shared_10DLC: type: boolean Pool: type: object description: A texting (10DLC) campaign. properties: pool_id: type: string name: type: string use_case: type: string description: Campaign registry use case, for example `MIXED` or `UCAAS_LOW`. registered: type: boolean shared_10DLC: type: boolean Disposition: type: object properties: code: type: integer example: 0 label: type: string example: Success category: type: string enum: [success, pending, config-error, delivery-failed, email-issue, carrier-issue, other] SendRecipient: type: object description: | Who to send to. Give exactly one of `to` (a phone number) or `contact_id` (a contact in your account). With `contact_id`, `phone_selector` picks which of the contact's numbers to use; the send fails with "No contact", "Contact on DNC" or "No phone number for contact" if that lookup fails. If both are given, `to` wins. oneOf: - required: [to] - required: [contact_id] properties: to: type: string pattern: '^\+[1-9]\d{1,14}$' example: '+13125550142' description: Recipient in E.164. contact_id: type: string format: uuid description: Send to a contact instead of a raw number. See `GET /contact/public/contacts`. phone_selector: type: string enum: [primary, any, main_phone, mobile_phone, home_phone, office_phone, other_phone] default: primary description: | Which contact number to use with `contact_id`. `primary` walks main, mobile, home, office, other in that order. `any` takes the first number found. foreign_id: type: string maxLength: 256 description: Your own reference. Echoed on `callback_url`; status webhooks do not carry it. callback_url: type: string format: uri description: | Receives one unsigned POST with the outcome of this send (see the `sendOutcome` callback). One attempt, 10 second timeout, no retries. For signed, retried delivery subscribe to the `contact.rvm.status` or `contact.sms.status` webhook instead. Must be a publicly reachable http(s) URL. A value that is not a URL at all fails the send with 3019 (Invalid Callback). Otherwise the address is checked just before the POST: if it is not http(s), does not resolve, or resolves to a private, loopback or link-local address, the callback is skipped and the send itself is unaffected. Redirects are not followed; a `3xx` is the final answer. postal_code: type: string description: Recipient postal code. Improves the time-zone guess used for calling hours. brand_id: type: string format: uuid description: Registered brand to send under. Required when your account enforces brand registration. See `GET /automation/public/brands`. max_attempts: type: integer minimum: 1 description: | Make the contact frequency limit stricter for this send: at most this many attempts to the number within the window. Honored only when it is lower than your account's limit (see `delivery_limits.frequency` on `GET /register/public/account`); a higher or invalid value is ignored without an error. max_attempt_window_ms: type: integer minimum: 1 maximum: 2592000000 description: | Make the frequency window longer for this send, in milliseconds. Honored only when it is longer than your account's window; values over 30 days (2592000000) are treated as 30 days. A shorter or invalid value is ignored without an error. SendCallerId: type: object properties: phone_line_id: type: string format: uuid description: | Phone line the call is placed from; one of its numbers is shown as the caller ID, picked per recipient. Return calls and texts reach that line and follow its routing. Omit it to call from your default phone line; with no default line the send fails with `4010`. When you send both, `caller_id` is ignored. See `GET /phone/public/lines`. caller_id: type: string pattern: '^\+[1-9]\d{1,14}$' example: '+13125550142' description: | BYOC accounts only. Your carrier's number in E.164, shown as the caller ID exactly as given. Ignored on other accounts, which always call from a phone line. SendRvm: description: | Ringless voicemail. Give the audio as exactly one of: `media_id` (an uploaded or recorded file), `tts_body` plus `voice_id` (text to speech), or `audio_url` (BYOC accounts only; other accounts get "Not allowed audio_url"). Outside the contact's calling hours the voicemail is held and retried for up to 3 days, then fails with reason `tcpa_expired`. allOf: - $ref: '#/components/schemas/SendRecipient' - $ref: '#/components/schemas/SendCallerId' - type: object properties: media_id: type: string format: uuid description: Audio file to drop. See `GET /media/public/media`. tts_body: type: string description: Text to speak with `voice_id`. Supports merge fields. Up to 1,200 characters once merge fields are filled in; longer text fails with `3021`. voice_id: type: string format: uuid description: Voice for `tts_body`. See `GET /voice/public/voices`. audio_url: type: string format: uri description: BYOC only. Public mp3 or wav URL to fetch and drop. mobile_only: type: boolean description: Skip numbers that are not mobile. byoc: type: object description: BYOC call options. properties: sti_orig_id: type: string format: uuid sti_attestation: type: string enum: [A, B, C] SendSmsMessage: description: | SMS, or RCS when `template_id` is given. Send from a `phone_line_id` whose line has an approved texting campaign: that line picks the sender number and the registered campaign. Without a phone line, a sender is picked from your account's own texting numbers. Give the content as `body` (plain text) or `template_id` (an RCS template built in the dashboard; the template's fallback text is sent where RCS is unavailable). If both are given, the template wins. Add `media_urls` or `media_ids` (10 files in total) to send MMS. `body` is then the caption and is optional. The caption is sent as written, with no opt-out text added, and an MMS with no caption is sent as media only. A text-only `body` without opt-out language gets ` Reply STOP to opt-out` appended. Media cannot be combined with `template_id` (3032). A send with no `body`, no `template_id` and no media fails with 3020. Messaging is not held for calling hours: outside the contact's allowed hours the send fails with reason code 4011 (TCPA Hours) and is not retried. allOf: - $ref: '#/components/schemas/SendRecipient' - type: object anyOf: - required: [body] - required: [template_id] - required: [media_urls] properties: media_urls: minItems: 1 - required: [media_ids] properties: media_ids: minItems: 1 properties: body: type: string maxLength: 1600 description: Message text, or the caption when media is attached. media_urls: $ref: '#/components/schemas/MmsMediaUrls' media_ids: $ref: '#/components/schemas/MmsMediaIds' template_id: type: string format: uuid description: RCS template. Turns this send into RCS. See `GET /template/public/templates?type=rcs`. phone_line_id: type: string format: uuid description: | Phone line to send from. Its assigned numbers are used as the sender and its approved texting campaign is used for routing. See `GET /phone/public/lines`. SendVoiceBroadcast: description: | Press-1 call. Plays one message to a live person and another to an answering machine, and can transfer, confirm or opt out on a key press. Give content as media (`media_on_*`) or text to speech (`tts_on_*` plus `voice_id`); `voice_id` is required whenever any `tts_on_*` field is set. Calls from `phone_line_id`, or your default phone line when it's omitted. Outside the contact's calling hours the call is held and retried for up to 3 days, then fails with reason `tcpa_expired`. allOf: - $ref: '#/components/schemas/SendRecipient' - $ref: '#/components/schemas/SendCallerId' - type: object anyOf: - required: [media_on_speech] - required: [media_on_beep] - required: [tts_on_speech] - required: [tts_on_beep] properties: media_on_speech: type: string format: uuid description: Media played when a live person answers. media_on_beep: type: string format: uuid description: Media played to an answering machine. media_on_transfer: type: string format: uuid description: Media played before a transfer. media_on_confirm: type: string format: uuid description: Media played after the confirm key. media_on_opt_out: type: string format: uuid description: Media played after the opt-out key. voice_id: type: string format: uuid description: Voice for every `tts_on_*` field. See `GET /voice/public/voices`. tts_on_speech: type: string description: Text spoken when a live person answers. tts_on_beep: type: string description: Text spoken to an answering machine. tts_on_transfer: type: string description: Text spoken before a transfer. tts_on_confirm: type: string description: Text spoken after the confirm key. tts_on_opt_out: type: string description: Text spoken after the opt-out key. transfer_digit: type: integer minimum: 0 maximum: 9 description: Key that transfers the call. Needs `transfer_ivr_id` or `transfer_to`. transfer_ivr_id: type: string format: uuid description: Phone line that receives transfers. Preferred over `transfer_to`. transfer_to: type: string pattern: '^\+[1-9]\d{1,14}$' example: '+13125550142' description: Number that receives transfers when no `transfer_ivr_id` is given. confirm_digit: type: integer minimum: 0 maximum: 9 description: Key that confirms interest. opt_out_digit: type: integer minimum: 0 maximum: 9 description: Key that adds the number to your do-not-contact list. max_ring_seconds: type: integer default: 30 description: How long to wait for an answer before giving up. amd_enabled: type: boolean default: false description: Detect answering machines and play the `*_on_beep` content after the greeting. SendAiBroadcast: description: | Outbound call handled by one of your published AI agents. Build and publish the agent first (`POST /agents/public/agents`, then `.../publish`). The send fails with "No Agent", "Agent Not Active" or "Agent Is Draft" if the agent cannot take calls. Outcomes arrive on `contact.rvm.status` with `campaign_type: ai_broadcast` and, per call, on one `ai_agent.*` event (see the `/ai-broadcast` operation). Outside the contact's calling hours the call is held and retried for up to 3 days, then fails with reason `tcpa_expired`. allOf: - $ref: '#/components/schemas/SendRecipient' - $ref: '#/components/schemas/SendCallerId' - type: object required: [agent_id] properties: agent_id: type: string format: uuid description: A published, active agent. See `GET /agents/public/agents`. voice_ids: type: array minItems: 1 items: type: string format: uuid description: Override the agent's voice for this call. voice_rotation: type: string enum: [sticky, round_robin] description: How to choose between several `voice_ids`. SendOutcome: type: object description: | Body POSTed once to a send's `callback_url` when its outcome is known. Unsigned. If the request fails validation before it is sent, a shorter body with `status: failure`, `reason`, `reason_code`, `product_code`, `phone_number` and `foreign_id` is posted instead. properties: drop_id: type: string format: uuid team_id: type: string format: uuid session_id: type: string format: uuid log_id: type: string format: uuid contact_id: type: [string, 'null'] format: uuid phone_number: type: string caller_id: type: [string, 'null'] product_code: type: string description: '`rvm`, `sms`, `rcs`, `voice_broadcast` or `ai_broadcast`.' status: type: string enum: [success, failure] reason: type: string description: Empty on success. reason_code: type: [integer, 'null'] description: See [Outcomes](https://www.dropcowboy.com/developers/api/outcomes). quantity: type: number product_cost: type: number compliance_fee: type: number tts_fee: type: number dnc: type: boolean attempt_date: type: string format: date-time foreign_id: type: [string, 'null'] proof_of_delivery_url: type: string format: uri description: | Present when a voicemail system took the call: a ringless voicemail with `reason_code` 0, 4001 or 4002, or a voice broadcast or AI call that ended with 0 in a mailbox. See `GET /campaign/public/receipts/{token}`. The `callback_url` body never carries `frequency_limit`; that is on the status webhook only. CreateIntegrationEventRequest: type: object required: - event_type - title - source properties: event_type: type: string enum: [payment_received, payment_failed, subscription_changed, deal_updated, form_submitted, custom] description: Type of integration event title: type: string maxLength: 500 description: Human-readable event title source: type: string maxLength: 50 pattern: '^[a-zA-Z0-9-]+$' description: The system the event came from, for example `stripe`, `hubspot` or `custom-crm`. icon: type: string maxLength: 10 description: Emoji or icon identifier contact_id: type: string description: Direct contact ID (takes precedence over contact_lookup) contact_lookup: type: object properties: email: type: string format: email phone: type: string description: Look up contact by email (tried first) or phone external_url: type: string format: uri maxLength: 2048 description: Link to the event in the source system data: type: object description: Custom key-value data (max 4KB serialized) client_event_id: type: string maxLength: 100 description: Client-provided idempotency key (unique within 24h per team) priority: type: string enum: [low, normal, high] default: normal IntegrationEvent: type: object properties: id: type: string event_type: type: string title: type: string source: type: string icon: type: string contact_id: type: string external_url: type: string data: type: object client_event_id: type: string priority: type: string created_at: type: string format: date-time TimelineEntry: type: object description: | One activity on a contact. Which other fields are present depends on `type`: notes carry `note_id` and `note`, calls carry `call_id`, `direction` and `duration`, tag changes carry `tag_id` and `tag_label`, consent changes carry the `consent_*` fields, follow-ups carry `followup_id` and `data`, and integration events carry `source`, `title` and `data`. additionalProperties: true properties: entry_id: type: string format: uuid type: type: string description: | Such as `note`, `call`, `sms`, `tag_added`, `tag_removed`, `consent_granted`, `consent_revoked`, `followup.created`, `followup.triggered`, `web_session`, `web_form_submitted`, `goal_achieved`, `appointment.booked` or `integration`. example: tag_added team_id: type: string contact_id: type: string format: uuid user_id: type: [string, 'null'] task_id: type: [string, 'null'] list_id: type: [string, 'null'] note_id: type: string note: type: string call_id: type: string sms_id: type: string sms_body: type: string direction: type: [string, 'null'] duration: type: integer from: type: string example: '+13125550142' to: type: string example: '+13125550187' disposition: {} tag_id: type: string tag_label: type: string consent_id: type: string consent_type: type: string consent_method: type: string consent_text: type: [string, 'null'] followup_id: type: string source: type: string description: For integration events, the app that sent it. title: type: string icon: type: [string, 'null'] external_url: type: [string, 'null'] sub_type: type: [string, 'null'] data: type: [object, 'null'] additionalProperties: true attachments: type: array items: {} created_at: type: integer description: Epoch milliseconds. CampaignEventData: type: object description: '`data` for `campaign.created`, `campaign.updated`, `campaign.deleted`, `campaign.started` and `campaign.paused`.' required: [team_id, campaign_id, campaign_type, list_ids] properties: team_id: type: string format: uuid team: $ref: '#/components/schemas/WebhookTeam' user_id: type: string format: uuid description: The teammate who made the change. user: $ref: '#/components/schemas/WebhookUser' campaign_id: type: string format: uuid campaign_type: type: [string, 'null'] enum: [rvm, sms, email, voice_broadcast, ai_broadcast, null] description: The campaign's channel. Null when the campaign was saved without one. list_ids: type: array items: type: string format: uuid description: The contact lists the campaign sends to. Empty when it targets none. CampaignCompletedEventData: type: object description: '`data` for `campaign.completed`.' required: [team_id, campaign_id, delivery_type, campaign_type, list_ids] properties: team_id: type: string format: uuid team: $ref: '#/components/schemas/WebhookTeam' campaign_id: type: string format: uuid delivery_type: type: string description: | The campaign's delivery pacing with `-complete` appended, for example `immediate-complete` or `drip-complete`. campaign_type: type: [string, 'null'] enum: [rvm, sms, email, voice_broadcast, ai_broadcast, null] description: The campaign's channel. Null when the campaign was saved without one. list_ids: type: array items: type: string format: uuid description: The contact lists the campaign sent to. Empty when it targeted none. PhoneNumberEventData: type: object description: '`data` for `number.provisioned`, `number.released` and `number.updated`.' required: [team_id, phone_number] properties: team_id: type: string format: uuid team: $ref: '#/components/schemas/WebhookTeam' user_id: type: string format: uuid description: The teammate who made the change. user: $ref: '#/components/schemas/WebhookUser' phone_number: type: string description: E.164. PhoneNumberFlaggedEventData: type: object description: '`data` for `number.flagged`.' required: [team_id, phone_number, reason] properties: team_id: type: string format: uuid team: $ref: '#/components/schemas/WebhookTeam' phone_number: type: string description: E.164. reason: type: string enum: [complaint] description: Why the number was flagged. Always `complaint` today. TeammateEventData: type: object description: '`data` for `user.created`, `user.updated`, `user.deleted`, `user.login` and `user.logout`. No credentials or tokens are ever included.' required: [team_id, user_id] properties: team_id: type: string format: uuid team: $ref: '#/components/schemas/WebhookTeam' user_id: type: string format: uuid description: The teammate the event is about. For `user.updated`, the teammate who was edited. user: $ref: '#/components/schemas/WebhookUser' updated_by: type: string format: uuid description: '`user.updated` only. User id of the teammate who made the change; no record is attached for it.' SubscriptionEventData: type: object description: '`data` for `subscription.changed`, `subscription.cancelled` and `subscription.reactivated`.' required: [team_id] properties: team_id: type: string format: uuid team: $ref: '#/components/schemas/WebhookTeam' SubscriptionPausedEventData: type: object description: '`data` for `subscription.paused`.' required: [team_id, paused] properties: team_id: type: string format: uuid team: $ref: '#/components/schemas/WebhookTeam' paused: type: boolean description: '`true` when the subscription was paused, `false` when it was resumed.' SubscriptionInvoicedEventData: type: object description: '`data` for `subscription.invoiced`. No amounts or payment details are included.' required: [team_id, invoice_id, status] properties: team_id: type: string format: uuid team: $ref: '#/components/schemas/WebhookTeam' invoice_id: type: string description: The invoice number shown on the invoice in your billing history. recurring: type: boolean description: '`true` for a regular renewal invoice, `false` for a one-off charge.' subscription_changed: type: boolean description: | `true` when this renewal invoice was processed while a plan change on your account was being applied, otherwise `false`. Always `false` for one-off charges. status: type: string enum: [paid, posted, payment_due, not_paid, voided, pending] description: The status the invoice just reached. ArchiveCompleteEventData: type: object description: '`data` for `archive.complete`.' required: [team_id, archive_date, record_count, ftp_synced] properties: team_id: type: string format: uuid team: $ref: '#/components/schemas/WebhookTeam' archive_date: type: string format: date description: The day the archive covers, `YYYY-MM-DD`. record_count: type: integer minimum: 0 description: Number of result rows in the archive. ftp_synced: type: boolean description: '`true` when your account copies archives to your FTP server.' DomainVerificationCompleteEventData: type: object description: '`data` for `domain.verification.complete`.' required: [team_id, domain_id, custom_domain, completed_at] properties: team_id: type: string format: uuid team: $ref: '#/components/schemas/WebhookTeam' domain_id: type: string format: uuid custom_domain: type: string description: The domain that was verified. completed_at: type: integer description: Epoch milliseconds. DomainVerificationFailedEventData: type: object description: '`data` for `domain.verification.failed`.' required: [team_id, domain_id, custom_domain, step, error] properties: team_id: type: string format: uuid team: $ref: '#/components/schemas/WebhookTeam' domain_id: type: string format: uuid custom_domain: type: string description: The domain being checked. step: type: integer enum: [1, 2] description: The setup step being checked. `1` is the CNAME records, `2` the MX record. error: type: string description: A short description of what went wrong. Treat it as informational text, not a stable code. TaskEventData: type: object description: '`data` for `task.opened` and `task.closed`.' required: [team_id, task_id] properties: team_id: type: string format: uuid team: $ref: '#/components/schemas/WebhookTeam' task_id: type: string format: uuid user_id: type: [string, 'null'] description: | The teammate who opened or closed the task. Null when no teammate did, for example a task opened by an inbound message. user: $ref: '#/components/schemas/WebhookUser' contact_id: type: [string, 'null'] description: | The contact the task is about. Null, or absent along with `contact`, for a task with no contact, such as an appointment booked without one. contact: $ref: '#/components/schemas/WebhookContact' TaskAssignedEventData: type: object description: '`data` for `task.assigned`.' required: [team_id, user_id, task_id, assigned_to] properties: team_id: type: string format: uuid team: $ref: '#/components/schemas/WebhookTeam' task_id: type: string format: uuid user_id: type: string format: uuid description: The teammate who made the assignment. user: $ref: '#/components/schemas/WebhookUser' contact_id: type: [string, 'null'] description: The contact the task is about. Null for a task with no contact. contact: $ref: '#/components/schemas/WebhookContact' assigned_to: type: string description: The user id of the teammate the task is now assigned to. Not expanded into a record. CallEventData: type: object description: | `data` for `contact.call.ringing`, `contact.call.answered` and `contact.call.missed`. Dialer calls (outbound) never carry `ivr_id`; inbound phone-line calls always do. required: [call_id, team_id] properties: call_id: type: string call: $ref: '#/components/schemas/WebhookCall' team_id: type: string format: uuid team: $ref: '#/components/schemas/WebhookTeam' user_id: type: [string, 'null'] description: The dialer agent on outbound calls; on inbound calls the user the call is assigned to, absent when there is none. user: $ref: '#/components/schemas/WebhookUser' contact_id: type: [string, 'null'] description: Null on an inbound call from a number that matches no contact. contact: $ref: '#/components/schemas/WebhookContact' list_id: type: [string, 'null'] description: The dialer list on outbound calls; usually null on inbound calls. list: $ref: '#/components/schemas/WebhookList' ivr_id: type: [string, 'null'] description: Inbound calls only. The phone line that was dialed. call_direction: type: string enum: [inbound, outbound] description: '`contact.call.answered` and `contact.call.missed` only.' CallHangupData: type: object description: '`data` for `contact.call.hangup`. Dialer calls never carry `ivr_id`; inbound phone-line calls always do.' required: [call_id, team_id] properties: call_id: type: string call: $ref: '#/components/schemas/WebhookCall' team_id: type: string format: uuid team: $ref: '#/components/schemas/WebhookTeam' user_id: type: [string, 'null'] user: $ref: '#/components/schemas/WebhookUser' contact_id: type: [string, 'null'] contact: $ref: '#/components/schemas/WebhookContact' list_id: type: [string, 'null'] list: $ref: '#/components/schemas/WebhookList' ivr_id: type: [string, 'null'] description: Inbound calls only. The phone line that was dialed. hangup_cause: type: string description: | Telephony hangup cause, such as `NORMAL_CLEARING`, `USER_BUSY`, `NO_ANSWER`, `ORIGINATOR_CANCEL`, or `NORMAL_TEMPORARY_FAILURE` when a dialer call could not be placed. Always set on dialer calls; can be absent on inbound calls. CallAbandonedData: type: object description: '`data` for `contact.call.abandoned`. Dialer calls only.' required: [call_id, team_id, reason] properties: call_id: type: string call: $ref: '#/components/schemas/WebhookCall' team_id: type: string format: uuid team: $ref: '#/components/schemas/WebhookTeam' user_id: type: [string, 'null'] description: The dialer session's agent. user: $ref: '#/components/schemas/WebhookUser' contact_id: type: [string, 'null'] contact: $ref: '#/components/schemas/WebhookContact' list_id: type: [string, 'null'] list: $ref: '#/components/schemas/WebhookList' reason: type: string enum: [no_agent] description: Why the call was dropped. `no_agent` means no agent was free to take it. CallQueuedData: type: object description: '`data` for `contact.call.queued`. Inbound phone-line calls only.' required: [call_id, team_id, direction, call_type] properties: call_id: type: string call: $ref: '#/components/schemas/WebhookCall' team_id: type: string format: uuid team: $ref: '#/components/schemas/WebhookTeam' user_id: type: [string, 'null'] description: The user the call is assigned to, absent when there is none. user: $ref: '#/components/schemas/WebhookUser' contact_id: type: [string, 'null'] description: Null when the caller's number matches no contact. contact: $ref: '#/components/schemas/WebhookContact' list_id: type: [string, 'null'] list: $ref: '#/components/schemas/WebhookList' ivr_id: type: [string, 'null'] description: The phone line that was dialed. from: type: [string, 'null'] description: The caller's number. to: type: [string, 'null'] description: The phone line number that was dialed. direction: type: string enum: [inbound] call_type: type: string enum: [external] CallDispositionData: type: object description: '`data` for `contact.call.disposition` and `contact.call.disposition.changed`.' required: [call_id, team_id, disposition_sentiment, call_duration, call_direction] properties: call_id: type: string call: $ref: '#/components/schemas/WebhookCall' team_id: type: string format: uuid team: $ref: '#/components/schemas/WebhookTeam' user_id: type: [string, 'null'] description: The dialer agent on `contact.call.disposition`; the teammate who made the edit on `contact.call.disposition.changed`. user: $ref: '#/components/schemas/WebhookUser' contact_id: type: [string, 'null'] description: The contact the call was with. contact: $ref: '#/components/schemas/WebhookContact' list_id: type: [string, 'null'] description: The list the call was dialed from. list: $ref: '#/components/schemas/WebhookList' ivr_id: type: [string, 'null'] description: The phone line the call came in on. Null for dialer calls. disposition_id: type: [string, 'null'] description: | Always sent. The disposition, a short token rather than a uuid: `sold`, `follow_up`, `try_later`, `no_contact`, `no_interest`, `bad_lead`, `dnc`, `not_reachable`, `network_blocked`, `no_service`, `no_answer`, `rejected`, `busy`, `skip`, `redial`, `abandoned`, `voicemail`, `warm_transfer`, `cold_transfer`, `qa_fail`, `qa_success`, `none`, or one of the team's custom slots `custom_1` to `custom_9`. disposition_sentiment: type: [string, 'null'] enum: [good, neutral, bad, null] call_duration: type: number description: Seconds. Can be fractional on the hangup-time `contact.call.disposition`; 0 when unknown. On `contact.call.disposition.changed`, the call's recorded duration. call_direction: type: string enum: [inbound, outbound] description: Always `outbound` on `contact.call.disposition`. On `contact.call.disposition.changed`, the call's own direction. CallRecordingAvailableData: type: object description: '`data` for `contact.call.recording.available`.' required: [call_id, team_id, call_recording_id, recording_type] properties: call_id: type: string call: $ref: '#/components/schemas/WebhookCall' team_id: type: string format: uuid team: $ref: '#/components/schemas/WebhookTeam' user_id: type: [string, 'null'] user: $ref: '#/components/schemas/WebhookUser' contact_id: type: [string, 'null'] contact: $ref: '#/components/schemas/WebhookContact' call_recording_id: type: string format: uuid description: The call recording's id. call_recording: $ref: '#/components/schemas/WebhookCallRecording' recording_type: type: string enum: [call, voicemail] extension: type: [string, 'null'] description: The audio file's extension, with the dot, such as `.mp3`. preview: type: [string, 'null'] description: Transcript of the contact's side of the recording. Can be long; not truncated to a short preview. recording_session: type: [integer, 'null'] description: Which recording session of the call this is, when a call was recorded in several sessions. recording_transcript_part: type: [integer, 'null'] description: Which part of a long transcript this is, when the transcript was split. CallReadData: type: object description: '`data` for `contact.call.read` and `contact.call.unread`. `contact_id` is the contact on the call.' required: [call_id] properties: call_id: type: string call: $ref: '#/components/schemas/WebhookCall' team_id: type: [string, 'null'] format: uuid team: $ref: '#/components/schemas/WebhookTeam' user_id: type: [string, 'null'] description: The teammate who changed the call. user: $ref: '#/components/schemas/WebhookUser' contact_id: type: [string, 'null'] contact: $ref: '#/components/schemas/WebhookContact' AiAgentCallStartedData: type: object description: | `data` for `ai_agent.call.started`. Inbound calls carry `direction: inbound`, `call_id`, `from`, `to` and `ivr_id`. Outbound AI voice broadcast calls carry no `direction`, and instead `campaign_id`, `session_id`, `drop_id` and `phone_number`. required: [team_id] properties: team_id: type: string format: uuid team: $ref: '#/components/schemas/WebhookTeam' agent_id: type: [string, 'null'] description: The AI agent handling the call. direction: type: string enum: [inbound] description: Inbound calls only. call_id: type: string description: Inbound calls only. call: $ref: '#/components/schemas/WebhookCall' from: type: [string, 'null'] description: Inbound calls only. The caller's number. to: type: [string, 'null'] description: Inbound calls only. The phone line number that was dialed. ivr_id: type: [string, 'null'] description: Inbound calls only. The phone line that was dialed. campaign_id: type: [string, 'null'] description: Outbound only. session_id: type: [string, 'null'] description: Outbound only. The campaign run. drop_id: type: string description: Outbound only. The send (a 24-character hex id), the same `drop_id` as on `contact.rvm.status`. campaign_session: $ref: '#/components/schemas/WebhookCampaignSession' phone_number: type: [string, 'null'] description: Outbound only. The number being called, E.164. ContactOperationData: type: object description: | Fields on an event that covers many contacts at once. Only ids are sent, never contact records: read a contact with `GET /contact/public/contacts/{id}`, or page through a list with `more_via`. properties: total_count: type: integer description: How many contacts the change covered. Can exceed the ids sent. truncated: type: boolean description: True when `total_count` is more than the ids sent. next_cursor: type: [string, 'null'] description: >- Cursor after the last id sent: pass it as `after_id` to `GET /contact/public/lists/{id}/contacts`. Null when not truncated. Absent on list copies and moves. more_via: type: [string, 'null'] format: uri description: >- Link to the rest of the contacts: `GET /contact/public/lists/{id}/contacts` on `https://api-v2.dropcowboy.com`, with `after_id` and `limit=25` already filled in. Set only when the event is truncated and belongs to a list, including `contact.list.added` from an import into a list. Null otherwise: when not truncated, when the event has no list (such as `contact.created` and `contact.updated` from an import), and on list copies. operation: type: object description: >- The change behind the event. Absent on per-contact events, on web form events, and on events from `POST /contact/public/contacts`. properties: type: type: string enum: [import, manual, list.move, list.copy] id: type: string format: uuid description: The import id for imports; otherwise matches the envelope's `operation_id`. ContactCreatedData: allOf: - $ref: '#/components/schemas/ContactOperationData' - type: object required: [team_id] properties: team_id: type: string format: uuid team: $ref: '#/components/schemas/WebhookTeam' user_id: type: [string, 'null'] description: >- The teammate or API key owner who created the contacts. Absent when a web form, inbound call, text, email or chat created the contact, and on per-contact import events. user: $ref: '#/components/schemas/WebhookUser' contact_ids: description: >- A single id (string) when one contact was created on its own; an array for contacts created together. An import sends at most 25; a `POST /contact/public/contacts` request sends every new id. oneOf: - type: string format: uuid - type: array items: type: string format: uuid contact_id: type: string format: uuid description: Only on per-contact import events, in place of `contact_ids`. contact: $ref: '#/components/schemas/WebhookContact' source: type: string description: >- How the contact was created: `manual` (app), `api` (`POST /contact/public/contacts`), `import` (a CSV import or another bulk create), `webform`, `call`, `sms` or `email`. Absent for contacts created by website chat and by some web form submissions. ContactUpdatedData: allOf: - $ref: '#/components/schemas/ContactOperationData' - type: object required: [team_id] properties: team_id: type: string format: uuid team: $ref: '#/components/schemas/WebhookTeam' user_id: type: [string, 'null'] description: The teammate who made the change. Absent for web form and per-contact import events. user: $ref: '#/components/schemas/WebhookUser' contact_id: type: string format: uuid description: Set when one contact was edited. contact: $ref: '#/components/schemas/WebhookContact' contact_ids: description: >- Set instead of `contact_id` when creating contacts matched existing ones: a single id (string), or an array. An import sends at most 25. oneOf: - type: string format: uuid - type: array items: type: string format: uuid ContactDeletedData: type: object required: [team_id, contact_id] properties: team_id: type: string format: uuid team: $ref: '#/components/schemas/WebhookTeam' user_id: type: [string, 'null'] user: $ref: '#/components/schemas/WebhookUser' contact_id: type: string format: uuid contact: $ref: '#/components/schemas/WebhookContact' ContactAssignedData: type: object required: [team_id, contact_id] properties: team_id: type: string format: uuid team: $ref: '#/components/schemas/WebhookTeam' user_id: type: [string, 'null'] description: The teammate who made the change. user: $ref: '#/components/schemas/WebhookUser' contact_id: type: string format: uuid contact: $ref: '#/components/schemas/WebhookContact' owner: type: [string, 'null'] description: User id of the new owner. Null when the owner was cleared. previous_owner: type: [string, 'null'] description: User id of the previous owner. Null when the contact had none. ContactDispositionData: type: object required: [team_id, contact_id, disposition_id] properties: team_id: type: string format: uuid team: $ref: '#/components/schemas/WebhookTeam' user_id: type: [string, 'null'] user: $ref: '#/components/schemas/WebhookUser' contact_id: type: string format: uuid contact: $ref: '#/components/schemas/WebhookContact' disposition_id: type: string description: The disposition now on the contact. list_id: type: string format: uuid description: Present when the disposition was set from within a list. list: $ref: '#/components/schemas/WebhookList' ContactForgottenData: type: object required: [team_id, contact_id, reason] properties: team_id: type: string format: uuid team: $ref: '#/components/schemas/WebhookTeam' user_id: type: [string, 'null'] user: $ref: '#/components/schemas/WebhookUser' contact_id: type: string format: uuid contact: $ref: '#/components/schemas/WebhookContact' reason: type: string default: user_request description: The reason given with the request; `user_request` when none was given. ContactDataDeletedData: type: object required: [team_id, contact_id, deleted_at, events_deleted] properties: team_id: type: string format: uuid team: $ref: '#/components/schemas/WebhookTeam' contact_id: type: string format: uuid contact: $ref: '#/components/schemas/WebhookContact' deleted_by: type: [string, 'null'] description: User id of the teammate who made the request. No `user` record is attached. deleted_at: type: integer description: Epoch milliseconds. events_deleted: type: integer description: Number of web visits deleted. ContactConsentGrantedData: type: object required: [team_id, consent_id, consent_type] properties: team_id: type: string format: uuid team: $ref: '#/components/schemas/WebhookTeam' contact_id: type: [string, 'null'] description: Absent when the consent was recorded for a phone number or email with no contact yet. contact: $ref: '#/components/schemas/WebhookContact' consent_id: type: string format: uuid consent_type: type: string enum: [esign, tcpa_optin, sms_optin, sms_optin_confirmed, email_optin, web_tracking] consent_method: type: [string, 'null'] description: How consent was captured, as given by the caller; `webform` for web form submissions. phone_number: type: [string, 'null'] description: Not sent for web form consent. email: type: [string, 'null'] description: Not sent for web form consent. granted_at: type: integer description: Epoch milliseconds. Not sent for web form consent, which sends `created_at`. consent_text: type: [string, 'null'] description: Web form consent only. The consent language the visitor agreed to. page_url: type: [string, 'null'] description: Web form consent only. The page the form was on. created_at: type: integer description: Web form consent only. Epoch milliseconds. ContactConsentRevokedData: type: object required: [team_id] properties: team_id: type: string format: uuid team: $ref: '#/components/schemas/WebhookTeam' contact_id: type: [string, 'null'] contact: $ref: '#/components/schemas/WebhookContact' consent_id: type: string format: uuid description: The new opt-out record. original_consent_id: type: [string, 'null'] description: The consent that was revoked, when the revoke named one. Not sent for email unsubscribes. original_consent_type: type: [string, 'null'] description: Type of the revoked consent; `email_optin` for email unsubscribes. consent_type: type: string description: Web form opt-outs only. The opt-out type recorded. consent_method: type: [string, 'null'] description: Web form opt-outs only. phone_number: type: [string, 'null'] description: Not sent for email unsubscribes or web form opt-outs. email: type: [string, 'null'] revoked_at: type: integer description: Epoch milliseconds. Web form opt-outs send `created_at` instead. revoke_reason: type: string description: >- How it was revoked, for example `api`, `manual`, `unsubscribe`, or `revoke_all` and `cross_channel_sync` when your compliance settings carried a revoke over from another channel. consent_text: type: [string, 'null'] description: Web form opt-outs only. page_url: type: [string, 'null'] description: Web form opt-outs only. created_at: type: integer description: Web form opt-outs only. Epoch milliseconds. ContactDocumentData: type: object required: [team_id, contact_id, document_id] properties: team_id: type: string format: uuid team: $ref: '#/components/schemas/WebhookTeam' user_id: type: [string, 'null'] description: Absent for inbound faxes. user: $ref: '#/components/schemas/WebhookUser' contact_id: type: string format: uuid contact: $ref: '#/components/schemas/WebhookContact' document_id: type: string format: uuid document: $ref: '#/components/schemas/WebhookDocument' ContactNoteData: type: object required: [team_id, contact_id, note_id] properties: team_id: type: string format: uuid team: $ref: '#/components/schemas/WebhookTeam' user_id: type: [string, 'null'] user: $ref: '#/components/schemas/WebhookUser' contact_id: type: string format: uuid contact: $ref: '#/components/schemas/WebhookContact' note_id: type: string format: uuid note: $ref: '#/components/schemas/WebhookNote' ContactReminderData: type: object required: [team_id, contact_id, followup_id] properties: team_id: type: string format: uuid team: $ref: '#/components/schemas/WebhookTeam' user_id: type: [string, 'null'] description: The teammate who acted, not necessarily the one the reminder is assigned to. user: $ref: '#/components/schemas/WebhookUser' contact_id: type: string format: uuid contact: $ref: '#/components/schemas/WebhookContact' followup_id: type: string format: uuid description: The reminder. No reminder record is attached. ContactTagData: type: object required: [team_id, contact_id, tag_id] properties: team_id: type: string format: uuid team: $ref: '#/components/schemas/WebhookTeam' user_id: type: [string, 'null'] user: $ref: '#/components/schemas/WebhookUser' contact_id: type: string format: uuid contact: $ref: '#/components/schemas/WebhookContact' tag_id: type: string format: uuid description: No tag record is attached here; the contact's current tags are in `contact.tags`. ContactExportCompleteData: type: object required: [team_id, export_id] properties: team_id: type: string format: uuid team: $ref: '#/components/schemas/WebhookTeam' user_id: type: [string, 'null'] description: The teammate who requested the export. user: $ref: '#/components/schemas/WebhookUser' export_id: type: string format: uuid export: $ref: '#/components/schemas/WebhookExport' ContactImportCompleteData: type: object required: [team_id, import_id, operation] properties: team_id: type: string format: uuid team: $ref: '#/components/schemas/WebhookTeam' import_id: type: string format: uuid import: $ref: '#/components/schemas/WebhookImport' operation: type: object properties: type: type: string enum: [import] id: type: string format: uuid description: Same as `import_id`. ContactListMembershipData: allOf: - $ref: '#/components/schemas/ContactOperationData' - type: object required: [team_id, list_id] properties: team_id: type: string format: uuid team: $ref: '#/components/schemas/WebhookTeam' user_id: type: [string, 'null'] description: >- The teammate or API key owner who made the change. Null for web forms; absent for list copies and moves and per-contact events. user: $ref: '#/components/schemas/WebhookUser' list_id: type: string format: uuid list: $ref: '#/components/schemas/WebhookList' previous_list_id: type: string format: uuid description: '`contact.list.added` from a move in the app or API only: the list the contacts left.' contact_ids: description: >- Up to 25 ids, sorted by when the contacts were created. Empty for list copies and moves. A web form submission sends its one id as a string. oneOf: - type: string format: uuid - type: array maxItems: 25 items: type: string format: uuid contact_id: type: string format: uuid description: Only on per-contact events, in place of `contact_ids`. contact: $ref: '#/components/schemas/WebhookContact' operation_id: type: string format: uuid description: Web form submissions only, which send it here rather than on the envelope. ContactTimelineEntryData: type: object required: [team_id, contact_id, entry_id] properties: team_id: type: string format: uuid team: $ref: '#/components/schemas/WebhookTeam' user_id: type: [string, 'null'] user: $ref: '#/components/schemas/WebhookUser' contact_id: type: string format: uuid contact: $ref: '#/components/schemas/WebhookContact' entry_id: type: string format: uuid timeline_entry: $ref: '#/components/schemas/WebhookTimelineEntry' action: type: string enum: [update] description: Always `update`, including when the entry was just created. ListChangeData: type: object required: [team_id, list_id] properties: team_id: type: string format: uuid team: $ref: '#/components/schemas/WebhookTeam' user_id: type: [string, 'null'] user: $ref: '#/components/schemas/WebhookUser' list_id: type: string format: uuid list: $ref: '#/components/schemas/WebhookList' PipelineStageMoveData: allOf: - $ref: '#/components/schemas/ContactOperationData' - type: object required: [team_id, board_id, list_id] properties: team_id: type: string format: uuid team: $ref: '#/components/schemas/WebhookTeam' user_id: type: [string, 'null'] user: $ref: '#/components/schemas/WebhookUser' board_id: type: string format: uuid description: The pipeline board. No board record is attached. list_id: type: string format: uuid description: The stage. list: $ref: '#/components/schemas/WebhookList' contact_ids: type: array maxItems: 25 items: type: string format: uuid description: Up to 25 ids, sorted by when the contacts were created. PipelineStageAgedData: type: object required: [team_id, contact_id, board_id, list_id, entered_at, stale_after_days, days_in_stage] properties: team_id: type: string format: uuid team: $ref: '#/components/schemas/WebhookTeam' contact_id: type: string format: uuid contact: $ref: '#/components/schemas/WebhookContact' board_id: type: string format: uuid list_id: type: string format: uuid description: The stage. list: $ref: '#/components/schemas/WebhookList' entered_at: type: integer description: When the contact entered the stage, epoch milliseconds. stale_after_days: type: integer description: The stage's aging threshold. days_in_stage: type: integer description: Whole days since `entered_at`. ContactInactivityData: type: object required: [team_id, contact_id, board_id, list_id, entered_at, days_inactive] properties: team_id: type: string format: uuid team: $ref: '#/components/schemas/WebhookTeam' contact_id: type: string format: uuid contact: $ref: '#/components/schemas/WebhookContact' board_id: type: string format: uuid list_id: type: string format: uuid description: The stage. list: $ref: '#/components/schemas/WebhookList' entered_at: type: integer description: When the contact entered the stage, epoch milliseconds. days_inactive: type: integer description: Whole days since `entered_at`. PipelineStageDurationData: type: object required: [team_id, contact_id, list_id, duration_ms, entered_at, due_at] properties: team_id: type: string format: uuid team: $ref: '#/components/schemas/WebhookTeam' contact_id: type: string format: uuid contact: $ref: '#/components/schemas/WebhookContact' board_id: type: [string, 'null'] description: The pipeline board when the list is a stage; null for a plain list. list_id: type: string format: uuid list: $ref: '#/components/schemas/WebhookList' duration_ms: type: integer description: The watched duration, in milliseconds. entered_at: type: integer description: When the contact entered the list, epoch milliseconds. due_at: type: integer description: '`entered_at` plus `duration_ms`.' AppointmentEventData: type: object description: | `data` of every `appointment.*` event. `team` and `contact` are the records for `team_id` and `contact_id`. Events with extra fields extend this schema. required: [team_id, team, booking_id, booking_type_id, contact_id, contact, start_at, source, join_url, location_type] properties: team_id: type: string format: uuid team: $ref: '#/components/schemas/WebhookTeam' booking_id: type: string format: uuid description: The appointment. For a group class, the class occurrence. booking_type_id: type: string format: uuid description: The booking type (meeting or class template) it was booked on. contact_id: type: [string, 'null'] format: uuid description: | The contact the event is about. For a whole-class `appointment.cancelled` it is null; see each event for who it names. contact: $ref: '#/components/schemas/WebhookContact' host_user_ids: type: array description: The team members hosting it. Only ids; no user records are attached. items: type: string format: uuid start_at: type: integer description: Scheduled start, epoch milliseconds. source: type: [string, 'null'] description: | Where the booking was made: usually `dashboard`, `public_page`, `embed`, `ai_voice`, `ai_chat`, `automation` or `api`. Null on bookings that did not record one. join_url: type: [string, 'null'] format: uri description: | Video meeting link, when the booking has one. Always null on `appointment.cancelled`, `appointment.waitlist.joined` and `appointment.attendee.cancelled`. It is the live meeting link, so treat it as you would a calendar invite. location_type: type: [string, 'null'] enum: [in_person, phone, google_meet, microsoft_teams, zoom, custom_link, null] description: How the appointment takes place. Null when the booking type has no location. AppointmentRescheduledData: description: '`data` of `appointment.rescheduled`.' allOf: - $ref: '#/components/schemas/AppointmentEventData' - type: object required: [previous_start_at] properties: previous_start_at: type: [integer, 'null'] description: The start time before the move, epoch milliseconds. AppointmentCancelledData: description: '`data` of `appointment.cancelled`.' allOf: - $ref: '#/components/schemas/AppointmentEventData' - type: object required: [cancel_reason] properties: cancel_reason: type: [string, 'null'] description: The reason given when cancelling, if any. Free text. attendee_count: type: integer description: Group class occurrences only. How many attendees the occurrence had. AppointmentUpdatedData: description: '`data` of `appointment.updated`.' allOf: - $ref: '#/components/schemas/AppointmentEventData' - type: object required: [changed] properties: changed: type: array description: Names of the fields that changed. Today always `[join_url]`. items: type: string AppointmentWaitlistJoinedData: description: '`data` of `appointment.waitlist.joined`.' allOf: - $ref: '#/components/schemas/AppointmentEventData' - type: object required: [party_id, waitlist_position] properties: party_id: type: string format: uuid description: The party, meaning everyone booked together in one submission. waitlist_position: type: [integer, 'null'] minimum: 1 description: The party's place in line, starting at 1. Null if it could not be determined. AppointmentWaitlistPromotedData: description: '`data` of `appointment.waitlist.promoted`.' allOf: - $ref: '#/components/schemas/AppointmentEventData' - type: object required: [party_id] properties: party_id: type: string format: uuid description: The party that was moved off the waitlist. AppointmentAttendeeBookedData: description: '`data` of `appointment.attendee.booked`. `contact_id` is the attendee.' allOf: - $ref: '#/components/schemas/AppointmentEventData' - type: object required: [attendee_id, party_id] properties: attendee_id: type: string format: uuid description: The attendee's seat in the occurrence. party_id: type: string format: uuid description: The party the attendee was booked with. AppointmentAttendeeCancelledData: description: '`data` of `appointment.attendee.cancelled`. `contact_id` is the attendee.' allOf: - $ref: '#/components/schemas/AppointmentEventData' - type: object required: [attendee_id, party_id, cancel_reason] properties: attendee_id: type: string format: uuid description: The attendee's seat in the occurrence. party_id: type: string format: uuid description: The party the attendee was booked with. cancel_reason: type: [string, 'null'] description: The reason given when cancelling, if any. Free text. ReviewRequestEventData: type: object description: | `data` of every `review_request.*` event. `team` is the record for `team_id`. The contact is sent as a small nested `contact` object, not the full contact record. Events with extra fields extend this schema. required: [team_id, team, review_request_id, contact, channel] properties: team_id: type: string format: uuid team: $ref: '#/components/schemas/WebhookTeam' review_request_id: type: string format: uuid contact: type: object description: | The contact the request was sent to. `review_request.sent` also carries `first_name`, `last_name`, `email` and `phone_number`, each null when unknown; the other events carry only `contact_id`. required: [contact_id] properties: contact_id: type: string format: uuid first_name: type: [string, 'null'] last_name: type: [string, 'null'] email: type: [string, 'null'] phone_number: type: [string, 'null'] description: E.164. channel: type: string description: | How the request was sent: `sms` or `email`. The value is passed through from whoever created the request without validation, and anything other than `email` is sent as a text. ReviewRequestSentData: description: '`data` of `review_request.sent`.' allOf: - $ref: '#/components/schemas/ReviewRequestEventData' - type: object required: [source, source_event, review_url] properties: source: type: string description: | What created the request: usually `manual` (a team member), `automation`, or `api`. Callers can set their own value. source_event: type: [string, 'null'] description: The event that triggered an automated request, such as `appointment.completed`. review_url: type: [string, 'null'] format: uri description: | The review page link sent to the contact. It contains a signed token that lets anyone holding the link rate, leave feedback and click out as this contact until it expires 30 days after sending. Do not publish it or pass it to systems that should not act for the contact. ReviewRequestRatedData: description: '`data` of `review_request.rated`.' allOf: - $ref: '#/components/schemas/ReviewRequestEventData' - type: object required: [sentiment] properties: sentiment: type: string enum: [positive, negative] description: The contact's rating. ReviewRequestClickedOutData: description: '`data` of `review_request.clicked_out`.' allOf: - $ref: '#/components/schemas/ReviewRequestEventData' - type: object required: [review_platform] properties: review_platform: type: string enum: [google, facebook] description: The review site the contact was sent to. ReviewRequestFeedbackSubmittedData: description: '`data` of `review_request.feedback_submitted`.' allOf: - $ref: '#/components/schemas/ReviewRequestEventData' - type: object required: [feedback_text] properties: feedback_text: type: string maxLength: 4000 description: The contact's feedback, trimmed and cut to 4000 characters. FormSubmittedData: type: object description: | `data` of `form.submitted`. `team`, `contact` and `list` are the records for `team_id`, `contact_id` and `list_id`. required: [team_id, team, contact_id, contact, form_id, form_name, list_id, list, page_url, submitted_at, form_fields, fields] properties: team_id: type: string format: uuid team: $ref: '#/components/schemas/WebhookTeam' contact_id: type: string format: uuid description: The contact the submission was saved to, new or existing. contact: $ref: '#/components/schemas/WebhookContact' form_id: type: string format: uuid description: The web form. form_name: type: [string, 'null'] list_id: type: [string, 'null'] format: uuid description: The list the form adds contacts to. Null when it has none. list: $ref: '#/components/schemas/WebhookList' page_url: type: [string, 'null'] description: The page the form was submitted from, when known. submitted_at: type: integer description: Epoch milliseconds. form_fields: type: array description: Each answered field, in form order. Fields left blank are omitted. items: type: object required: [name, label, type, value] properties: name: type: string description: The field's name, such as `first_name` or a custom field slug. label: type: string description: The label shown on the form. type: type: string description: The field type, such as `text`, `email` or `phone`. value: type: string description: The answer. Phone numbers are E.164. fields: type: object description: The same answers as `form_fields`, keyed by field name. additionalProperties: type: string ContactGoalAchievedData: type: object description: | `data` of `contact.goal.achieved`. `team` and `contact` are the records for `team_id` and `contact_id`. Goal settings the goal does not have are omitted. required: [team_id, team, contact_id, contact, goal_id, goal_name, goal_type, tag_ids, remove_tag_ids, list_ids, remove_from_list_ids, remove_from_all_calling_lists, visitor_id, event_name, page_url, achieved_at] properties: team_id: type: string format: uuid team: $ref: '#/components/schemas/WebhookTeam' contact_id: type: string format: uuid contact: $ref: '#/components/schemas/WebhookContact' goal_id: type: string format: uuid goal_name: type: string goal_type: type: string enum: [web] funnel_stage: type: string description: | Sometimes present. One of `awareness`, `interest`, `consideration`, `intent`, `evaluation`, `conversion`, `retention`, `qualified`, `negotiation`, `closed`, `churned`, `nurture`. category: type: string description: | Sometimes present. One of `acquisition`, `activation`, `engagement`, `conversion`, `revenue`, `compliance`, `retention`, `segmentation`, `pipeline`, `sales`. goal_value: type: number minimum: -10000 maximum: 10000 description: Sometimes present. The value you gave the goal; negative for goals that count against the score. priority: type: number minimum: 1 maximum: 10 description: Sometimes present. tag_ids: type: array description: Tags the goal adds to the contact. items: type: string format: uuid remove_tag_ids: type: array description: Tags the goal removes from the contact. items: type: string format: uuid list_ids: type: array description: Lists the goal adds the contact to. items: type: string format: uuid remove_from_list_ids: type: array description: Lists the goal removes the contact from. `*` means every list. items: type: string remove_from_all_calling_lists: type: boolean visitor_id: type: string format: uuid description: The tracking script's id for the visitor's browser. session_id: type: [string, 'null'] format: uuid description: The browsing session, when the script sent one. event_name: type: string description: The tracked event that met the goal, such as `page_view` or `conversion`. page_url: type: [string, 'null'] description: The page the event happened on, when known. achieved_at: type: integer description: Epoch milliseconds. ProductInterestCapturedData: type: object description: | `data` of `contact.product_interest.captured`. `team` and `contact` are the records for `team_id` and `contact_id`. required: [team_id, team, contact_id, contact, visitor_id, session_id, product_id, product_name, price_cents, currency, page_url, captured_at] properties: team_id: type: string format: uuid team: $ref: '#/components/schemas/WebhookTeam' contact_id: type: string format: uuid contact: $ref: '#/components/schemas/WebhookContact' visitor_id: type: [string, 'null'] format: uuid description: The tracking script's id for the visitor's browser, when known. session_id: type: [string, 'null'] format: uuid description: The browsing session, when known. product_id: type: string description: Your product identifier, exactly as your site sent it to the tracking script. product_name: type: [string, 'null'] price_cents: type: [integer, 'null'] description: The price in the currency's minor unit (cents for USD), when your site sent one. currency: type: [string, 'null'] description: Currency code as your site sent it, such as `USD`. page_url: type: [string, 'null'] description: The page the interest was shown on, when known. captured_at: type: integer description: When the interest was recorded, epoch milliseconds. WebEventLinkedData: type: object description: | `data` of `contact.web_event.linked`. `team` and `contact` are the records for `team_id` and `contact_id`. required: [team_id, team, contact_id, contact, visitor_id, linked_count] properties: team_id: type: string format: uuid team: $ref: '#/components/schemas/WebhookTeam' contact_id: type: string format: uuid contact: $ref: '#/components/schemas/WebhookContact' visitor_id: type: string format: uuid description: The tracking script's id for the visitor's browser. linked_count: type: integer minimum: 1 description: How many earlier website events were linked to the contact. WebEventTrackedData: type: object description: | `data` of `contact.web_event.tracked`. `team` is the record for `team_id`. No contact is included. required: [team_id, team, visitor_id, site_id, event_count] properties: team_id: type: string format: uuid team: $ref: '#/components/schemas/WebhookTeam' visitor_id: type: string format: uuid description: The tracking script's id for the visitor's browser. session_id: type: [string, 'null'] format: uuid description: The browsing session. Omitted when the script did not send one. site_id: type: string format: uuid description: The site the tracking script is installed on. event_count: type: integer minimum: 0 description: How many events from the batch were stored. RvmSentData: type: object description: | `data` for `contact.rvm.sent`. A voicemail sent from the app carries `rvm_id`; one sent through the Drop Cowboy integration carries `integration_id` and `entry_id` instead. required: [team_id] properties: team_id: type: string format: uuid team: $ref: '#/components/schemas/WebhookTeam' user_id: type: [string, 'null'] description: The teammate who sent the voicemail. user: $ref: '#/components/schemas/WebhookUser' contact_id: type: [string, 'null'] contact: $ref: '#/components/schemas/WebhookContact' rvm_id: type: string format: uuid description: Voicemails sent from the app only. integration_id: type: string description: Drop Cowboy integration sends only. integration: $ref: '#/components/schemas/WebhookIntegration' entry_id: type: [string, 'null'] description: Drop Cowboy integration sends only. The contact timeline entry for the voicemail. timeline_entry: $ref: '#/components/schemas/WebhookTimelineEntry' MessageOptOutData: type: object description: '`data` for `contact.msg.opt_out`.' required: [team_id, phone_number, opted_out_at] properties: team_id: type: string format: uuid team: $ref: '#/components/schemas/WebhookTeam' contact_id: type: [string, 'null'] description: Null when the number matches no contact. contact: $ref: '#/components/schemas/WebhookContact' phone_number: type: string description: The number that opted out, E.164. sms_body: type: [string, 'null'] description: The text the contact sent. consent_id: type: [string, 'null'] description: The consent record that stores the opt-out. number_id: type: [string, 'null'] description: Your number that received the text. ivr_id: type: [string, 'null'] description: The phone line that received the text. opted_out_at: type: integer description: Epoch milliseconds. MessageDispositionChangedData: type: object description: '`data` for `contact.msg.disposition.changed`.' required: [team_id, sms_id, disposition] properties: team_id: type: string format: uuid team: $ref: '#/components/schemas/WebhookTeam' user_id: type: [string, 'null'] description: The teammate who set the disposition. user: $ref: '#/components/schemas/WebhookUser' contact_id: type: [string, 'null'] contact: $ref: '#/components/schemas/WebhookContact' sms_id: type: string format: uuid description: The text the disposition was set on. sms: $ref: '#/components/schemas/WebhookSms' list_id: type: [string, 'null'] list: $ref: '#/components/schemas/WebhookList' disposition: type: string description: >- The new disposition: a built-in value such as `follow_up`, `sold`, `no_interest`, `bad_lead` or `dnc`, or the id of one of your custom dispositions. EmailSentData: type: object description: '`data` for `contact.email.sent`.' required: [email_id, team_id] properties: email_id: type: string format: uuid email: $ref: '#/components/schemas/WebhookEmail' team_id: type: string format: uuid team: $ref: '#/components/schemas/WebhookTeam' user_id: type: [string, 'null'] description: The teammate who sent the email; null for campaign and automation sends without one. user: $ref: '#/components/schemas/WebhookUser' contact_id: type: [string, 'null'] contact: $ref: '#/components/schemas/WebhookContact' entry_id: type: [string, 'null'] description: The contact timeline entry for the email. timeline_entry: $ref: '#/components/schemas/WebhookTimelineEntry' EmailReceivedData: type: object description: '`data` for `contact.email.received`.' required: [email_id, team_id] properties: email_id: type: string format: uuid email: $ref: '#/components/schemas/WebhookEmail' team_id: type: string format: uuid team: $ref: '#/components/schemas/WebhookTeam' user_id: type: [string, 'null'] description: The teammate the mailbox belongs to. user: $ref: '#/components/schemas/WebhookUser' contact_id: type: [string, 'null'] description: The sender's contact. contact: $ref: '#/components/schemas/WebhookContact' mailbox_id: type: [string, 'null'] description: The mailbox the email arrived in; null for a shared inbound address. EmailLinkClickedData: type: object description: '`data` for `contact.email.link_clicked`.' required: [team_id, contact_id, channel, url, verdict] properties: team_id: type: string format: uuid team: $ref: '#/components/schemas/WebhookTeam' contact_id: type: string description: The contact who sent the email. contact: $ref: '#/components/schemas/WebhookContact' task_id: type: [string, 'null'] description: The inbox conversation the email belongs to. email_id: type: [string, 'null'] description: The received email the link was in. email: $ref: '#/components/schemas/WebhookEmail' sms_id: type: 'null' description: Always null. sms: type: 'null' description: Always null. channel: type: string enum: [email] url: type: string description: The link's destination. url_domain: type: [string, 'null'] verdict: type: string enum: [safe, suspicious] description: The threat check's result. `suspicious` links show a warning page first. threat_types: type: array items: type: string description: What the threat check found; empty for a `safe` link. user_proceeded: type: boolean description: True when the teammate continued past the warning page for a `suspicious` link. clicked_at: type: integer description: Epoch milliseconds. FaxEventData: type: object description: '`data` for `contact.fax.sent`, `contact.fax.received` and `contact.fax.deleted`.' required: [team_id, fax_id] properties: team_id: type: string format: uuid team: $ref: '#/components/schemas/WebhookTeam' user_id: type: [string, 'null'] description: The teammate who sent or deleted the fax. Absent on `contact.fax.received`. user: $ref: '#/components/schemas/WebhookUser' contact_id: type: [string, 'null'] description: Always null on `contact.fax.deleted`. contact: $ref: '#/components/schemas/WebhookContact' fax_id: type: string format: uuid fax: $ref: '#/components/schemas/WebhookFax' VoicemailReceivedData: type: object description: '`data` for `contact.voicemail.received`.' required: [team_id, call_id, call_recording_id] properties: team_id: type: string format: uuid team: $ref: '#/components/schemas/WebhookTeam' user_id: type: [string, 'null'] description: The teammate the call was assigned to, if any. user: $ref: '#/components/schemas/WebhookUser' contact_id: type: [string, 'null'] description: Null when the caller matches no contact. contact: $ref: '#/components/schemas/WebhookContact' call_id: type: string call: $ref: '#/components/schemas/WebhookCall' call_recording_id: type: string call_recording: $ref: '#/components/schemas/WebhookCallRecording' ivr_id: type: [string, 'null'] description: The phone line that was dialed. VoicemailStateData: type: object description: '`data` for `contact.voicemail.read` and `contact.voicemail.unread`.' required: [team_id, call_recording_id] properties: team_id: type: string format: uuid team: $ref: '#/components/schemas/WebhookTeam' user_id: type: [string, 'null'] description: The teammate who changed the voicemail. user: $ref: '#/components/schemas/WebhookUser' call_recording_id: type: string call_recording: $ref: '#/components/schemas/WebhookCallRecording' ChatSessionData: type: object description: '`data` for `contact.chat.session_started`.' required: [team_id, contact_id, conversation_id] properties: team_id: type: string format: uuid team: $ref: '#/components/schemas/WebhookTeam' contact_id: type: string format: uuid contact: $ref: '#/components/schemas/WebhookContact' conversation_id: type: string format: uuid chat_site_id: type: [string, 'null'] description: The chat widget the conversation came from. ChatIdentifiedData: type: object description: '`data` for `contact.chat.identified`.' required: [team_id, contact_id, conversation_id, email, is_new] properties: team_id: type: string format: uuid team: $ref: '#/components/schemas/WebhookTeam' contact_id: type: string format: uuid contact: $ref: '#/components/schemas/WebhookContact' conversation_id: type: string format: uuid email: type: string description: The email address the visitor gave. is_new: type: boolean description: True when a new contact was created for the visitor. ChatMessageData: type: object description: '`data` for `contact.chat.received` and `contact.chat.sent`.' required: [team_id, conversation_id, message_id] properties: team_id: type: string format: uuid team: $ref: '#/components/schemas/WebhookTeam' contact_id: type: [string, 'null'] contact: $ref: '#/components/schemas/WebhookContact' conversation_id: type: string format: uuid message_id: type: string format: uuid chat_site_id: type: [string, 'null'] description: The chat widget the conversation came from. ChatConversationClosedData: type: object description: '`data` for `contact.chat.conversation_closed`.' required: [team_id, conversation_id] properties: team_id: type: string format: uuid team: $ref: '#/components/schemas/WebhookTeam' conversation_id: type: string format: uuid chat_site_id: type: 'null' description: Always null. RevenueContactData: type: object description: > Short summary of the contact the billing activity belongs to. This is not the full contact record, and the event does not attach one. properties: contact_id: type: string format: uuid first_name: type: [string, 'null'] last_name: type: [string, 'null'] email: type: [string, 'null'] description: Omitted when the contact has no email. main_phone: type: [string, 'null'] description: E.164. Omitted when the contact has no main phone. chargebee_customer_id: type: [string, 'null'] description: The linked Chargebee customer's id. RevenueEventData: type: object description: > `data` of the `revenue.*` events and `customer.trial.converted`. Money fields are integers in the minor unit (cents for USD) of the Chargebee currency; no currency code is sent. `team_id` is kept and its record attached as `team`. required: [team_id, contact, revenue_event, amount, mrr_impact] properties: team_id: type: string format: uuid team: $ref: '#/components/schemas/WebhookTeam' contact: $ref: '#/components/schemas/RevenueContactData' revenue_event: type: object description: The recorded revenue event. properties: revenue_event_id: type: string format: uuid event_type: type: string enum: - subscription_started - subscription_renewed - subscription_upgraded - subscription_downgraded - subscription_canceled - one_time_purchase - refund description: > Matches the webhook event. `customer.trial.converted` carries `subscription_started`, and `revenue.refund.created` carries `refund`. amount: type: integer description: > Minor units. The plan price or invoice total; negative for a refund and 0 for a cancellation. mrr_impact: type: integer description: > Change in monthly recurring revenue, minor units. Positive when a subscription starts or upgrades, negative on downgrade or cancellation, 0 on renewal and refund. arr_impact: type: integer description: Change in annual recurring revenue, minor units. `mrr_impact` times 12. plan_id: type: [string, 'null'] description: The Chargebee plan id. plan_name: type: [string, 'null'] description: Readable plan name derived from `plan_id`. billing_cycle: type: [string, 'null'] enum: [monthly, annual, null] previous_plan_id: type: [string, 'null'] description: Upgrades and downgrades only. The plan the contact moved off. is_new_customer: type: boolean description: True on the contact's first revenue event. is_expansion: type: boolean description: True on upgrades. is_contraction: type: boolean description: True on downgrades. billing_provider: type: string description: '`chargebee` for every event from your Chargebee site.' chargebee_customer_id: type: [string, 'null'] chargebee_subscription_id: type: [string, 'null'] chargebee_invoice_id: type: [string, 'null'] description: The invoice charged, or for a refund the invoice refunded. Null when no invoice was involved. customer_tenure_days: type: [integer, 'null'] description: Cancellations only. Days the contact was a customer. churn_reason: type: [string, 'null'] description: Cancellations only. Chargebee's cancel reason, when it gives one. is_trial_conversion: type: boolean description: > True when this subscription start converted a trial. This and the other trial fields are present only on events sent straight from Chargebee, not on held activity delivered after you link a customer. subscription_status: type: [string, 'null'] enum: [future, in_trial, active, non_renewing, paused, cancelled, null] description: Chargebee's subscription status when the event happened. Present only on events sent straight from Chargebee. trial_start: type: [integer, 'null'] description: Epoch milliseconds. Null when the subscription had no trial. Present only on events sent straight from Chargebee. trial_end: type: [integer, 'null'] description: Epoch milliseconds. Null when the subscription had no trial. Present only on events sent straight from Chargebee. created_at: type: integer description: When the billing activity happened. Epoch milliseconds. amount: type: integer description: Same as `revenue_event.amount`. mrr_impact: type: integer description: Same as `revenue_event.mrr_impact`. plan_name: type: [string, 'null'] description: Same as `revenue_event.plan_name`. billing_provider: type: string description: Same as `revenue_event.billing_provider`. CustomerTrialExpiringData: type: object description: > `data` of `customer.trial.expiring`. `team_id` is kept and its record attached as `team`. Money fields are integers in the minor unit (cents for USD) of the Chargebee currency; no currency code is sent. required: [team_id, contact, revenue_event, trial_end, days_remaining] properties: team_id: type: string format: uuid team: $ref: '#/components/schemas/WebhookTeam' contact: $ref: '#/components/schemas/RevenueContactData' revenue_event: type: object description: The contact's current trial. No revenue event is recorded for this webhook. properties: subscription_status: type: string enum: [in_trial] trial_start: type: [integer, 'null'] description: Epoch milliseconds. trial_end: type: integer description: Epoch milliseconds. trial_potential_mrr: type: [integer, 'null'] description: Monthly recurring revenue if the trial converts at its current plan, minor units. days_remaining: type: integer minimum: 0 description: Whole days until `trial_end`, rounded up. billing_provider: type: [string, 'null'] description: '`chargebee`.' chargebee_customer_id: type: [string, 'null'] trial_end: type: integer description: Same as `revenue_event.trial_end`. days_remaining: type: integer minimum: 0 description: Same as `revenue_event.days_remaining`. trial_potential_mrr: type: [integer, 'null'] description: Same as `revenue_event.trial_potential_mrr`. billing_provider: type: [string, 'null'] description: Same as `revenue_event.billing_provider`. CommissionEventData: type: object description: > `data` of the `commission.*` events. Money fields are integer cents. Only `team_id` is expanded (into `team`); `contact` and `recipient_user` carry ids only. On `commission.pending`, `goal` also carries the goal's `name`, `category` and `funnel_stage`, and `commission` carries only the fields set when it was created (no approval, rejection, payment or clawback fields, and no `modified_at`). Each event adds its own fields: `approved_by` (`commission.approved`), `rejected_by` and `reason` (`commission.rejected`), `paid_by` and `paid_at` (`commission.paid`). required: [team_id, commission] properties: team_id: type: string format: uuid team: $ref: '#/components/schemas/WebhookTeam' commission: type: object description: The commission. properties: commission_id: type: string format: uuid team_id: type: string format: uuid user_id: type: [string, 'null'] description: The teammate the commission is paid to. Null when a partner is paid instead. partner_id: type: [string, 'null'] description: The referral partner the commission is paid to, if any. goal_id: type: [string, 'null'] description: The goal that earned the commission. contact_id: type: [string, 'null'] closed_by_user_id: type: [string, 'null'] description: The teammate who last worked the contact when the deal closed. closed_by_user_group_id: type: [string, 'null'] amount: type: integer description: Commission amount, cents. deal_value: type: integer description: Value of the deal the commission is based on, cents. status: type: string enum: [pending, approved, rejected, paid] approved_by: type: [string, 'null'] description: User id. approved_at: type: [integer, 'null'] description: Epoch milliseconds. rejected_by: type: [string, 'null'] description: User id. rejected_at: type: [integer, 'null'] description: Epoch milliseconds. rejected_reason: type: [string, 'null'] paid_by: type: [string, 'null'] description: User id. paid_at: type: [integer, 'null'] description: Epoch milliseconds. flagged_for_clawback: type: boolean description: True when the sale behind the commission was later refunded. clawback_reason: type: [string, 'null'] clawback_at: type: [integer, 'null'] description: Epoch milliseconds. source_event_type: type: [string, 'null'] enum: [disposition, web_event, revenue, null] description: | What earned the commission: `disposition` for a call disposition that achieved a goal, `web_event` for a tracked website event, `revenue` for a subscription renewal under a recurring commission agreement. source_event_id: type: [string, 'null'] description: | Id of the disposition event, web event or revenue event that earned it. For `disposition`, this is the `disposition_event_id` sent in `disposition.completed`. created_at: type: integer description: Epoch milliseconds. modified_at: type: integer description: Epoch milliseconds. contact: type: object properties: contact_id: type: [string, 'null'] goal: type: object properties: goal_id: type: [string, 'null'] name: type: [string, 'null'] description: '`commission.pending` only. Null if the goal could not be read.' category: type: [string, 'null'] description: '`commission.pending` only.' funnel_stage: type: [string, 'null'] description: '`commission.pending` only.' recipient_user: type: object description: Who is paid. properties: user_id: type: [string, 'null'] partner_id: type: [string, 'null'] approved_by: type: string description: '`commission.approved` only. User id of the approver.' rejected_by: type: [string, 'null'] description: | `commission.rejected` only. User id of the teammate who rejected it, or null when it was rejected automatically because the sale was refunded. reason: type: [string, 'null'] description: | `commission.rejected` only. The reason the teammate gave, if any. For an automatic rejection, a description of the refund that caused it. paid_by: type: string description: '`commission.paid` only. User id of the teammate who marked it paid.' paid_at: type: integer description: '`commission.paid` only. Epoch milliseconds.' GoalTriggeredData: type: object description: > `data` of `goal.triggered`. The ids are kept and their records attached: `team`, `contact` (null when `contact_id` is null), and on disposition goals `user` and `call`. Web goals carry `visitor_id`, `session_id`, `event_id`, `event_name` and `page_url`. Call and disposition goals carry `source_type`, `user_id`, `disposition_id`, `call_id` and `commission_id` instead. required: [team_id, goal_id, achieved_at] properties: team_id: type: string format: uuid team: $ref: '#/components/schemas/WebhookTeam' goal_id: type: string format: uuid goal_name: type: string goal_type: type: string enum: [web, call, disposition] funnel_stage: type: [string, 'null'] enum: [awareness, interest, consideration, intent, evaluation, conversion, retention, qualified, negotiation, closed, churned, nurture, null] category: type: [string, 'null'] enum: [acquisition, activation, engagement, conversion, revenue, compliance, retention, segmentation, pipeline, sales, null] goal_value: type: [number, 'null'] description: The value you gave the goal, in whole dollars, not cents. Can be negative. priority: type: [integer, 'null'] minimum: 1 maximum: 10 tag_ids: type: array description: Tags the goal adds to the contact. items: type: string list_ids: type: array description: Lists the goal adds the contact to. items: type: string contact_id: type: [string, 'null'] description: Null when the website visitor isn't linked to a contact. Always set on call and disposition goals. contact: $ref: '#/components/schemas/WebhookContact' visitor_id: type: string description: Web goals only. The tracked website visitor. session_id: type: string description: Web goals only. event_id: type: string description: Web goals only. The tracked website event that achieved the goal, not the webhook's `event_id`. event_name: type: string description: Web goals only. page_url: type: [string, 'null'] description: Web goals only. source_type: type: string enum: [disposition] description: Call and disposition goals only. What achieved the goal. user_id: type: [string, 'null'] description: Call and disposition goals only. The teammate who set the disposition. user: $ref: '#/components/schemas/WebhookUser' disposition_id: type: [string, 'null'] description: Call and disposition goals only. The disposition that achieved the goal, such as `sold`. call_id: type: [string, 'null'] description: Call and disposition goals only. The call that was dispositioned. call: $ref: '#/components/schemas/WebhookCall' commission_id: type: [string, 'null'] description: | Call and disposition goals only. The pending commission the goal created, which is also announced by `commission.pending`. Null when the goal pays no commission. achieved_at: type: integer description: Epoch milliseconds. DispositionCompletedData: type: object description: > `data` of `disposition.completed`: the call disposition as it was evaluated against your goals. The ids are kept and their records attached: `team`, `user`, `contact`, `call` and `list`. required: [disposition_event_id, event_type, team_id, contact_id, goal_ids, created_at] properties: disposition_event_id: type: string format: uuid description: | Id of this evaluation. A commission it created carries the same value as `commission.source_event_id`. event_type: type: string enum: [disposition] team_id: type: string format: uuid team: $ref: '#/components/schemas/WebhookTeam' contact_id: type: string format: uuid contact: $ref: '#/components/schemas/WebhookContact' user_id: type: [string, 'null'] description: The teammate who set the disposition. user: $ref: '#/components/schemas/WebhookUser' disposition_id: type: [string, 'null'] description: The disposition, with the same values as on `contact.call.disposition`, such as `sold` or `dnc`. disposition_sentiment: type: [string, 'null'] enum: [good, neutral, bad, null] disposition_label: type: [string, 'null'] description: Always null for call dispositions. disposition_friendly: type: [string, 'null'] description: Always null for call dispositions. disposition_key: type: [string, 'null'] description: Always null for call dispositions. call_id: type: [string, 'null'] call: $ref: '#/components/schemas/WebhookCall' call_duration: type: number description: Seconds; 0 when unknown. call_type: type: [string, 'null'] enum: [inbound, outbound, null] description: The call's direction. campaign_id: type: [string, 'null'] description: Always null for call dispositions. campaign_name: type: [string, 'null'] description: Always null for call dispositions. list_id: type: [string, 'null'] description: The list the call was dialed from. list: $ref: '#/components/schemas/WebhookList' list_name: type: [string, 'null'] description: Always null for call dispositions; read `list.list_name`. calls_to_contact: type: [integer, 'null'] description: How many calls your team has made to the contact. Null if it could not be counted. attribution: type: [object, 'null'] description: Always null for call dispositions. deal_value: type: [integer, 'null'] description: Always null for call dispositions. goal_ids: type: array description: The goals this disposition achieved. Empty when none matched. items: type: string format: uuid created_at: type: integer description: Epoch milliseconds. DispositionGoalTriggeredData: type: object description: > `data` of `disposition.goal.triggered`. `team`, `user` and `contact` are attached; the ids inside `disposition` are not expanded. required: [team_id, contact_id, goal_id, disposition, achieved_at] properties: team_id: type: string format: uuid team: $ref: '#/components/schemas/WebhookTeam' contact_id: type: string format: uuid contact: $ref: '#/components/schemas/WebhookContact' user_id: type: [string, 'null'] description: The teammate who set the disposition. user: $ref: '#/components/schemas/WebhookUser' goal_id: type: string format: uuid goal_name: type: string funnel_stage: type: [string, 'null'] description: Same values as on `goal.triggered`. category: type: [string, 'null'] description: Same values as on `goal.triggered`. goal_value: type: [number, 'null'] description: The value you gave the goal, in whole dollars, not cents. priority: type: [integer, 'null'] disposition: type: object description: The disposition that achieved the goal and the call it was set on. properties: disposition_id: type: [string, 'null'] disposition_sentiment: type: [string, 'null'] enum: [good, neutral, bad, null] call_id: type: [string, 'null'] call_duration: type: number description: Seconds; 0 when unknown. call_type: type: [string, 'null'] enum: [inbound, outbound, null] description: The call's direction. campaign_id: type: [string, 'null'] description: Always null for call dispositions. list_id: type: [string, 'null'] deal_value: type: [integer, 'null'] description: Always null for call dispositions. commission_id: type: [string, 'null'] description: The pending commission the goal created, if it pays one. achieved_at: type: integer description: Epoch milliseconds. DispositionSaleClosedData: type: object description: > `data` of `disposition.sale.closed`. Money fields are integer cents. `team`, `user`, `contact` and `call` are attached. required: [team_id, contact_id, goal_id, disposition_id, closed_at] properties: team_id: type: string format: uuid team: $ref: '#/components/schemas/WebhookTeam' contact_id: type: string format: uuid contact: $ref: '#/components/schemas/WebhookContact' user_id: type: [string, 'null'] description: The teammate who closed the sale. user: $ref: '#/components/schemas/WebhookUser' goal_id: type: string format: uuid goal_name: type: string deal_value: type: [integer, 'null'] description: The commission's deal value, cents. Null when the goal pays no commission. commission_id: type: [string, 'null'] description: Null when the goal pays no commission. commission_amount: type: [integer, 'null'] description: The commission amount, cents. Null when the goal pays no commission. disposition_id: type: string enum: [sold] call_id: type: [string, 'null'] call: $ref: '#/components/schemas/WebhookCall' campaign_id: type: [string, 'null'] description: Always null for call dispositions. closed_at: type: integer description: Epoch milliseconds. DispositionDncRequestedData: type: object description: > `data` of `disposition.dnc.requested`. `team`, `user`, `contact`, `call` and `list` are attached. required: [team_id, contact_id, goal_id, disposition_id, requested_at] properties: team_id: type: string format: uuid team: $ref: '#/components/schemas/WebhookTeam' contact_id: type: string format: uuid contact: $ref: '#/components/schemas/WebhookContact' user_id: type: [string, 'null'] description: The teammate who set the disposition. user: $ref: '#/components/schemas/WebhookUser' goal_id: type: string format: uuid description: The goal the `dnc` disposition achieved. goal_name: type: string disposition_id: type: string enum: [dnc] call_id: type: [string, 'null'] call: $ref: '#/components/schemas/WebhookCall' campaign_id: type: [string, 'null'] description: Always null for call dispositions. list_id: type: [string, 'null'] description: The list the call was dialed from. list: $ref: '#/components/schemas/WebhookList' requested_at: type: integer description: Epoch milliseconds. DomainEmailBounceData: type: object description: > `data` of `domain.email.bounce` and `domain.email.bounce_hard`, one recipient per event. `team`, and when the ids are set `email` and `contact`, are attached. required: [team_id, domain, message_id, recipient, bounce_type, timestamp] properties: team_id: type: string format: uuid team: $ref: '#/components/schemas/WebhookTeam' domain: type: string description: The sending domain, such as `mail.example.com`. message_id: type: string description: The message id the email was sent with. email_id: type: [string, 'null'] description: The Drop Cowboy email that bounced. Null when the message was not sent from Drop Cowboy. email: $ref: '#/components/schemas/WebhookEmail' contact_id: type: [string, 'null'] description: The contact the bounced email was sent to. Null when unknown. contact: $ref: '#/components/schemas/WebhookContact' recipient: type: string format: email description: The address that bounced. bounce_type: type: string enum: [Permanent, Transient, Undetermined] description: '`Permanent` on `domain.email.bounce_hard`; `Transient` or `Undetermined` on `domain.email.bounce`.' bounce_sub_type: type: [string, 'null'] description: The receiving server's reason category, such as `General`, `NoEmail`, `Suppressed` or `MailboxFull`. diagnostic_code: type: [string, 'null'] description: The receiving server's response, when it gave one. timestamp: type: integer description: When the bounce was processed, epoch milliseconds. DomainEmailComplaintData: type: object description: > `data` of `domain.email.complaint`, one recipient per event. `team`, and when the ids are set `email` and `contact`, are attached. required: [team_id, domain, message_id, recipient, timestamp] properties: team_id: type: string format: uuid team: $ref: '#/components/schemas/WebhookTeam' domain: type: string description: The sending domain, such as `mail.example.com`. message_id: type: string description: The message id the email was sent with. email_id: type: [string, 'null'] description: The Drop Cowboy email that was reported. Null when the message was not sent from Drop Cowboy. email: $ref: '#/components/schemas/WebhookEmail' contact_id: type: [string, 'null'] description: The contact the email was sent to. Null when unknown. contact: $ref: '#/components/schemas/WebhookContact' recipient: type: string format: email description: The address that marked the email as spam. feedback_type: type: string description: The complaint type reported by the recipient's mailbox provider, such as `abuse`. Absent when the provider did not give one. timestamp: type: integer description: When the complaint was processed, epoch milliseconds. responses: BadRequest: description: | Invalid request parameters. The code in `type` depends on the route, for example `validation-error`, `bad-request`, `invalid-request`, `invalid-parameters` or `missing-parameters`; branch on `status`. content: application/json: schema: $ref: '#/components/schemas/Error' example: type: https://api-v2.dropcowboy.com/errors/validation-error title: Validation Error status: 400 detail: Request validation failed Unauthorized: description: Authentication required or invalid credentials content: application/json: schema: $ref: '#/components/schemas/Error' example: type: https://api-v2.dropcowboy.com/errors/unauthorized title: Unauthorized status: 401 detail: Invalid API key or secret instance: /contact/public/contacts Forbidden: description: Insufficient permissions content: application/json: schema: $ref: '#/components/schemas/Error' example: type: https://api-v2.dropcowboy.com/errors/forbidden title: Forbidden status: 403 detail: Insufficient permissions for this operation NotFound: description: Resource not found content: application/json: schema: $ref: '#/components/schemas/Error' example: type: https://api-v2.dropcowboy.com/errors/not-found title: Not Found status: 404 detail: The requested resource was not found DetectionKeyServiceUnavailable: description: The Detection key store could not be reached. Nothing was changed; retry. content: application/json: schema: $ref: '#/components/schemas/Error' example: type: https://api-v2.dropcowboy.com/errors/service-unavailable title: Service Unavailable status: 503 detail: Detection key service is unavailable, try again Conflict: description: Resource conflict content: application/json: schema: $ref: '#/components/schemas/Error' example: type: https://api-v2.dropcowboy.com/errors/conflict title: Conflict status: 409 detail: A resource with this identifier already exists PaymentRequired: description: | Nothing was done because of billing: either the plan allotment and prepaid balance are both empty, or the subscription payment failed and the account is past due. Always problem details with `type` `.../errors/payment-required`. When the account is past due, email and campaign routes add `details.dunning_tier` (`restricted`, `suspended` or `canceled`). Top up or fix the payment method in the dashboard, then retry. content: application/json: schema: $ref: '#/components/schemas/Error' examples: balance: summary: Balance exhausted value: type: https://api-v2.dropcowboy.com/errors/payment-required title: Payment Required status: 402 detail: Insufficient balance for this request instance: /voice/public/tts/synthesize request_id: 5d7f9b1c-3e2a-4c6e-8a0f-2b4d6f8a0c35 pastDue: summary: Subscription payment failed value: type: https://api-v2.dropcowboy.com/errors/payment-required title: Payment Required status: 402 detail: Your subscription payment didn't go through, so sending is paused. This is separate from your messaging funds balance. Update your payment method to resume sending campaigns. instance: /campaign/public/campaigns request_id: 9e1a3c5f-7b2d-4f4e-a6c8-0d2f4b6e8a17 details: dunning_tier: suspended TooManyRequests: description: | Your account went over the rate limit of 1,500 requests per second, with bursts to 2,000, across all routes. Retry with exponential backoff and jitter. content: application/json: schema: $ref: '#/components/schemas/ThrottleError' example: message: Too Many Requests ServerError: description: Unexpected failure. Safe to retry idempotent reads; for writes, check state first. content: application/json: schema: $ref: '#/components/schemas/Error' example: type: https://api-v2.dropcowboy.com/errors/server-error title: Server Error status: 500 detail: Internal error instance: /contact/public/contacts request_id: 0b8f5d2e-3c4a-4e1f-9a7b-6d5c4b3a2f10 InsufficientScope: description: The credentials are valid but lack every scope this operation accepts. content: application/json: schema: $ref: '#/components/schemas/Error' example: type: https://api-v2.dropcowboy.com/errors/insufficient-scope title: Insufficient Scope status: 403 detail: 'This endpoint requires one of: contacts:read' instance: /contact/public/contacts SendBadRequest: description: | Nothing was sent. The body was empty, was not a JSON object or was larger than 256 KB, or `x-key`, `x-secret`, `Authorization` or `Idempotency-Key` contained characters other than printable ASCII. Fix the request before retrying. content: application/json: schema: $ref: '#/components/schemas/Error' examples: emptyBody: summary: Empty or non-JSON body value: type: https://api-v2.dropcowboy.com/errors/validation-error title: Validation Error status: 400 detail: The request body must be a non-empty JSON object. instance: /sms request_id: 4e1b7c9a-2d6f-4a8e-b3c5-9f0a1d2e3b4c details: [] notQueued: summary: Body too large or headers not printable ASCII value: type: https://api-v2.dropcowboy.com/errors/validation-error title: Validation Error status: 400 detail: The request could not be queued. The body must be JSON under 256 KB and the X-Key, X-Secret, Authorization and Idempotency-Key headers must be printable ASCII. instance: /sms request_id: 8a2c4e6f-1b3d-4f5a-9c7e-0d2f4a6c8e1b details: [] SendMissingCredentials: description: | The request carried no credentials: neither a non-blank `x-key` and `x-secret` pair nor an `Authorization: Bearer` token. Nothing was sent. Credentials that are present but wrong (a bad key or secret, or a token that is invalid, expired or lacks the send scope) are not caught here: they get `202`, and the failure (`reason_code` 3007, Not authorized) is posted to `callback_url` only. content: application/json: schema: $ref: '#/components/schemas/Error' example: type: https://api-v2.dropcowboy.com/errors/missing-credentials title: Unauthorized status: 401 detail: 'Send an API key in X-Key and X-Secret, or an access token in Authorization: Bearer.' instance: /sms request_id: 2d4f6a8c-0e1b-4c3d-8f5a-7b9c1d3e5f70 SendNotQueued: description: | We couldn't queue the request and nothing was sent. Retry with backoff and the same `Idempotency-Key`. This body is not a problem-details object. content: application/json: schema: $ref: '#/components/schemas/AsyncError' example: status: error SendQueueNoReceipt: description: | We couldn't confirm the request was queued. Retry with the same `Idempotency-Key`: if the first attempt went through, the retry is skipped as a duplicate. content: application/json: schema: $ref: '#/components/schemas/Error' example: type: https://api-v2.dropcowboy.com/errors/internal-error title: Bad Gateway status: 502 detail: The message was not queued. It is safe to retry. instance: /sms request_id: 6f8a0c2e-4b1d-4e3f-a5c7-9d1b3f5a7c90 KnowledgeForbidden: description: | Either the credentials lack the scope (`insufficient-scope`), or the caller is a signed-in user whose role cannot use or manage AI agents (`forbidden`, with `detail` "You do not have permission to use Knowledge Bases." on reads or "You do not have permission to manage Knowledge Bases." on changes). Team API keys are never limited by role. If the role cannot be looked up the answer is `500` (`server-error`) instead. content: application/json: schema: $ref: '#/components/schemas/Error' examples: scope: summary: Missing scope value: type: https://api-v2.dropcowboy.com/errors/insufficient-scope title: Insufficient Scope status: 403 detail: 'This endpoint requires one of: agents:write' instance: /document/public/knowledge-bases role: summary: User role cannot manage knowledge bases value: type: https://api-v2.dropcowboy.com/errors/forbidden title: Forbidden status: 403 detail: You do not have permission to manage Knowledge Bases. instance: /document/public/knowledge-bases request_id: 3c5e7a9b-1d2f-4a6c-8e0b-5f7d9a1c3e62 AgentForbidden: description: | Either the credentials lack the scope (`insufficient-scope`), or the caller is a signed-in user whose role does not allow it (`forbidden`). `detail` is "You do not have permission to manage AI Agents." when the role has no AI agent access, or "Not authorized to perform this AI chat action" when the role may not create, change, delete or publish agents. Team API keys are never limited by role. If the role cannot be looked up the answer is `500` (`server-error`) instead. content: application/json: schema: $ref: '#/components/schemas/Error' examples: scope: summary: Missing scope value: type: https://api-v2.dropcowboy.com/errors/insufficient-scope title: Insufficient Scope status: 403 detail: 'This endpoint requires one of: agents:write' instance: /agents/public/agents role: summary: User role has no AI agent access value: type: https://api-v2.dropcowboy.com/errors/forbidden title: Forbidden status: 403 detail: You do not have permission to manage AI Agents. instance: /agents/public/agents request_id: 8a2c4e6f-0b1d-4f3a-9c5e-7d9b1f3a5c84 action: summary: User role may not publish agents value: type: https://api-v2.dropcowboy.com/errors/forbidden title: Forbidden status: 403 detail: Not authorized to perform this AI chat action instance: /agents/public/agents/4e6a8c0d-2f1b-4d3e-a5c7-9b1d3f5e7a26/publish request_id: 1f3b5d7e-9a2c-4e6f-8b0d-2c4e6a8f0b13