{"openapi":"3.1.0","info":{"title":"CallLoop Public API","description":"REST API for managing contacts and sending SMS, voice, and ringless messages from your own systems.\n\n**Personal Access Token.** An organization owner creates a token in CallLoop (API settings, or GraphQL `createPersonalAccessToken`). The secret is shown once. Grant `contact:read` / `contact:write` and `message:read` / `message:write` as needed.\n\n**Header.** Send every request with `Authorization: Bearer pat_v1.<token>`. The `pat_v1.` prefix is required.\n\n**Phone numbers.** Prefer E.164 (`+15551234567`). National US numbers without a country code are accepted. `fromNumber` on send must belong to your account. Invalid `fromNumber` or `toNumber` returns HTTP 400 naming the field.\n\n**Send, then poll.** `POST /api/public/v1/message/sms|voice|ringless` accepts the message and returns HTTP 200 with `state.status` = `Queued`. That means it is accepted for sending, not that the carrier delivered it. Poll `GET /api/public/v1/message/{id}` with the returned numeric `id` or `state.uuid` until a terminal status (`Delivered`, `Sent`, `Failed`, `Skipped`, and similar).\n\n**Rate limits.** The gateway may return HTTP 429 when the organization exceeds its request quota. SMS from a given number may also be throttled (about 2 messages per second when throttling is enabled). Retry with backoff.\n\n**Media IDs.** `assetIds`, `cardIds`, `audioIds`, and `audioId` must already exist in the account. Upload audio and assets in CallLoop first; send endpoints do not accept file uploads.\n","version":"v1"},"servers":[{"url":"https://api.callloop.com"}],"security":[{"bearerAuth":[]}],"tags":[{"name":"Contacts","description":"Create, look up, and update contacts. Requires a PAT with contact:read or contact:write."},{"name":"Messages","description":"Send SMS, voice, and ringless voicemail, then poll for delivery. Requires a PAT with message:read or message:write."}],"paths":{"/api/public/v1/contacts/{contactId}":{"get":{"tags":["Contacts"],"summary":"Get a contact","description":"Use this to load one contact by numeric ID after create, or when you already store the ID.\n\nRequired: path `contactId`. Optional: none.\n","operationId":"getById","parameters":[{"name":"contactId","in":"path","description":"Contact ID","required":true,"schema":{"type":"integer","format":"int64"}}],"responses":{"200":{"description":"Contact as stored (`status`, opt-in, lists, custom fields). Not a message delivery confirmation.","content":{"application/json":{"schema":{"$ref":"#/components/schemas/ContactLightResponse"},"example":{"id":1,"firstName":"Jane","lastName":"Doe","phone":"+15555550100","status":"Active","subscriptionState":"SUBSCRIBED"}}}}},"security":[{"bearerAuth":[]}]},"put":{"tags":["Contacts"],"summary":"Update a contact","description":"Use this to replace fields on an existing contact (full update).\n\nRequired: path `contactId` and a body. `phoneNumber` is required to change or keep the number. Other fields are optional; omitted values may be cleared depending on the field.\n","operationId":"update","parameters":[{"name":"contactId","in":"path","description":"Contact ID","required":true,"schema":{"type":"integer","format":"int64"}}],"requestBody":{"content":{"application/json":{"schema":{"$ref":"#/components/schemas/ContactApiRequest"}}},"required":true},"responses":{"200":{"description":"Contact after save. Not a send or delivery result.","content":{"application/json":{"schema":{"$ref":"#/components/schemas/ContactLightResponse"},"example":{"id":1,"firstName":"Jane","lastName":"Doe","phone":"+15555550100","status":"Active","subscriptionState":"SUBSCRIBED"}}}}},"security":[{"bearerAuth":[]}]},"patch":{"tags":["Contacts"],"summary":"Partially update a contact","description":"Use this to change only the fields you send (partial update). Unspecified fields are left as they are.\n\nRequired: path `contactId`. Optional body: any subset of contact fields, including an empty body.\n","operationId":"patch","parameters":[{"name":"contactId","in":"path","description":"Contact ID","required":true,"schema":{"type":"integer","format":"int64"}}],"requestBody":{"content":{"application/json":{"schema":{"$ref":"#/components/schemas/ContactApiRequest"}}}},"responses":{"200":{"description":"Contact after the patch. Not a send or delivery result.","content":{"application/json":{"schema":{"$ref":"#/components/schemas/ContactLightResponse"},"example":{"id":1,"firstName":"Jane","lastName":"Doe","phone":"+15555550100","status":"Active","subscriptionState":"SUBSCRIBED"}}}}},"security":[{"bearerAuth":[]}]}},"/api/public/v1/contacts/opt-in-status":{"put":{"tags":["Contacts"],"summary":"Update contact opt-in status","description":"Use this to set SMS and call/ringless subscription states for one contact.\n\nRequired: `contactId` plus `textMessageState` and/or `callAndRinglessState` (`SUBSCRIBED` or `SOFT_UNSUBSCRIBED`). Optional: omit a channel to leave it unchanged if the body allows.\n","operationId":"updateContactOptInStatus","requestBody":{"content":{"application/json":{"schema":{"$ref":"#/components/schemas/ContactOptInStatusData"},"example":{"contactId":1,"textMessageState":"SUBSCRIBED","callAndRinglessState":"SOFT_UNSUBSCRIBED"}}},"required":true},"responses":{"200":{"description":"Saved opt-in payload. No message is sent.","content":{"application/json":{"schema":{"$ref":"#/components/schemas/ContactOptInStatusData"},"example":{"contactId":1,"textMessageState":"SUBSCRIBED","callAndRinglessState":"SOFT_UNSUBSCRIBED"}}}}},"security":[{"bearerAuth":[]}]}},"/api/public/v1/message/voice":{"post":{"tags":["Messages"],"summary":"Send a voice message","description":"Use this to queue one outbound voice call (uploaded audio for live answer and/or voicemail).\n\nRequired: `fromNumber`, `toNumber`, and `voiceMessage` with at least one enabled audio (`liveAnswer.audioId` and/or `voiceMail.audioId`). Optional: `validOnly` (skip DNC contacts), `premiumAmDetectionEnabled`, transfer settings. Audio IDs must already exist in the account.\n","operationId":"sendVoice","requestBody":{"content":{"application/json":{"schema":{"$ref":"#/components/schemas/ApiSendVoiceRequest"}}},"required":true},"responses":{"200":{"description":"Call queued (`state.status` = `Queued`), not answered or delivered. DNC + `validOnly` may already be `Skipped`.","content":{"*/*":{"schema":{"$ref":"#/components/schemas/MessageDataResponse"}}}},"400":{"description":"Invalid payload, missing audio, or `fromNumber` / `toNumber` failed format validation."}},"security":[{"bearerAuth":[]}]}},"/api/public/v1/message/sms":{"post":{"tags":["Messages"],"summary":"Send an SMS message","description":"Use this to queue one SMS. The destination contact is matched or created from `toNumber`.\n\nRequired: `fromNumber` (a number on your account), `toNumber`, and `smsMessage` (`text` is the body). Optional: `validOnly`, `compliance` / `complianceText`, `assetIds`, `cardIds`, `audioIds`. Those media IDs must already exist in CallLoop; this endpoint does not upload files. `validOnly` is accepted on SMS but DNC skip at queue time applies to voice and ringless.\n","operationId":"sendSms","requestBody":{"content":{"application/json":{"schema":{"$ref":"#/components/schemas/ApiSendSmsRequest"}}},"required":true},"responses":{"200":{"description":"Message queued (`state.status` = `Queued`), not delivered. Poll GET /api/public/v1/message/{id}.","content":{"*/*":{"schema":{"$ref":"#/components/schemas/MessageDataResponse"}}}},"400":{"description":"Invalid payload, or `fromNumber` / `toNumber` failed format validation."}},"security":[{"bearerAuth":[]}]}},"/api/public/v1/message/ringless":{"post":{"tags":["Messages"],"summary":"Send a ringless voicemail","description":"Use this to queue a ringless voicemail drop.\n\nRequired: `fromNumber`, `toNumber`, and `ringlessMessage.audioId` (an audio item already uploaded in CallLoop). Optional: `validOnly` (skip DNC contacts), `callbackEnabled`.\n","operationId":"sendRingless","requestBody":{"content":{"application/json":{"schema":{"$ref":"#/components/schemas/ApiSendRinglessRequest"}}},"required":true},"responses":{"200":{"description":"Drop queued (`state.status` = `Queued`), not deposited. DNC + `validOnly` may already be `Skipped`.","content":{"*/*":{"schema":{"$ref":"#/components/schemas/MessageDataResponse"}}}},"400":{"description":"Invalid payload, missing `audioId`, or `fromNumber` / `toNumber` failed format validation."}},"security":[{"bearerAuth":[]}]}},"/api/public/v1/contacts":{"get":{"tags":["Contacts"],"summary":"List organization contacts","description":"Use this to page through contacts in the authenticated organization, optionally filtered.\n\nRequired: a PAT with `contact:read`. Optional query filters: `status`, `searchPattern`, subscription states, excluded lists/contacts. Pagination: `page`, `size` (default 20, max 250), `sort`.\n","operationId":"getAll","parameters":[{"name":"status","in":"query","description":"Contact lifecycle filter (`Active`, `Archived`, …). Not a message delivery status.","required":false,"schema":{"$ref":"#/components/schemas/ContactStatus"}},{"name":"searchPattern","in":"query","required":false,"schema":{"type":"string"}},{"name":"subscriptionStates","in":"query","required":false,"schema":{"type":"array","items":{"type":"string","enum":["SUBSCRIBED","HARD_UNSUBSCRIBED","SOFT_UNSUBSCRIBED","RE_SUBSCRIBED"]}}},{"name":"callSubscriptionStates","in":"query","required":false,"schema":{"type":"array","items":{"type":"string","enum":["SUBSCRIBED","HARD_UNSUBSCRIBED","SOFT_UNSUBSCRIBED","RE_SUBSCRIBED"]}}},{"name":"excludedListIds","in":"query","required":false,"schema":{"type":"array","items":{"type":"integer","format":"int64"}}},{"name":"excludedContactIds","in":"query","required":false,"schema":{"type":"array","items":{"type":"integer","format":"int64"}}},{"name":"page","in":"query","description":"Zero-based page index (0..N)","required":false,"schema":{"type":"integer","default":0,"minimum":0}},{"name":"size","in":"query","description":"Number of contacts per page. Default: 20. Max: 250.","required":false,"schema":{"type":"integer","default":20,"minimum":1}},{"name":"sort","in":"query","description":"Sorting criteria in the format: property,(asc|desc). Default sort order is ascending. Multiple sort criteria are supported.","required":false,"schema":{"type":"array","items":{"type":"string"}}}],"responses":{"200":{"description":"Page of contacts as stored. `status` is the contact lifecycle (`Active`, `Archived`, …), not a message delivery result. Deleted contacts are not listed.","content":{"application/json":{"schema":{"$ref":"#/components/schemas/PageResponseContactLightResponse"},"example":{"content":[{"id":1,"firstName":"Jane","lastName":"Doe","phone":"+15555550100","status":"Active","subscriptionState":"SUBSCRIBED"}],"pageNumber":0,"pageSize":20,"total":1}}}}},"security":[{"bearerAuth":[]}]},"post":{"tags":["Contacts"],"summary":"Create a contact","description":"Use this to add a contact before sending, or to store CRM fields you will personalize later.\n\nRequired: `phoneNumber`. Optional: name, email, `timeZoneType`, `customFields`, `listIds`. Duplicate phone numbers for the same organization are rejected (unless the existing row was deleted).\n","operationId":"create","requestBody":{"content":{"application/json":{"schema":{"$ref":"#/components/schemas/ContactApiRequest"}}},"required":true},"responses":{"200":{"description":"Contact saved. Does not mean a message was sent or delivered.","content":{"application/json":{"schema":{"$ref":"#/components/schemas/ContactLightResponse"},"example":{"id":1,"firstName":"Jane","lastName":"Doe","phone":"+15555550100","status":"Active","subscriptionState":"SUBSCRIBED"}}}}},"security":[{"bearerAuth":[]}]},"delete":{"tags":["Contacts"],"summary":"Delete contacts","description":"Use this to permanently delete a known set of contacts by ID.\n\nRequired: query `contactIds`. Optional: none.\n","operationId":"delete","parameters":[{"name":"contactIds","in":"query","description":"IDs of contacts to delete","required":true,"schema":{"type":"array","items":{"type":"integer","format":"int64"}}}],"responses":{"200":{"description":"Confirmation string with the number deleted. Deletes are finished in this response.","content":{"application/json":{"schema":{"type":"string"}}}}},"security":[{"bearerAuth":[]}]}},"/api/public/v1/contacts/{contactId}/soft-unsubscribe":{"post":{"tags":["Contacts"],"summary":"Soft-unsubscribe a contact","description":"Use this to stop messaging one contact without deleting them (soft unsubscribe / opt-out).\n\nRequired: path `contactId`. Optional: none.\n","operationId":"softUnsubscribe","parameters":[{"name":"contactId","in":"path","description":"Contact ID","required":true,"schema":{"type":"integer","format":"int64"}}],"responses":{"200":{"description":"Contact with updated subscription state. No message is sent.","content":{"application/json":{"schema":{"$ref":"#/components/schemas/ContactLightResponse"},"example":{"id":1,"firstName":"Jane","lastName":"Doe","phone":"+15555550100","status":"Unsubscribed","subscriptionState":"SOFT_UNSUBSCRIBED"}}}}},"security":[{"bearerAuth":[]}]}},"/api/public/v1/contacts/{contactId}/re-subscribe":{"post":{"tags":["Contacts"],"summary":"Re-subscribe a contact","description":"Use this after explicit consent to allow messaging a previously unsubscribed contact.\n\nRequired: path `contactId`. Optional: none.\n","operationId":"reSubscribe","parameters":[{"name":"contactId","in":"path","description":"Contact ID","required":true,"schema":{"type":"integer","format":"int64"}}],"responses":{"200":{"description":"Contact with updated subscription state. No message is sent.","content":{"application/json":{"schema":{"$ref":"#/components/schemas/ContactLightResponse"},"example":{"id":1,"firstName":"Jane","lastName":"Doe","phone":"+15555550100","status":"Active","subscriptionState":"RE_SUBSCRIBED"}}}}},"security":[{"bearerAuth":[]}]}},"/api/public/v1/contacts/campaign-preview":{"post":{"tags":["Contacts"],"summary":"Preview contacts for campaign triggers","description":"Use this to see which contacts would match campaign trigger configs before launching a campaign.\n\nRequired: a body of trigger configs. Optional: pagination (`page`, `size`, `sort`).\n","operationId":"getCampaignPreviewContacts","parameters":[{"name":"page","in":"query","description":"Zero-based page index (0..N)","required":false,"schema":{"type":"integer","default":0,"minimum":0}},{"name":"size","in":"query","description":"The size of the page to be returned","required":false,"schema":{"type":"integer","default":20,"minimum":1}},{"name":"sort","in":"query","description":"Sorting criteria in the format: property,(asc|desc). Default sort order is ascending. Multiple sort criteria are supported.","required":false,"schema":{"type":"array","items":{"type":"string"}}}],"requestBody":{"content":{"application/json":{"schema":{"type":"array","items":{"$ref":"#/components/schemas/CampaignTriggerConfig"}}}},"required":true},"responses":{"200":{"description":"Preview page of matching contacts. No contacts are changed and no messages are sent.","content":{"application/json":{"schema":{"$ref":"#/components/schemas/PageResponseContactLightResponse"},"example":{"content":[{"id":1,"firstName":"Jane","lastName":"Doe","phone":"+15555550100","status":"Active","subscriptionState":"SUBSCRIBED"}],"pageNumber":0,"pageSize":20,"total":1}}}}},"security":[{"bearerAuth":[]}]}},"/api/public/v1/contacts/bulk/verify-dnc":{"post":{"tags":["Contacts"],"summary":"Verify DNC contacts","description":"Use this to queue or run Do-Not-Call verification for contacts.\n\nRequired: a body with `ids` or a filter. If `ids` is present, those contacts are marked for verification immediately.                     If `ids` is omitted, a background job runs.\n","operationId":"verifyDncContacts","requestBody":{"content":{"application/json":{"schema":{"$ref":"#/components/schemas/ContactBulkOperationRequest"},"examples":{"byFilter":{"summary":"By grouped filters","description":"Each inner array is an OR group. Do not send `filters` in the same request.","value":{"filter":{"listIds":[1],"sourceFilters":[[{"field":"STATUS","type":"CONTACT","dataType":"SELECT","condition":"IN","value":"Active","orIndex":0,"andIndex":0},{"field":"company_name","type":"CUSTOM","dataType":"TEXT","condition":"CONTAINS_ANY","value":"Acme","orIndex":0,"andIndex":1}]]}}},"byIds":{"summary":"By contact IDs","value":{"ids":[1,2]}}}}},"required":true},"responses":{"200":{"description":"Verification requested or queued, not that DNC lookup has finished for every contact.","content":{"application/json":{"schema":{"type":"string"}}}}},"security":[{"bearerAuth":[]}]}},"/api/public/v1/contacts/bulk/unarchive":{"post":{"tags":["Contacts"],"summary":"Bulk unarchive contacts","description":"Use this to restore archived contacts to Active.\n\nRequired: a body with `ids` or a filter. If `ids` is present, those contacts are unarchived immediately. If `ids` is omitted, a background job runs.\n","operationId":"bulkUnArchive","requestBody":{"content":{"application/json":{"schema":{"$ref":"#/components/schemas/ContactBulkOperationRequest"},"examples":{"byFilter":{"summary":"By grouped filters","description":"Each inner array is an OR group. Do not send `filters` in the same request.","value":{"filter":{"listIds":[1],"sourceFilters":[[{"field":"STATUS","type":"CONTACT","dataType":"SELECT","condition":"IN","value":"Active","orIndex":0,"andIndex":0},{"field":"company_name","type":"CUSTOM","dataType":"TEXT","condition":"CONTAINS_ANY","value":"Acme","orIndex":0,"andIndex":1}]]}}},"byIds":{"summary":"By contact IDs","value":{"ids":[1,2]}}}}},"required":true},"responses":{"200":{"description":"Confirmation string. With `ids`, the change is already applied. Without `ids`, the job is queued, not finished.","content":{"application/json":{"schema":{"type":"string"}}}}},"security":[{"bearerAuth":[]}]}},"/api/public/v1/contacts/bulk/soft-unsubscribe":{"post":{"tags":["Contacts"],"summary":"Bulk soft-unsubscribe contacts","description":"Use this to soft-unsubscribe many contacts at once.\n\nRequired: a body with `ids` or a filter (`searchPattern` / `filter`). If `ids` is present, those contacts are updated immediately. If `ids` is omitted, a background job runs.\n","operationId":"bulkSoftUnsubscribe","requestBody":{"content":{"application/json":{"schema":{"$ref":"#/components/schemas/ContactBulkOperationRequest"},"examples":{"byFilter":{"summary":"By grouped filters","description":"Each inner array is an OR group. Do not send `filters` in the same request.","value":{"filter":{"listIds":[1],"sourceFilters":[[{"field":"STATUS","type":"CONTACT","dataType":"SELECT","condition":"IN","value":"Active","orIndex":0,"andIndex":0},{"field":"company_name","type":"CUSTOM","dataType":"TEXT","condition":"CONTAINS_ANY","value":"Acme","orIndex":0,"andIndex":1}]]}}},"byIds":{"summary":"By contact IDs","value":{"ids":[1,2]}}}}},"required":true},"responses":{"200":{"description":"Confirmation string. With `ids`, the change is already applied. Without `ids`, the job is queued, not finished.","content":{"application/json":{"schema":{"type":"string"}}}}},"security":[{"bearerAuth":[]}]}},"/api/public/v1/contacts/bulk/remove-from-lists":{"post":{"tags":["Contacts"],"summary":"Remove contacts from lists","description":"Use this to detach contacts from one or more lists.\n\nRequired: query `listIds`, plus a body with `ids` or a filter. If `ids` is present, membership is updated immediately. If `ids` is omitted, a background job runs.\n","operationId":"removeFromLists","parameters":[{"name":"listIds","in":"query","description":"List IDs","required":true,"schema":{"type":"array","items":{"type":"integer","format":"int64"}}}],"requestBody":{"content":{"application/json":{"schema":{"$ref":"#/components/schemas/ContactBulkOperationRequest"},"examples":{"byFilter":{"summary":"By grouped filters","description":"Each inner array is an OR group. Do not send `filters` in the same request.","value":{"filter":{"listIds":[1],"sourceFilters":[[{"field":"STATUS","type":"CONTACT","dataType":"SELECT","condition":"IN","value":"Active","orIndex":0,"andIndex":0},{"field":"company_name","type":"CUSTOM","dataType":"TEXT","condition":"CONTAINS_ANY","value":"Acme","orIndex":0,"andIndex":1}]]}}},"byIds":{"summary":"By contact IDs","value":{"ids":[1,2]}}}}},"required":true},"responses":{"200":{"description":"Confirmation string. Immediate removals are finished; a started job is queued, not complete.","content":{"application/json":{"schema":{"type":"string"}}}}},"security":[{"bearerAuth":[]}]}},"/api/public/v1/contacts/bulk/re-subscribe":{"post":{"tags":["Contacts"],"summary":"Bulk re-subscribe contacts","description":"Use this to re-subscribe many contacts at once after consent.\n\nRequired: a body with `ids` or a filter. If `ids` is present, those contacts are updated immediately. If `ids` is omitted, a background job runs.\n","operationId":"bulkReSubscribe","requestBody":{"content":{"application/json":{"schema":{"$ref":"#/components/schemas/ContactBulkOperationRequest"},"examples":{"byFilter":{"summary":"By grouped filters","description":"Each inner array is an OR group. Do not send `filters` in the same request.","value":{"filter":{"listIds":[1],"sourceFilters":[[{"field":"STATUS","type":"CONTACT","dataType":"SELECT","condition":"IN","value":"Active","orIndex":0,"andIndex":0},{"field":"company_name","type":"CUSTOM","dataType":"TEXT","condition":"CONTAINS_ANY","value":"Acme","orIndex":0,"andIndex":1}]]}}},"byIds":{"summary":"By contact IDs","value":{"ids":[1,2]}}}}},"required":true},"responses":{"200":{"description":"Confirmation string. With `ids`, the change is already applied. Without `ids`, the job is queued, not finished.","content":{"application/json":{"schema":{"type":"string"}}}}},"security":[{"bearerAuth":[]}]}},"/api/public/v1/contacts/bulk/delete":{"post":{"tags":["Contacts"],"summary":"Bulk delete contacts","description":"Use this to permanently delete many contacts by IDs or by filter.\n\nRequired: a body with `ids` or a filter. If `ids` is present, those contacts are deleted immediately. If `ids` is omitted, a background job runs.\n","operationId":"bulkDelete","requestBody":{"content":{"application/json":{"schema":{"$ref":"#/components/schemas/ContactBulkOperationRequest"},"examples":{"byFilter":{"summary":"By grouped filters","description":"Each inner array is an OR group. Do not send `filters` in the same request.","value":{"filter":{"listIds":[1],"sourceFilters":[[{"field":"STATUS","type":"CONTACT","dataType":"SELECT","condition":"IN","value":"Active","orIndex":0,"andIndex":0},{"field":"company_name","type":"CUSTOM","dataType":"TEXT","condition":"CONTAINS_ANY","value":"Acme","orIndex":0,"andIndex":1}]]}}},"byIds":{"summary":"By contact IDs","value":{"ids":[1,2]}}}}},"required":true},"responses":{"200":{"description":"Confirmation string (count deleted, or that a bulk job started). Immediate deletes are finished; a started job is queued.","content":{"application/json":{"schema":{"type":"string"}}}}},"security":[{"bearerAuth":[]}]}},"/api/public/v1/contacts/bulk/archive":{"post":{"tags":["Contacts"],"summary":"Bulk archive contacts","description":"Use this to archive many contacts (they leave Active lists but are not deleted).\n\nRequired: a body with `ids` or a filter. If `ids` is present, those contacts are archived immediately. If `ids` is omitted, a background job runs.\n","operationId":"bulkArchive","requestBody":{"content":{"application/json":{"schema":{"$ref":"#/components/schemas/ContactBulkOperationRequest"},"examples":{"byFilter":{"summary":"By grouped filters","description":"Each inner array is an OR group. Do not send `filters` in the same request.","value":{"filter":{"listIds":[1],"sourceFilters":[[{"field":"STATUS","type":"CONTACT","dataType":"SELECT","condition":"IN","value":"Active","orIndex":0,"andIndex":0},{"field":"company_name","type":"CUSTOM","dataType":"TEXT","condition":"CONTAINS_ANY","value":"Acme","orIndex":0,"andIndex":1}]]}}},"byIds":{"summary":"By contact IDs","value":{"ids":[1,2]}}}}},"required":true},"responses":{"200":{"description":"Confirmation string. With `ids`, archive is already applied. Without `ids`, the job is queued, not finished.","content":{"application/json":{"schema":{"type":"string"}}}}},"security":[{"bearerAuth":[]}]}},"/api/public/v1/contacts/bulk/add-to-lists":{"post":{"tags":["Contacts"],"summary":"Add contacts to lists","description":"Use this to attach contacts to one or more lists.\n\nRequired: query `listIds`, plus a body with `ids` or a filter. If `ids` is present, membership is updated immediately. If `ids` is omitted, a background job runs.\n","operationId":"addToLists","parameters":[{"name":"listIds","in":"query","description":"List IDs","required":true,"schema":{"type":"array","items":{"type":"integer","format":"int64"}}}],"requestBody":{"content":{"application/json":{"schema":{"$ref":"#/components/schemas/ContactBulkOperationRequest"},"examples":{"byFilter":{"summary":"By grouped filters","description":"Each inner array is an OR group. Do not send `filters` in the same request.","value":{"filter":{"listIds":[1],"sourceFilters":[[{"field":"STATUS","type":"CONTACT","dataType":"SELECT","condition":"IN","value":"Active","orIndex":0,"andIndex":0},{"field":"company_name","type":"CUSTOM","dataType":"TEXT","condition":"CONTAINS_ANY","value":"Acme","orIndex":0,"andIndex":1}]]}}},"byIds":{"summary":"By contact IDs","value":{"ids":[1,2]}}}}},"required":true},"responses":{"200":{"description":"Confirmation string (count added, or that a bulk job started). Immediate adds are finished; a started job is queued.","content":{"application/json":{"schema":{"type":"string"}}}}},"security":[{"bearerAuth":[]}]}},"/api/public/v1/contacts/active-count":{"post":{"tags":["Contacts"],"summary":"Get active contacts count for a broadcast","description":"Use this to estimate how many Active contacts a broadcast filter would reach.\n\nRequired: `type` (content type) and `filterValue` in the body. Optional: none beyond the filter.\n","operationId":"getActiveContactsCount","requestBody":{"content":{"application/json":{"schema":{"$ref":"#/components/schemas/ActiveContactsCountRequest"},"examples":{"sourceFilters":{"summary":"Grouped source filters","description":"Each inner array is an OR group. Do not send `filters` in the same request.","value":{"type":"SMS","filterValue":{"listIds":[1],"sourceFilters":[[{"field":"STATUS","type":"CONTACT","dataType":"SELECT","condition":"IN","value":"Active","orIndex":0,"andIndex":0},{"field":"company_name","type":"CUSTOM","dataType":"TEXT","condition":"CONTAINS_ANY","value":"Acme","orIndex":0,"andIndex":1}]]}}},"segmentFilters":{"summary":"Segment-style filters","description":"Flat `filters` list. Do not send `sourceFilters` in the same request.","value":{"type":"SMS","filterValue":{"filters":[{"field":"STATUS","type":"CONTACT","dataType":"SELECT","condition":"IN","value":"Active","orIndex":0,"andIndex":0}]}}},"listIds":{"summary":"List IDs only","value":{"type":"SMS","filterValue":{"listIds":[1]}}}}}},"required":true},"responses":{"200":{"description":"Integer count of matching Active contacts. Not a send and does not queue messages.","content":{"application/json":{"schema":{"type":"integer","format":"int32"}}}}},"security":[{"bearerAuth":[]}]}},"/api/public/v1/message/{id}":{"get":{"tags":["Messages"],"summary":"Get a message by numeric id or state UUID","description":"Use this to poll after send until a terminal status. Pass the numeric `id` or `state.uuid` from the send response.\n\nRequired: path `id`. Optional: none. Check `state.status` (full lifecycle), `state.simpleStatus` (collapsed), and `extStatus` (provider-facing). Keep polling while status is `Queued` / `Pending`.\n","operationId":"getById_1","parameters":[{"name":"id","in":"path","description":"Message numeric ID or state UUID","required":true,"schema":{"type":"string"}}],"responses":{"200":{"description":"Current snapshot, not a delivery guarantee. `Queued`/`Pending` means still in flight.","content":{"*/*":{"schema":{"$ref":"#/components/schemas/MessageDataResponse"}}}}},"security":[{"bearerAuth":[]}]}},"/api/public/v1/message/compliance":{"get":{"tags":["Messages"],"summary":"Get a compliance message","description":"Use this to read the default STOP/HELP footer that is appended when `compliance` is true on SMS send.\n\nRequired: a PAT with `message:read`. Optional: none.\n","operationId":"getComplianceMessage","responses":{"200":{"description":"Default compliance text string. Not a sent message and not a delivery result.","content":{"*/*":{"schema":{"type":"string"}}}}},"security":[{"bearerAuth":[]}]}},"/api/public/v1/contacts/statistics":{"get":{"tags":["Contacts"],"summary":"Get organization contacts statistics","description":"Use this for dashboard totals (active, archived, unsubscribed, and similar counts).\n\nRequired: a PAT with `contact:read`. Optional: none.\n","operationId":"getStatistics","responses":{"200":{"description":"Current count snapshot for the organization, not a per-contact payload.","content":{"application/json":{"schema":{"$ref":"#/components/schemas/ContactStatisticsResponse"}}}}},"security":[{"bearerAuth":[]}]}},"/api/public/v1/contacts/search":{"get":{"tags":["Contacts"],"summary":"Search lists and contacts","description":"Use this for a combined typeahead of lists and contacts (for example, picker UIs).\n\nRequired: none. Optional: `searchPattern` and exclusion IDs (`excludedListIds`, `excludedContactIds`, `excludedSegmentIds`).\n","operationId":"searchListsAndContacts","parameters":[{"name":"searchPattern","in":"query","required":false,"schema":{"type":"string"}},{"name":"excludedListIds","in":"query","required":false,"schema":{"type":"array","items":{"type":"integer","format":"int64"}}},{"name":"excludedContactIds","in":"query","required":false,"schema":{"type":"array","items":{"type":"integer","format":"int64"}}},{"name":"excludedSegmentIds","in":"query","required":false,"schema":{"type":"array","items":{"type":"integer","format":"int64"}}}],"responses":{"200":{"description":"Matching lists and contacts. A search snapshot, not a send or delivery result.","content":{"application/json":{"schema":{"$ref":"#/components/schemas/ListContactSearchResult"}}}}},"security":[{"bearerAuth":[]}]}},"/api/public/v1/contacts/by-list/{listId}":{"get":{"tags":["Contacts"],"summary":"Get contacts by list ID","description":"Use this to page contacts that belong to one list.\n\nRequired: path `listId`. Optional: the same `ContactSearch` filters and pagination as list contacts.\n","operationId":"getListContacts","parameters":[{"name":"listId","in":"path","description":"List ID","required":true,"schema":{"type":"integer","format":"int64"}},{"name":"status","in":"query","description":"Contact lifecycle filter (`Active`, `Archived`, …). Not a message delivery status.","required":false,"schema":{"$ref":"#/components/schemas/ContactStatus"}},{"name":"searchPattern","in":"query","required":false,"schema":{"type":"string"}},{"name":"subscriptionStates","in":"query","required":false,"schema":{"type":"array","items":{"type":"string","enum":["SUBSCRIBED","HARD_UNSUBSCRIBED","SOFT_UNSUBSCRIBED","RE_SUBSCRIBED"]}}},{"name":"callSubscriptionStates","in":"query","required":false,"schema":{"type":"array","items":{"type":"string","enum":["SUBSCRIBED","HARD_UNSUBSCRIBED","SOFT_UNSUBSCRIBED","RE_SUBSCRIBED"]}}},{"name":"excludedListIds","in":"query","required":false,"schema":{"type":"array","items":{"type":"integer","format":"int64"}}},{"name":"excludedContactIds","in":"query","required":false,"schema":{"type":"array","items":{"type":"integer","format":"int64"}}},{"name":"page","in":"query","description":"Zero-based page index (0..N)","required":false,"schema":{"type":"integer","default":0,"minimum":0}},{"name":"size","in":"query","description":"The size of the page to be returned","required":false,"schema":{"type":"integer","default":20,"minimum":1}},{"name":"sort","in":"query","description":"Sorting criteria in the format: property,(asc|desc). Default sort order is ascending. Multiple sort criteria are supported.","required":false,"schema":{"type":"array","items":{"type":"string"}}}],"responses":{"200":{"description":"Page of contacts on that list. Membership is current as of the request; not a message status.","content":{"application/json":{"schema":{"$ref":"#/components/schemas/PageResponseContactLightResponse"},"example":{"content":[{"id":1,"firstName":"Jane","lastName":"Doe","phone":"+15555550100","status":"Active","subscriptionState":"SUBSCRIBED"}],"pageNumber":0,"pageSize":20,"total":1}}}}},"security":[{"bearerAuth":[]}]}},"/api/public/v1/contacts/by-ids":{"get":{"tags":["Contacts"],"summary":"Get contacts by IDs","description":"Use this to load a known set of contacts in one call after you already store their IDs.\n\nRequired: query `contactIds`. Optional: none.\n","operationId":"getByIds","parameters":[{"name":"contactIds","in":"query","description":"Contact IDs","required":true,"schema":{"type":"array","items":{"type":"integer","format":"int64"}}}],"responses":{"200":{"description":"Contacts found in your organization. Missing IDs are omitted; not a delivery confirmation.","content":{"application/json":{"schema":{"type":"array","items":{"$ref":"#/components/schemas/ContactLightResponse"}},"example":[{"id":1,"firstName":"Jane","lastName":"Doe","phone":"+15555550100","status":"Active","subscriptionState":"SUBSCRIBED"}]}}}},"security":[{"bearerAuth":[]}]}}},"components":{"schemas":{"ContactApiRequest":{"type":"object","properties":{"firstName":{"type":"string"},"lastName":{"type":"string"},"email":{"type":"string"},"phoneNumber":{"type":"string","description":"Contact phone number. Required on create and full update; optional on patch. Prefer E.164 (`+15551234567`); national US format is accepted."},"timeZoneType":{"type":"string","enum":["EASTERN","EAST_INDIANA","MICHIGAN","CENTRAL","MOUNTAIN","ARIZONA","PACIFIC","ALASKA","HAWAII","ATLANTIC","NEWFOUNDLAND","SASKATCHEWAN","UNDEFINED"]},"customFields":{"type":"array","items":{"$ref":"#/components/schemas/ContactCustomFieldRequest"}},"listIds":{"type":"array","items":{"type":"integer","format":"int64"}}}},"ContactCustomFieldRequest":{"type":"object","properties":{"key":{"type":"string"},"value":{"type":"string"}}},"ContactCustomFieldResponse":{"type":"object","properties":{"text":{"type":"string"},"number":{"type":"integer","format":"int64"},"date":{"type":"string","format":"date-time"},"numeric":{"type":"number"},"currencyType":{"type":"string","enum":["USD"]},"key":{"type":"string"},"name":{"type":"string"},"type":{"type":"string"}}},"ContactLightResponse":{"type":"object","properties":{"id":{"type":"integer","format":"int64"},"name":{"type":"string"},"firstName":{"type":"string"},"lastName":{"type":"string"},"email":{"type":"string"},"phone":{"type":"string"},"internationalPhoneNumber":{"type":"string"},"optIn":{"type":"string","enum":["AddedManually","Imported","Integrated","AddedByKeyword","AddedByWebForm","Migrated"]},"status":{"$ref":"#/components/schemas/ContactStatus","description":"Contact lifecycle (`Active`, `Archived`, `Unsubscribed`, …). Not a message delivery status.","example":"Active"},"carrierType":{"type":"string"},"createdAt":{"type":"string","format":"date-time"},"updatedAt":{"type":"string","format":"date-time"},"deletedAt":{"type":"string","format":"date-time"},"subscriptionState":{"type":"string","enum":["SUBSCRIBED","HARD_UNSUBSCRIBED","SOFT_UNSUBSCRIBED","RE_SUBSCRIBED"]},"callSubscriptionState":{"type":"string","enum":["SUBSCRIBED","HARD_UNSUBSCRIBED","SOFT_UNSUBSCRIBED","RE_SUBSCRIBED"]},"dnc":{"type":"boolean"},"timeZoneType":{"type":"string","enum":["EASTERN","EAST_INDIANA","MICHIGAN","CENTRAL","MOUNTAIN","ARIZONA","PACIFIC","ALASKA","HAWAII","ATLANTIC","NEWFOUNDLAND","SASKATCHEWAN","UNDEFINED"]},"listings":{"type":"array","items":{"$ref":"#/components/schemas/ListLightResponse"}},"contactCustomFields":{"type":"array","items":{"$ref":"#/components/schemas/ContactCustomFieldResponse"}},"activity":{"$ref":"#/components/schemas/ContactSearchActivity"}}},"ContactSearchActivity":{"type":"object","properties":{"lastContacted":{"type":"string","format":"date-time"},"lastSms":{"type":"string","format":"date-time"},"lastVoice":{"type":"string","format":"date-time"},"lastRingless":{"type":"string","format":"date-time"},"lastInbound":{"type":"string","format":"date-time"},"lastMessageStatus":{"type":"string"},"totalSms":{"type":"integer","format":"int64"},"totalVoice":{"type":"integer","format":"int64"},"totalRingless":{"type":"integer","format":"int64"},"lastMessageStatusAt":{"type":"string","format":"date-time"},"lastShortUrlClick":{"type":"string","format":"date-time"},"lastCallback":{"type":"string","format":"date-time"},"lastCallForward":{"type":"string","format":"date-time"},"lastCallTransfer":{"type":"string","format":"date-time"}}},"ContactStatus":{"type":"string","enum":["Active","Inactive","Archived","Invalid","Unsubscribed","Deleted","Dnc"],"example":"Active"},"ListLightResponse":{"type":"object","properties":{"id":{"type":"integer","format":"int64"},"createdAt":{"type":"string","format":"date-time"},"updatedAt":{"type":"string","format":"date-time"},"name":{"type":"string"}}},"ContactOptInStatusData":{"type":"object","description":"Contact opt-in status","properties":{"contactId":{"type":"integer","format":"int64"},"textMessageState":{"type":"string","enum":["SUBSCRIBED","SOFT_UNSUBSCRIBED"],"example":"SUBSCRIBED"},"callAndRinglessState":{"type":"string","enum":["SUBSCRIBED","SOFT_UNSUBSCRIBED"],"example":"SUBSCRIBED"}}},"ApiSendVoiceRequest":{"type":"object","properties":{"fromNumber":{"type":"string","description":"Caller ID / from number on your CallLoop account. Prefer E.164 (`+15551234567`). Invalid format returns HTTP 400 (`From number is invalid`).","minLength":1},"toNumber":{"type":"string","description":"Destination number. Prefer E.164 (`+15551234567`). Matched or created as a contact. Invalid format returns HTTP 400 (`To number is invalid`).","minLength":1},"voiceMessage":{"$ref":"#/components/schemas/VoiceConfigRequest","description":"Voice payload. At least one enabled audio ID is required (`liveAnswer` and/or `voiceMail`)."}},"required":["fromNumber","toNumber","voiceMessage"]},"LiveAnswerConfigRequest":{"type":"object","properties":{"enabled":{"type":"boolean"},"transferCallEnabled":{"type":"boolean"},"transferCallDigit":{"type":"integer","format":"int32"},"transferCallNumber":{"type":"string"},"unsubscribeDigit":{"type":"integer","format":"int32"},"audioId":{"type":"integer","format":"int64","description":"ID of an existing audio item played on live answer. Upload audio in CallLoop first; send does not accept files."}}},"VoiceConfigRequest":{"type":"object","properties":{"liveAnswer":{"$ref":"#/components/schemas/LiveAnswerConfigRequest"},"voiceMail":{"$ref":"#/components/schemas/VoiceMailConfigRequest"},"validOnly":{"type":"boolean","description":"When true, skip sending if the destination contact is on the DNC list (`state.status` = `Skipped`). Default false."},"premiumAmDetectionEnabled":{"type":"boolean","description":"When true, use premium answering-machine detection (more accurate AMD; may incur extra cost). Default false."}}},"VoiceMailConfigRequest":{"type":"object","properties":{"enabled":{"type":"boolean"},"audioId":{"type":"integer","format":"int64","description":"ID of an existing audio item left as voicemail. Upload audio in CallLoop first; send does not accept files."}}},"MessageDataResponse":{"type":"object","properties":{"id":{"type":"integer","format":"int64"},"state":{"$ref":"#/components/schemas/MessageStateResponse"},"type":{"type":"string","enum":["MMS","SMS","INBOUND_SMS","CALL","RINGLESS","LOOKUP","INBOUND_FORWARD_CALL"]},"text":{"type":"string"},"totalShortUrl":{"type":"integer","format":"int64"},"uniqueClicks":{"type":"integer","format":"int64"},"extStatus":{"type":"string","description":"Carrier/provider-facing status, distinct from `state.status` and `state.simpleStatus`. For SMS and ringless this is typically `simpleStatus`. For voice this is the provider call result (for example CallFire)."},"extStatusLabel":{"type":"string","description":"Human-readable label for `extStatus`."},"duration":{"type":"integer","format":"int32"},"penaltyApplied":{"type":"boolean","description":"True when a short-call penalty was billed (voice duration of about 6 seconds or less). False if no penalty applied."},"extStatusDescription":{"type":"string","description":"Human-readable explanation of `extStatus` when the provider supplies one."}}},"MessageStateResponse":{"type":"object","properties":{"id":{"type":"integer","format":"int64"},"uuid":{"type":"string","format":"uuid"},"source":{"type":"string","format":"uuid"},"createdAt":{"type":"string","format":"date-time"},"updatedAt":{"type":"string","format":"date-time"},"timeToSend":{"type":"string","format":"date-time"},"status":{"type":"string","description":"Full internal lifecycle: `Queued` (accepted, not yet sent), `Sent`, `Delivered`, `Failed`, `Skipped`, and similar. A send HTTP 200 with `Queued` means queued, not delivered. Distinct from `simpleStatus` (collapsed bucket) and top-level `extStatus` (provider-facing).","enum":["Deleted","Canceled","Unresolved","Pending","Success","Running","Paused","Draft","Archived","Disabled","Active","Inactive","Unsubscribed","Subscribed","Confirmed","InProgress","Completed","Created","Callback","CallbackAttempt","Answered","Missed","Processing","RetryProcessing","Declined","Invalid","Sending","Queued","Failed","Skipped","Scheduled","SentToCarrier","Sent","Finished","Delivered","SystemDelivered","Received","Undelivered","Trial","Transferred","WithConversions","WithClicks","Dnc"]},"simpleStatus":{"type":"string","description":"Collapsed status for clients: `Pending` (in flight, including `Queued`), `Success` (`Sent`/`Delivered`), `Failed` (`Failed`/`Declined`/`Invalid`/`Undelivered`), plus a few passthrough values (`Skipped`, `Unsubscribed`, …). Distinct from `status` (full lifecycle) and `extStatus` (provider-facing).","enum":["Deleted","Canceled","Unresolved","Pending","Success","Running","Paused","Draft","Archived","Disabled","Active","Inactive","Unsubscribed","Subscribed","Confirmed","InProgress","Completed","Created","Callback","CallbackAttempt","Answered","Missed","Processing","RetryProcessing","Declined","Invalid","Sending","Queued","Failed","Skipped","Scheduled","SentToCarrier","Sent","Finished","Delivered","SystemDelivered","Received","Undelivered","Trial","Transferred","WithConversions","WithClicks","Dnc"]},"reason":{"type":"string"},"failReasonType":{"type":"string","enum":["INSUFFICIENT_FUNDS","RETRY_AMOUNT_EXCEEDED"]},"boundType":{"type":"string","enum":["INBOUND","OUTBOUND"]},"chargeAmount":{"type":"number","format":"float"},"fromNumberId":{"type":"integer","format":"int64"},"fromNumberStr":{"type":"string"},"contactId":{"type":"integer","format":"int64"},"contactName":{"type":"string"},"toNumber":{"type":"string"}}},"ApiSendSmsRequest":{"type":"object","properties":{"fromNumber":{"type":"string","description":"Caller ID / from number on your CallLoop account. Prefer E.164 (`+15551234567`). Invalid format returns HTTP 400 (`From number is invalid`).","minLength":1},"toNumber":{"type":"string","description":"Destination number. Prefer E.164 (`+15551234567`). Matched or created as a contact. Invalid format returns HTTP 400 (`To number is invalid`).","minLength":1},"smsMessage":{"$ref":"#/components/schemas/SmsConfigRequest","description":"SMS payload. `text` is the body; media IDs must already exist in the account."}},"required":["fromNumber","smsMessage","toNumber"]},"SmsConfigRequest":{"type":"object","properties":{"text":{"type":"string","description":"SMS body. Personalization tokens are resolved at send time."},"compliance":{"type":"boolean","description":"When true, append the STOP/HELP compliance footer to the body. Use GET /api/public/v1/message/compliance for the default text."},"validOnly":{"type":"boolean","description":"When true, skip sending to contacts on the DNC list. Default false."},"complianceText":{"type":"string","description":"Extra text appended after the standard `Reply STOP to end.` prefix when `compliance` is true. Ignored when `compliance` is false."},"assetIds":{"type":"array","description":"IDs of existing MMS/assets in the account. Upload assets in CallLoop first; send does not accept files.","items":{"type":"integer","format":"int64"}},"cardIds":{"type":"array","description":"IDs of existing contact cards in the account. Not created on send.","items":{"type":"integer","format":"int64"}},"audioIds":{"type":"array","description":"IDs of existing audio items in the account. Upload audio in CallLoop first; send does not accept files.","items":{"type":"integer","format":"int64"}}}},"ApiSendRinglessRequest":{"type":"object","properties":{"fromNumber":{"type":"string","description":"Caller ID / from number on your CallLoop account. Prefer E.164 (`+15551234567`). Invalid format returns HTTP 400 (`From number is invalid`).","minLength":1},"toNumber":{"type":"string","description":"Destination number. Prefer E.164 (`+15551234567`). Matched or created as a contact. Invalid format returns HTTP 400 (`To number is invalid`).","minLength":1},"ringlessMessage":{"$ref":"#/components/schemas/RinglessConfigRequest","description":"Ringless payload. `audioId` must already exist in the account."}},"required":["fromNumber","ringlessMessage","toNumber"]},"RinglessConfigRequest":{"type":"object","properties":{"audioId":{"type":"integer","format":"int64","description":"ID of an existing audio item in the account. Upload audio in CallLoop first; send does not accept files."},"callbackEnabled":{"type":"boolean"},"validOnly":{"type":"boolean","description":"When true, skip sending if the destination contact is on the DNC list (`state.status` = `Skipped`). Default false."}}},"AutomationTriggerData":{"type":"object","properties":{"triggerType":{"type":"string","enum":["MAILCHIMP_CONTACT_SUBSCRIBED","CLICK_BANK_TRANSACTION","CL_EMAIL_CAPTURED","CL_CONTACT_ADDED_TO_LIST","CL_CONTACT_REMOVED_FROM_LIST","CL_CONTACT_UNSUBSCRIBED","CL_CONTACT_CREATED_OR_UPDATED"]},"data":{"type":"object","additionalProperties":{}}}},"CampaignTriggerAppConfig":{"type":"object","properties":{"accountIntegrationType":{"type":"string","enum":["ActiveCampaign","Drip","Keap","Custom","HubSpot","Zapier","LeadConnector","ClickBank","MailChimp","Shopify"]},"trigger":{"$ref":"#/components/schemas/AutomationTriggerData"}}},"CampaignTriggerConfig":{"type":"object","properties":{"appConfig":{"$ref":"#/components/schemas/CampaignTriggerAppConfig"},"webhookConfig":{"$ref":"#/components/schemas/CampaignTriggerWebhookConfig"}}},"CampaignTriggerWebhookConfig":{"type":"object","properties":{"app":{"type":"string","enum":["ActiveCampaign","Drip","Keap","Custom","HubSpot","Zapier","LeadConnector","ClickBank","MailChimp","Shopify"]},"appPhoneField":{"type":"string"},"link":{"type":"string"}}},"PageResponseContactLightResponse":{"type":"object","properties":{"content":{"type":"array","items":{"$ref":"#/components/schemas/ContactLightResponse"}},"pageNumber":{"type":"integer","format":"int32"},"pageSize":{"type":"integer","format":"int32"},"total":{"type":"integer","format":"int64"}}},"ContactBulkOperationRequest":{"type":"object","properties":{"ids":{"type":"array","items":{"type":"integer","format":"int64"}},"searchPattern":{"type":"string"},"filter":{"$ref":"#/components/schemas/FilterValue"},"listId":{"type":"integer","format":"int64"},"status":{"$ref":"#/components/schemas/ContactStatus","example":"Active"}}},"Filter":{"type":"object","description":"A single contact filter condition.","properties":{"field":{"type":"string","description":"Built-in field key, or custom field key when type is CUSTOM.\n\nBuilt-in fields:\n- `FIRST_NAME` — First name (CONTACT, TEXT)\n- `LAST_NAME` — Last name (CONTACT, TEXT)\n- `EMAIL` — Email (CONTACT, TEXT)\n- `PHONE_NUMBER` — Phone number (CONTACT, PHONE)\n- `PHONE_TYPE` — Phone type (LOOKUP, TEXT)\n- `CARRIER_NAME` — Carrier name (LOOKUP, TEXT)\n- `CREATED_AT` — Created at (CONTACT, DATE)\n- `UPDATED_AT` — Updated at (CONTACT, DATE)\n- `STATUS` — Contact status (CONTACT, SELECT)\n- `OPT_IN` — Opt-in method (CONTACT, SELECT)\n- `DNC` — Do not call (LOOKUP, BOOLEAN)\n- `TIME_ZONE` — Time zone (CONTACT, SELECT)\n- `LAST_SHORT_CLICK` — Last link clicked (ACTIVITY, DATE_TIME)\n- `LAST_CALLBACK` — Last callback (ACTIVITY, DATE_TIME)\n- `LAST_CALL_FORWARD` — Last call forwarded (ACTIVITY, DATE_TIME)\n- `LAST_CALL_TRANSFER` — Last call transferred (ACTIVITY, DATE_TIME)\n- `LAST_CONTACTED` — Last contacted (ACTIVITY, DATE_TIME)\n- `LAST_SMS` — Last SMS (ACTIVITY, DATE_TIME)\n- `LAST_VOICE` — Last Voice (ACTIVITY, DATE_TIME)\n- `LAST_RINGLESS` — Last Ringless (ACTIVITY, DATE_TIME)\n- `LAST_INBOUND` — Last Inbound (ACTIVITY, DATE_TIME)\n- `LAST_MESSAGE_STATUS` — Last message status (ACTIVITY, SELECT)\n- `TOTAL_SMS` — Total SMS (ACTIVITY, NUMBER)\n- `TOTAL_VOICE` — Total Voice (ACTIVITY, NUMBER)\n- `TOTAL_RINGLESS` — Total Ringless (ACTIVITY, NUMBER)\n- `LIST` — List (CONTACT, LIST)\n- `SEGMENT` — Segment (SEGMENTS, SEGMENT)\n\nCustom fields: use the key from your account with `type=CUSTOM`. Example: `company_name` (CUSTOM, TEXT).","example":"FIRST_NAME"},"value":{"type":"string","description":"Primary comparison value. Multi-value conditions (IN, CONTAINS_ANY, …) are comma-separated.","example":"Jane"},"valueExt":{"type":"string","description":"Second bound for BETWEEN / NOT_BETWEEN date filters. Omit otherwise."},"dataType":{"type":"string","description":"Must match the field data type. For custom fields use the custom field type.","enum":["LIST","SEGMENT","PHONE","TEXT","URL","BOOLEAN","NUMBER","CURRENCY","SELECT","DATE_TIME","DATE"]},"type":{"type":"string","description":"CONTACT, ACTIVITY, LOOKUP, or SEGMENTS for built-in fields. CUSTOM for a custom field key.","enum":["CONTACT","CUSTOM","ACTIVITY","LOOKUP","SEGMENTS"]},"condition":{"type":"string","description":"Operator for this field. Allowed operators depend on dataType.","enum":["EXISTS","NOT_EXISTS","IN","NOT_IN","CONTAINS_ANY","CONTAINS_NONE","STARTS_WITH_ANY","ENDS_WITH_ANY","BEFORE","ON","AFTER","BETWEEN","NOT_BETWEEN","EQ","NOT_EQ","LESS","LESS_EQ","GREATER","GREATER_EQ"]},"orIndex":{"type":"integer","format":"int32","description":"OR group. Same orIndex values are ANDed; different orIndex values are ORed.","example":0},"andIndex":{"type":"integer","format":"int32","description":"Position inside the OR group. Must be unique together with orIndex.","example":0}}},"FilterValue":{"type":"object","description":"Audience selector. Combine list/contact/segment IDs with either filters or sourceFilters, not both.","example":{"listIds":[1],"sourceFilters":[[{"field":"STATUS","type":"CONTACT","dataType":"SELECT","condition":"IN","value":"Active","orIndex":0,"andIndex":0},{"field":"company_name","type":"CUSTOM","dataType":"TEXT","condition":"CONTAINS_ANY","value":"Acme","orIndex":0,"andIndex":1}]]},"properties":{"sourceFilters":{"type":"array","description":"Grouped filters. Each inner list is a group; groups are ORed. Inside a group, same orIndex values are ANDed. Do not send together with filters.","items":{"type":"array","items":{"$ref":"#/components/schemas/Filter"}}},"filters":{"type":"array","description":"Flat filter list. AND/OR is expressed with orIndex/andIndex. Do not send together with sourceFilters.","items":{"$ref":"#/components/schemas/Filter"}},"contactIds":{"type":"array","description":"Explicit contact IDs to include.","items":{"type":"integer","format":"int64"},"uniqueItems":true},"listIds":{"type":"array","description":"Contact list IDs to include.","items":{"type":"integer","format":"int64"},"uniqueItems":true},"segmentIds":{"type":"array","description":"Segment IDs to include.","items":{"type":"integer","format":"int64"},"uniqueItems":true}}},"ActiveContactsCountRequest":{"type":"object","properties":{"filterValue":{"$ref":"#/components/schemas/FilterValue"},"type":{"type":"string","enum":["SMS","Voice","Ringless","None"]}}},"ContactStatisticsResponse":{"type":"object","properties":{"totalCount":{"type":"integer","format":"int64"},"activeCount":{"type":"integer","format":"int64"},"inactiveCount":{"type":"integer","format":"int64"},"invalidCount":{"type":"integer","format":"int64"},"archivedCount":{"type":"integer","format":"int64"},"unsubscribedCount":{"type":"integer","format":"int64"},"dncCount":{"type":"integer","format":"int64"}}},"KeywordForm":{"type":"object","properties":{"id":{"type":"integer","format":"int64"},"name":{"type":"string"}}},"ListContactSearchResult":{"type":"object","properties":{"listings":{"type":"array","items":{"$ref":"#/components/schemas/ListResponse"}},"totalListings":{"type":"integer","format":"int32"},"contacts":{"type":"array","items":{"$ref":"#/components/schemas/ContactLightResponse"}},"totalContacts":{"type":"integer","format":"int32"},"segments":{"type":"array","items":{"$ref":"#/components/schemas/SegmentResponse"}},"totalSegments":{"type":"integer","format":"int32"}}},"ListResponse":{"type":"object","properties":{"id":{"type":"integer","format":"int64"},"name":{"type":"string"},"createdAt":{"type":"string","format":"date-time"},"webForms":{"type":"array","items":{"$ref":"#/components/schemas/WebFormForm"}},"keywords":{"type":"array","items":{"$ref":"#/components/schemas/KeywordForm"}},"totalContactsCount":{"type":"integer","format":"int64"},"activeContactsCount":{"type":"integer","format":"int64"},"invalidContactsCount":{"type":"integer","format":"int64"},"unsubscribedContactsCount":{"type":"integer","format":"int64"}}},"SegmentResponse":{"type":"object","properties":{"id":{"type":"integer","format":"int64"},"createdAt":{"type":"string","format":"date-time"},"updatedAt":{"type":"string","format":"date-time"},"name":{"type":"string"},"filterValue":{"$ref":"#/components/schemas/FilterValue"}}},"WebFormForm":{"type":"object","properties":{"id":{"type":"integer","format":"int64"},"name":{"type":"string"}}}},"securitySchemes":{"bearerAuth":{"type":"http","description":"Personal Access Token. Send as: Authorization: Bearer pat_v1.<token>","scheme":"bearer","bearerFormat":"PAT"}}}}