{"openapi":"3.1.0","info":{"title":"BlueMarlin API","version":"1.0.0","description":"Public REST API for BlueMarlin. Authentication uses Bearer tokens (API keys with prefix `bm_live_`). Errors follow the Stripe-style shape { error: { type, code?, message, param? } }."},"servers":[{"url":"https://api.bluemarlinchat.com","description":"Production"}],"components":{"securitySchemes":{"ApiKeyAuth":{"type":"http","scheme":"bearer","bearerFormat":"bm_live_*","description":"BlueMarlin API key. Use `Authorization: Bearer bm_live_...`."}},"schemas":{"Error":{"type":"object","properties":{"error":{"type":"object","properties":{"type":{"type":"string","enum":["invalid_request_error","authentication_error","permission_error","not_found","conflict","rate_limit_error","api_error"]},"code":{"type":"string"},"message":{"type":"string"},"param":{"type":"string"}},"required":["type","message"]}},"required":["error"]},"DeletedResponse":{"type":"object","properties":{"object":{"type":"string","enum":["deleted"]},"id":{"type":"string"}},"required":["object","id"]},"Message":{"type":"object","properties":{"object":{"type":"string","enum":["message"]},"id":{"type":"string","format":"uuid","example":"019d1b6e-af9e-715b-80e9-6fc1240a585a"},"organizationId":{"type":"string","format":"uuid","example":"019d1b6e-af9e-715b-80e9-6fc1240a585a"},"accountPhoneNumber":{"type":"string"},"contactPhoneNumber":{"type":"string"},"waMessageId":{"type":["string","null"]},"direction":{"type":"string","enum":["incoming","outgoing"]},"type":{"type":"string","enum":["text","image","video","audio","document","sticker","location","contacts","template","interactive","reaction","system","unsupported"]},"status":{"type":"string","enum":["pending","sent","delivered","read","failed"]},"content":{"type":"object","additionalProperties":{}},"statusHistory":{"type":["object","null"],"additionalProperties":{"type":"number"}},"senderUserId":{"type":["string","null"],"format":"uuid","example":"019d1b6e-af9e-715b-80e9-6fc1240a585a"},"origin":{"type":["string","null"]},"campaignId":{"type":["string","null"],"format":"uuid","example":"019d1b6e-af9e-715b-80e9-6fc1240a585a"},"replyToWaMessageId":{"type":["string","null"]},"errorCode":{"type":["string","null"]},"errorMessage":{"type":["string","null"]},"mediaUrl":{"type":["string","null"]},"timestamp":{"type":"string","format":"date-time"},"createdAt":{"type":"string","format":"date-time"}},"required":["object","id","organizationId","accountPhoneNumber","contactPhoneNumber","waMessageId","direction","type","status","content","statusHistory","senderUserId","origin","campaignId","replyToWaMessageId","errorCode","errorMessage","timestamp","createdAt"]},"MessageList":{"type":"object","properties":{"object":{"type":"string","enum":["list"]},"data":{"type":"array","items":{"$ref":"#/components/schemas/Message"}},"has_more":{"type":"boolean"},"total":{"type":"integer","minimum":0},"next_cursor":{"type":["string","null"]}},"required":["object","data","has_more"]},"SendMessageBody":{"anyOf":[{"type":"object","properties":{"text":{"type":"string","minLength":1},"replyToWaMessageId":{"type":"string"}},"required":["text"]},{"type":"object","properties":{"mediaType":{"type":"string","enum":["image","video","audio","document"]},"mediaStorageKey":{"type":"string","minLength":1},"mimeType":{"type":"string","minLength":1},"filename":{"type":"string","minLength":1},"mediaSize":{"type":"integer","exclusiveMinimum":0},"caption":{"type":"string"},"replyToWaMessageId":{"type":"string"}},"required":["mediaType","mediaStorageKey","mimeType","filename","mediaSize"]},{"type":"object","properties":{"type":{"type":"string","enum":["template"]},"template":{"type":"object","properties":{"templateName":{"type":"string","minLength":1},"languageCode":{"type":"string","minLength":1},"components":{"type":"array","items":{"type":"object","additionalProperties":{}}}},"required":["templateName","languageCode"]}},"required":["type","template"]},{"type":"object","properties":{"reaction":{"type":"object","properties":{"targetWaMessageId":{"type":"string","minLength":1},"emoji":{"type":"string"}},"required":["targetWaMessageId","emoji"]}},"required":["reaction"]}]},"Conversation":{"type":"object","properties":{"object":{"type":"string","enum":["conversation"]},"id":{"type":"string","format":"uuid","example":"019d1b6e-af9e-715b-80e9-6fc1240a585a"},"organizationId":{"type":"string","format":"uuid","example":"019d1b6e-af9e-715b-80e9-6fc1240a585a"},"whatsappAccountId":{"type":"string","format":"uuid","example":"019d1b6e-af9e-715b-80e9-6fc1240a585a"},"contactPhoneNumber":{"type":"string"},"contactName":{"type":["string","null"]},"contactProfilePicUrl":{"type":["string","null"]},"status":{"type":"string","enum":["active","archived","spam","snoozed"]},"handler":{"type":"string","enum":["human","ai"]},"unreadCount":{"type":"integer","minimum":0},"isStarred":{"type":"boolean"},"mutedUntil":{"type":["string","null"],"format":"date-time"},"windowExpiresAt":{"type":["string","null"],"format":"date-time"},"assignedUserId":{"type":["string","null"],"format":"uuid","example":"019d1b6e-af9e-715b-80e9-6fc1240a585a"},"lastMessageContent":{"type":["string","null"]},"lastMessageAt":{"type":["string","null"],"format":"date-time"},"snoozedUntil":{"type":["string","null"],"format":"date-time"},"closedAt":{"type":["string","null"],"format":"date-time"},"attentionReason":{"type":["string","null"],"enum":["ai_handoff","ai_blocked","ai_error","mention","unanswered",null]},"attentionSince":{"type":["string","null"],"format":"date-time"},"lastHumanReplyAt":{"type":["string","null"],"format":"date-time"},"contactLanguage":{"type":["string","null"]},"createdAt":{"type":"string","format":"date-time"},"updatedAt":{"type":"string","format":"date-time"}},"required":["object","id","organizationId","whatsappAccountId","contactPhoneNumber","contactName","contactProfilePicUrl","status","unreadCount","isStarred","mutedUntil","windowExpiresAt","assignedUserId","lastMessageContent","lastMessageAt","createdAt","updatedAt"]},"ConversationList":{"type":"object","properties":{"object":{"type":"string","enum":["list"]},"data":{"type":"array","items":{"$ref":"#/components/schemas/Conversation"}},"has_more":{"type":"boolean"},"total":{"type":"integer","minimum":0},"next_cursor":{"type":["string","null"]}},"required":["object","data","has_more"]},"AssignConversationBody":{"type":"object","properties":{"userId":{"type":["string","null"],"format":"uuid","example":"019d1b6e-af9e-715b-80e9-6fc1240a585a"}},"required":["userId"]},"SetHandlerBody":{"type":"object","properties":{"handler":{"type":"string","enum":["human","ai"]},"handoffReturn":{"type":"string","enum":["archive","never"]}},"required":["handler"]},"Tag":{"type":"object","properties":{"object":{"type":"string","enum":["tag"]},"id":{"type":"string","format":"uuid","example":"019d1b6e-af9e-715b-80e9-6fc1240a585a"},"organizationId":{"type":"string","format":"uuid","example":"019d1b6e-af9e-715b-80e9-6fc1240a585a"},"name":{"type":"string"},"color":{"type":"string","minLength":1},"createdAt":{"type":"string","format":"date-time"},"updatedAt":{"type":"string","format":"date-time"}},"required":["object","id","organizationId","name","color","createdAt","updatedAt"]},"TagList":{"type":"object","properties":{"object":{"type":"string","enum":["list"]},"data":{"type":"array","items":{"$ref":"#/components/schemas/Tag"}},"has_more":{"type":"boolean"},"total":{"type":"integer","minimum":0},"next_cursor":{"type":["string","null"]}},"required":["object","data","has_more"]},"CreateTagBody":{"type":"object","properties":{"name":{"type":"string","minLength":1},"color":{"type":"string","minLength":1,"default":"blue"}},"required":["name"]},"UpdateTagBody":{"type":"object","properties":{"name":{"type":"string","minLength":1},"color":{"type":"string","minLength":1}}},"SetRecordTagsBody":{"type":"object","properties":{"tagIds":{"type":"array","items":{"type":"string","format":"uuid","example":"019d1b6e-af9e-715b-80e9-6fc1240a585a"}}},"required":["tagIds"]},"AddRecordTagBody":{"type":"object","properties":{"tagId":{"type":"string","format":"uuid","example":"019d1b6e-af9e-715b-80e9-6fc1240a585a"}},"required":["tagId"]},"TagsByPhonesBody":{"type":"object","properties":{"phones":{"type":"array","items":{"type":"string","minLength":1,"maxLength":40},"minItems":1,"maxItems":10000}},"required":["phones"]},"TagsByPhonesResponse":{"type":"object","properties":{"object":{"type":"string","enum":["tags_by_phones"]},"data":{"type":"object","additionalProperties":{"type":"array","items":{"$ref":"#/components/schemas/Tag"}}}},"required":["object","data"]},"UsageTotals":{"type":"object","properties":{"inputTokens":{"type":"integer","minimum":0},"outputTokens":{"type":"integer","minimum":0},"embeddingTokens":{"type":"integer","minimum":0},"estimatedCost":{"type":"number"},"requestCount":{"type":"integer","minimum":0}},"required":["inputTokens","outputTokens","embeddingTokens","estimatedCost","requestCount"]},"UsageResponse":{"type":"object","properties":{"object":{"type":"string","enum":["usage"]},"periodStart":{"type":"string","format":"date-time"},"periodEnd":{"type":"string","format":"date-time"},"user":{"allOf":[{"$ref":"#/components/schemas/UsageTotals"},{"type":["object","null"]}]},"org":{"$ref":"#/components/schemas/UsageTotals"},"quota":{"type":"object","properties":{"usedEur":{"type":"number"},"limitEur":{"type":"number"},"percent":{"type":"number"},"state":{"type":"string","enum":["ok","warning","blocked"]}},"required":["usedEur","limitEur","percent","state"]}},"required":["object","periodStart","periodEnd","user","org","quota"]},"Record":{"type":"object","properties":{"object":{"type":"string","enum":["record"]},"id":{"type":"string","format":"uuid","example":"019d1b6e-af9e-715b-80e9-6fc1240a585a"},"organizationId":{"type":"string","format":"uuid","example":"019d1b6e-af9e-715b-80e9-6fc1240a585a"},"collectionId":{"type":"string","format":"uuid","example":"019d1b6e-af9e-715b-80e9-6fc1240a585a"},"phoneNumber":{"type":["string","null"]},"phoneCountry":{"type":["string","null"],"minLength":2,"maxLength":2},"properties":{"type":"object","additionalProperties":{}},"lastContactedAt":{"type":["string","null"],"format":"date-time"},"lastRepliedAt":{"type":["string","null"],"format":"date-time"},"createdAt":{"type":"string","format":"date-time"},"updatedAt":{"type":"string","format":"date-time"}},"required":["object","id","organizationId","collectionId","phoneNumber","phoneCountry","properties","lastContactedAt","lastRepliedAt","createdAt","updatedAt"]},"RecordList":{"type":"object","properties":{"object":{"type":"string","enum":["list"]},"data":{"type":"array","items":{"$ref":"#/components/schemas/Record"}},"has_more":{"type":"boolean"},"total":{"type":"integer","minimum":0},"next_cursor":{"type":["string","null"]}},"required":["object","data","has_more"]},"RecordCount":{"type":"object","properties":{"object":{"type":"string","enum":["count"]},"count":{"type":"integer","minimum":0}},"required":["object","count"]},"CreateRecordBody":{"type":"object","properties":{"phoneNumber":{"type":["string","null"],"minLength":1},"phoneCountry":{"type":["string","null"],"minLength":2,"maxLength":2},"properties":{"type":"object","additionalProperties":{},"default":{},"description":"Field values keyed by field id. Relation/people fields: one id as a string when the field is single, an array of ids when it allows multiple (`relation_cardinality` when several ids are sent to a single link)."},"autoCreateOptions":{"type":"boolean","default":false}}},"UpdateRecordBody":{"type":"object","properties":{"phoneNumber":{"type":["string","null"],"minLength":1},"phoneCountry":{"type":["string","null"],"minLength":2,"maxLength":2},"properties":{"type":"object","additionalProperties":{},"description":"Field values keyed by field id. Relation/people fields: one id as a string when the field is single, an array of ids when it allows multiple (`relation_cardinality` when several ids are sent to a single link). Shallow-merged; omitted keys are preserved."},"autoCreateOptions":{"type":"boolean","default":false}}},"CollectionSchemaJson":{"type":"object","properties":{"sections":{"type":"array","items":{"type":"object","additionalProperties":{}}},"primaryFieldId":{"type":"string"}},"required":["sections"],"additionalProperties":{}},"CollectionRoles":{"type":"object","properties":{"status":{"type":"string"},"doneValues":{"type":"array","items":{"type":"string"}},"assignee":{"type":"string"},"due":{"type":"string"},"related":{"type":"string"},"order":{"type":"string"},"body":{"type":"string"}},"additionalProperties":false,"description":"Field ids the system reads a meaning from (a task's status, assignee…)."},"Collection":{"type":"object","properties":{"object":{"type":"string","enum":["collection"]},"id":{"type":"string","format":"uuid","example":"019d1b6e-af9e-715b-80e9-6fc1240a585a"},"name":{"type":"string"},"icon":{"type":"string"},"isContact":{"type":"boolean"},"isDefault":{"type":"boolean"},"schema":{"$ref":"#/components/schemas/CollectionSchemaJson"},"position":{"type":"integer"},"folderId":{"type":["string","null"],"format":"uuid","example":"019d1b6e-af9e-715b-80e9-6fc1240a585a"},"visibility":{"type":"string","enum":["shared","private","members"],"description":"`shared`: the whole team. `private`: only the owner. `members`: the owner plus the people in `memberUserIds`."},"ownerUserId":{"type":["string","null"],"format":"uuid","example":"019d1b6e-af9e-715b-80e9-6fc1240a585a"},"preset":{"type":"string","enum":["table","tasks","calendar","board","docs"]},"roles":{"allOf":[{"$ref":"#/components/schemas/CollectionRoles"},{"type":["object","null"]}]},"memberUserIds":{"type":"array","items":{"type":"string","format":"uuid","example":"019d1b6e-af9e-715b-80e9-6fc1240a585a"}},"createdAt":{"type":"string","format":"date-time"},"updatedAt":{"type":"string","format":"date-time"}},"required":["object","id","name","icon","isContact","isDefault","schema","position","folderId","visibility","ownerUserId","preset","roles","createdAt","updatedAt"]},"CollectionList":{"type":"object","properties":{"object":{"type":"string","enum":["list"]},"data":{"type":"array","items":{"$ref":"#/components/schemas/Collection"}},"has_more":{"type":"boolean"},"total":{"type":"integer","minimum":0},"next_cursor":{"type":["string","null"]}},"required":["object","data","has_more"]},"File":{"type":"object","properties":{"object":{"type":"string","enum":["file"]},"id":{"type":"string","format":"uuid","example":"019d1b6e-af9e-715b-80e9-6fc1240a585a"},"organizationId":{"type":"string","format":"uuid","example":"019d1b6e-af9e-715b-80e9-6fc1240a585a"},"recordId":{"type":["string","null"],"format":"uuid","example":"019d1b6e-af9e-715b-80e9-6fc1240a585a"},"collectionId":{"type":["string","null"],"format":"uuid","example":"019d1b6e-af9e-715b-80e9-6fc1240a585a"},"fieldId":{"type":["string","null"]},"name":{"type":"string"},"originalName":{"type":"string"},"mimeType":{"type":"string"},"size":{"type":["integer","null"]},"kind":{"type":"string","enum":["image","video","audio","pdf","document"]},"url":{"type":"string"},"isExternal":{"type":"boolean"},"position":{"type":"integer"},"createdAt":{"type":"string","format":"date-time"}},"required":["object","id","organizationId","recordId","collectionId","fieldId","name","originalName","mimeType","size","kind","url","isExternal","position","createdAt"]},"BulkExternalAttachBody":{"type":"object","properties":{"mode":{"type":"string","enum":["append","replace_field"],"default":"append"},"attachments":{"type":"array","items":{"type":"object","properties":{"recordId":{"type":"string","format":"uuid","example":"019d1b6e-af9e-715b-80e9-6fc1240a585a"},"fieldId":{"type":"string","minLength":1,"maxLength":64,"example":"f_photos"},"url":{"type":"string","maxLength":2048,"format":"uri"},"mimeType":{"type":"string","minLength":1,"maxLength":255},"name":{"type":"string","minLength":1,"maxLength":255}},"required":["recordId","fieldId","url"]},"minItems":1,"maxItems":5000}},"required":["attachments"]},"BulkExternalAttachResponse":{"type":"object","properties":{"object":{"type":"string","enum":["bulk_attach"]},"data":{"type":"array","items":{"$ref":"#/components/schemas/File"}},"deleted":{"type":"integer","minimum":0}},"required":["object","data","deleted"]},"Me":{"type":"object","properties":{"object":{"type":"string","enum":["auth_context"]},"organizationId":{"type":"string","format":"uuid"},"organizationName":{"type":"string"},"organizationSlug":{"type":"string"},"method":{"type":"string","enum":["cookie","api_key"]},"apiKeyId":{"type":["string","null"],"format":"uuid"},"userId":{"type":["string","null"]},"scopes":{"type":"array","items":{"type":"string"}}},"required":["object","organizationId","organizationName","organizationSlug","method","apiKeyId","userId","scopes"]},"WhatsAppAccount":{"type":"object","properties":{"object":{"type":"string","enum":["whatsapp_account"]},"id":{"type":"string","format":"uuid","example":"019d1b6e-af9e-715b-80e9-6fc1240a585a"},"organizationId":{"type":"string","format":"uuid","example":"019d1b6e-af9e-715b-80e9-6fc1240a585a"},"phoneNumberId":{"type":"string"},"wabaId":{"type":"string"},"phoneNumber":{"type":"string"},"displayName":{"type":"string"},"status":{"type":"string","enum":["active","disconnected","pending"]},"qualityRating":{"type":["string","null"]},"throughputMps":{"type":["integer","null"]},"nameStatus":{"type":["string","null"]},"platformType":{"type":["string","null"]},"connectionType":{"type":"string","enum":["dedicated","coexistence","sandbox"]},"webhookUrl":{"type":["string","null"]},"createdAt":{"type":"string","format":"date-time"},"updatedAt":{"type":"string","format":"date-time"}},"required":["object","id","organizationId","phoneNumberId","wabaId","phoneNumber","displayName","status","qualityRating","throughputMps","nameStatus","platformType","connectionType","webhookUrl","createdAt","updatedAt"]},"WhatsAppAccountListResponse":{"type":"object","properties":{"object":{"type":"string","enum":["list"]},"data":{"type":"array","items":{"$ref":"#/components/schemas/WhatsAppAccount"}},"has_more":{"type":"boolean"},"total":{"type":"integer","minimum":0}},"required":["object","data","has_more"]},"WebhookConfig":{"type":"object","properties":{"object":{"type":"string","enum":["webhook_config"]},"url":{"type":["string","null"]},"secret":{"type":"string"},"events":{"type":"array","items":{"type":"string","enum":["whatsapp.message","whatsapp.status","whatsapp.template","message.sent","contact.unsubscribed","contact.resubscribed"]}},"enabled":{"type":"boolean"}},"required":["object","url","secret","events","enabled"]},"UpdateWebhookBody":{"type":"object","properties":{"url":{"type":["string","null"]},"events":{"type":"array","items":{"type":"string","enum":["whatsapp.message","whatsapp.status","whatsapp.template","message.sent","contact.unsubscribed","contact.resubscribed"]}},"enabled":{"type":"boolean"}},"additionalProperties":false},"TestWebhookBody":{"type":"object","properties":{"eventType":{"type":"string","enum":["whatsapp.message","whatsapp.status","whatsapp.template","message.sent","contact.unsubscribed","contact.resubscribed"]}},"required":["eventType"]},"TestWebhookResponse":{"type":"object","properties":{"object":{"type":"string","enum":["webhook_test"]},"success":{"type":"boolean"},"statusCode":{"type":"integer"},"error":{"type":"string"}},"required":["object","success"]},"SearchResult":{"type":"object","properties":{"object":{"type":"string","enum":["search_result"]},"type":{"type":"string","enum":["record"]},"id":{"type":"string","format":"uuid","example":"019d1b6e-af9e-715b-80e9-6fc1240a585a"},"displayName":{"type":"string"},"phoneNumber":{"type":["string","null"]},"updatedAt":{"type":"string","format":"date-time"},"summary":{"type":"array","items":{"type":"object","properties":{"label":{"type":"string"},"value":{"type":"string"}},"required":["label","value"]},"description":"The collection's card lines under the title (up to three, empty ones skipped), as display text, to tell similar records apart."},"collection":{"type":"object","properties":{"id":{"type":"string","format":"uuid","example":"019d1b6e-af9e-715b-80e9-6fc1240a585a"},"name":{"type":"string"},"icon":{"type":"string"},"isContact":{"type":"boolean"}},"required":["id","name","icon","isContact"]}},"required":["object","type","id","displayName","phoneNumber","updatedAt","summary","collection"]},"SearchList":{"type":"object","properties":{"object":{"type":"string","enum":["list"]},"data":{"type":"array","items":{"$ref":"#/components/schemas/SearchResult"}},"has_more":{"type":"boolean"},"total":{"type":"integer","minimum":0},"next_cursor":{"type":["string","null"]}},"required":["object","data","has_more"]},"WebhookEventEnvelope":{"type":"object","properties":{"id":{"type":"string","format":"uuid","description":"Unique event id (UUID v7, so lexically sortable by time). Stable across redeliveries — use it as the idempotency key.","example":"01a09269-238c-72ac-9c94-2519b44838e3"},"object":{"type":"string","enum":["event"],"description":"Always `event` for BlueMarlin platform events. Meta relays carry `whatsapp_business_account` instead, which is how you tell the two families apart without reading the header."},"type":{"type":"string","enum":["message.sent","contact.unsubscribed","contact.resubscribed"],"example":"message.sent"},"created":{"type":"string","format":"date-time","description":"When the event happened (ISO 8601)."},"organizationId":{"type":"string","format":"uuid","description":"The workspace this event belongs to. Same id `GET /v1/me` returns."},"source":{"type":"string","enum":["panel","api","agent","automation","device","contact"],"description":"What caused it: `panel` (a member in the web app), `api` (an API-key call — very likely your own), `agent` (the AI answering), `automation` (campaign reply action, reminder, scheduled job), `device` (sent from the WhatsApp Business phone app), `contact` (the contact themselves, e.g. replying STOP). Ignore `api` to skip the echoes of your own writes.","example":"panel"},"data":{"type":"object","additionalProperties":{},"description":"Event-specific body; see each event below."}},"required":["id","object","type","created","organizationId","source","data"]},"MessageSentEventData":{"type":"object","properties":{"conversationId":{"type":"string","format":"uuid"},"whatsappAccountId":{"type":"string","format":"uuid"},"message":{"allOf":[{"$ref":"#/components/schemas/Message"},{"description":"The very same Message resource `GET /v1/messages` returns — same serializer, same fields. If you already read messages from this API you have nothing new to parse."}]}},"required":["conversationId","whatsappAccountId","message"]},"ContactRestrictionEventData":{"type":"object","properties":{"contactPhoneNumber":{"type":"string","example":"+34652225000"},"scope":{"type":"string","enum":["all","marketing"],"description":"`all` = do not contact at all. `marketing` = no marketing-category templates; service replies still allowed."},"previousScope":{"type":["string","null"],"enum":["all","marketing",null]},"reason":{"type":["string","null"]},"campaignId":{"type":["string","null"],"format":"uuid"}},"required":["contactPhoneNumber","scope"]},"MediaUrlResponse":{"type":"object","properties":{"object":{"type":"string","enum":["media_url"]},"url":{"type":"string","format":"uri"},"storageKey":{"type":"string"},"mediaSize":{"type":"integer"},"contentType":{"type":"string"},"alreadyDownloaded":{"type":"boolean"}},"required":["object","url"]},"ConversationActionResponse":{"type":"object","properties":{"object":{"type":"string","enum":["conversation_action"]},"success":{"type":"boolean"}},"required":["object","success"]},"MessageListAcrossConversations":{"type":"object","properties":{"object":{"type":"string","enum":["list"]},"data":{"type":"array","items":{"type":"object","additionalProperties":{}}},"has_more":{"type":"boolean"},"next_cursor":{"type":["string","null"]}},"required":["object","data","has_more","next_cursor"]},"SendMessageTopLevelBody":{"allOf":[{"anyOf":[{"type":"object","properties":{"conversationId":{"type":"string","format":"uuid","example":"019d1b6e-af9e-715b-80e9-6fc1240a585a"}},"required":["conversationId"]},{"type":"object","properties":{"accountId":{"type":"string","format":"uuid","example":"019d1b6e-af9e-715b-80e9-6fc1240a585a"},"contactPhoneNumber":{"type":"string","minLength":1}},"required":["accountId","contactPhoneNumber"]}]},{"$ref":"#/components/schemas/SendMessageBody"}]},"SentMessageResponse":{"type":"object","properties":{"object":{"type":"string","enum":["sent_message"]},"message":{"$ref":"#/components/schemas/Message"},"conversationId":{"type":"string","format":"uuid","example":"019d1b6e-af9e-715b-80e9-6fc1240a585a"}},"required":["object","message","conversationId"]},"TemplateList":{"type":"object","properties":{"object":{"type":"string","enum":["list"]},"data":{"type":"array","items":{"type":"object","additionalProperties":{}}},"has_more":{"type":"boolean"},"total":{"type":"integer","minimum":0}},"required":["object","data","has_more"]},"CreateWhatsAppTemplateBody":{"type":"object","properties":{"name":{"type":"string","minLength":1,"maxLength":512},"language":{"type":"string","minLength":2,"maxLength":10},"category":{"type":"string","enum":["marketing","utility","authentication"]},"components":{"type":"array","items":{"type":"object","additionalProperties":{}}},"allow_category_change":{"type":"boolean"},"parameter_format":{"type":"string","enum":["positional"]},"message_send_ttl_seconds":{"type":"integer"}},"required":["name","language","category","components"]},"UpdateWhatsAppTemplateBody":{"type":"object","properties":{"components":{"type":"array","items":{"type":"object","additionalProperties":{}},"minItems":1},"category":{"type":"string","enum":["marketing","utility","authentication"]},"message_send_ttl_seconds":{"type":"integer"}},"required":["components"]},"WhatsAppTemplateSyncResponse":{"type":"object","properties":{"object":{"type":"string","enum":["template_sync"]},"synced":{"type":"integer","minimum":0},"removed":{"type":"integer","minimum":0}},"required":["object","synced"]},"ContactRestrictionList":{"type":"object","properties":{"object":{"type":"string","enum":["list"]},"data":{"type":"array","items":{"type":"object","additionalProperties":{}}},"has_more":{"type":"boolean"},"next_cursor":{"type":["string","null"]}},"required":["object","data","has_more","next_cursor"]},"Member":{"type":"object","properties":{"object":{"type":"string","enum":["member"]},"id":{"type":"string"},"name":{"type":"string"},"email":{"type":"string"},"image":{"type":["string","null"]},"role":{"type":"string"}},"required":["object","id","name","email","image","role"]},"MemberList":{"type":"object","properties":{"object":{"type":"string","enum":["list"]},"data":{"type":"array","items":{"$ref":"#/components/schemas/Member"}},"has_more":{"type":"boolean"},"total":{"type":"integer","minimum":0}},"required":["object","data","has_more"]},"RegenerateWebhookSecretResponse":{"type":"object","properties":{"object":{"type":"string","enum":["webhook_secret"]},"secret":{"type":"string"}},"required":["object","secret"]}},"parameters":{}},"paths":{"/v1/conversations/{conversationId}/messages":{"get":{"tags":["Conversations"],"summary":"List messages for a conversation","security":[{"ApiKeyAuth":[]}],"parameters":[{"schema":{"type":"string","format":"uuid","example":"019d1b6e-af9e-715b-80e9-6fc1240a585a"},"required":true,"name":"conversationId","in":"path"},{"schema":{"type":"integer","minimum":1,"maximum":200,"default":100},"required":false,"name":"limit","in":"query"},{"schema":{"type":"string","format":"date-time"},"required":false,"name":"before","in":"query"}],"responses":{"200":{"description":"Messages ordered from oldest to newest.","content":{"application/json":{"schema":{"$ref":"#/components/schemas/MessageList"}}}},"400":{"description":"Invalid request (validation, missing params, bad shape).","content":{"application/json":{"schema":{"$ref":"#/components/schemas/Error"}}}},"401":{"description":"Missing or invalid authentication.","content":{"application/json":{"schema":{"$ref":"#/components/schemas/Error"}}}},"403":{"description":"Authenticated but not authorized (missing scope, foreign resource).","content":{"application/json":{"schema":{"$ref":"#/components/schemas/Error"}}}},"404":{"description":"Resource not found.","content":{"application/json":{"schema":{"$ref":"#/components/schemas/Error"}}}}}},"post":{"tags":["Conversations"],"summary":"Send a message in a conversation","description":"Supports text, media, template, and reaction payloads. Template messages bypass the 24-hour window; free-form messages require an active window.","security":[{"ApiKeyAuth":[]}],"parameters":[{"schema":{"type":"string","format":"uuid","example":"019d1b6e-af9e-715b-80e9-6fc1240a585a"},"required":true,"name":"conversationId","in":"path"}],"requestBody":{"content":{"application/json":{"schema":{"$ref":"#/components/schemas/SendMessageBody"}}}},"responses":{"201":{"description":"The sent message.","content":{"application/json":{"schema":{"$ref":"#/components/schemas/Message"}}}},"400":{"description":"Invalid request (validation, missing params, bad shape).","content":{"application/json":{"schema":{"$ref":"#/components/schemas/Error"}}}},"401":{"description":"Missing or invalid authentication.","content":{"application/json":{"schema":{"$ref":"#/components/schemas/Error"}}}},"403":{"description":"Authenticated but not authorized (missing scope, foreign resource).","content":{"application/json":{"schema":{"$ref":"#/components/schemas/Error"}}}},"404":{"description":"Resource not found.","content":{"application/json":{"schema":{"$ref":"#/components/schemas/Error"}}}},"429":{"description":"Rate limited by WhatsApp.","content":{"application/json":{"schema":{"$ref":"#/components/schemas/Error"}}}}}}},"/v1/conversations":{"get":{"tags":["Conversations"],"summary":"List conversations","security":[{"ApiKeyAuth":[]}],"parameters":[{"schema":{"type":"string","enum":["active","archived","spam","snoozed"]},"required":false,"name":"status","in":"query"},{"schema":{"type":"string","enum":["attention","mine","unassigned","ai","all","snoozed","closed"]},"required":false,"name":"view","in":"query"},{"schema":{"type":"string","maxLength":200},"required":false,"name":"q","in":"query"},{"schema":{"type":"string","format":"uuid","example":"019d1b6e-af9e-715b-80e9-6fc1240a585a"},"required":false,"name":"assignee","in":"query"},{"schema":{"type":"string","format":"uuid","example":"019d1b6e-af9e-715b-80e9-6fc1240a585a"},"required":false,"name":"tag","in":"query"},{"schema":{"type":["boolean","null"]},"required":false,"name":"starred","in":"query"},{"schema":{"type":"string","format":"uuid","example":"019d1b6e-af9e-715b-80e9-6fc1240a585a"},"required":false,"name":"accountId","in":"query"},{"schema":{"type":"string"},"required":false,"name":"phone","in":"query"},{"schema":{"type":"integer","minimum":1,"maximum":200,"default":50},"required":false,"name":"limit","in":"query"},{"schema":{"type":"string","format":"uuid","example":"019d1b6e-af9e-715b-80e9-6fc1240a585a"},"required":false,"name":"after","in":"query"},{"schema":{"type":["integer","null"],"minimum":0},"required":false,"name":"offset","in":"query"}],"responses":{"200":{"description":"Paginated conversations.","content":{"application/json":{"schema":{"$ref":"#/components/schemas/ConversationList"}}}},"400":{"description":"Invalid request (validation, missing params, bad shape).","content":{"application/json":{"schema":{"$ref":"#/components/schemas/Error"}}}},"401":{"description":"Missing or invalid authentication.","content":{"application/json":{"schema":{"$ref":"#/components/schemas/Error"}}}},"403":{"description":"Authenticated but not authorized (missing scope, foreign resource).","content":{"application/json":{"schema":{"$ref":"#/components/schemas/Error"}}}},"404":{"description":"Resource not found.","content":{"application/json":{"schema":{"$ref":"#/components/schemas/Error"}}}}}}},"/v1/tags/by-phones":{"post":{"tags":["Tags"],"summary":"Lookup tags for many phone numbers at once","description":"Returns a map of phone → tags[]. Phones without matching records are omitted from the map.","security":[{"ApiKeyAuth":[]}],"requestBody":{"content":{"application/json":{"schema":{"$ref":"#/components/schemas/TagsByPhonesBody"}}}},"responses":{"200":{"description":"Tags grouped by phone.","content":{"application/json":{"schema":{"$ref":"#/components/schemas/TagsByPhonesResponse"}}}},"400":{"description":"Invalid request (validation, missing params, bad shape).","content":{"application/json":{"schema":{"$ref":"#/components/schemas/Error"}}}},"401":{"description":"Missing or invalid authentication.","content":{"application/json":{"schema":{"$ref":"#/components/schemas/Error"}}}},"403":{"description":"Authenticated but not authorized (missing scope, foreign resource).","content":{"application/json":{"schema":{"$ref":"#/components/schemas/Error"}}}},"404":{"description":"Resource not found.","content":{"application/json":{"schema":{"$ref":"#/components/schemas/Error"}}}}}}},"/whatsapp/{version}/{phone_number_id}/messages":{"post":{"tags":["WhatsApp Cloud API (compatible)"],"summary":"Send a message (Meta Cloud API compatible)","description":"Drop-in replacement for Meta's `POST /{version}/{phone_number_id}/messages`: keep your existing Cloud API payloads and SDKs, change only the base URL and use your BlueMarlin API key (`bm_live_*`, scope `whatsapp:write`) as the Bearer token. Supports every Meta message type (text, template, interactive, media by link, location, reaction...). Sent messages are recorded in the BlueMarlin inbox automatically, and contact restrictions are enforced here exactly as on `/v1` — a blocked or opted-out recipient is refused with 403 before anything reaches Meta. Responses mirror Meta's shapes, including errors. See Meta's Cloud API reference for the full payload catalog.","security":[{"ApiKeyAuth":[]}],"parameters":[{"schema":{"type":"string","example":"v25.0","description":"Graph API version, forwarded to Meta as-is."},"required":true,"description":"Graph API version, forwarded to Meta as-is.","name":"version","in":"path"},{"schema":{"type":"string","description":"Meta phone number ID of one of your connected WhatsApp numbers."},"required":true,"description":"Meta phone number ID of one of your connected WhatsApp numbers.","name":"phone_number_id","in":"path"}],"requestBody":{"content":{"application/json":{"schema":{"type":"object","additionalProperties":{},"description":"Meta Cloud API message payload, forwarded verbatim.","example":{"messaging_product":"whatsapp","to":"+34600000000","type":"text","text":{"body":"Hello from BlueMarlin"}}}}}},"responses":{"200":{"description":"Meta's response, returned verbatim (includes `messages[0].id`, the wamid).","content":{"application/json":{"schema":{"type":"object","additionalProperties":{}}}}},"401":{"description":"Missing or invalid API key (Meta-style error shape)."},"403":{"description":"API key lacks the `whatsapp:write` scope, or the recipient is blocked / opted out of marketing (`error_subcode` is `contact_blocked` or `contact_opted_out`). Meta-style error shape."},"404":{"description":"Phone number not found or not owned by the key's organization."},"502":{"description":"The connection to Meta failed (`type: NetworkException`). Meta may or may not have accepted the message."},"504":{"description":"Meta did not answer within 30 seconds (`type: TimeoutException`). Meta may or may not have accepted the message."}}}},"/v1/me/usage":{"get":{"tags":["Auth"],"summary":"Month-to-date AI usage for the caller and their organization","description":"Returns the calling org's month-to-date AI token usage, estimated cost, and quota state for the current calendar month (UTC). When authenticated via cookie, `user` carries the individual caller's slice; for API-key callers `user` is null (no associated user). Requires the `settings:read` scope (this exposes org-wide billing data).","security":[{"ApiKeyAuth":[]}],"responses":{"200":{"description":"Usage + quota snapshot.","content":{"application/json":{"schema":{"$ref":"#/components/schemas/UsageResponse"}}}},"400":{"description":"Invalid request (validation, missing params, bad shape).","content":{"application/json":{"schema":{"$ref":"#/components/schemas/Error"}}}},"401":{"description":"Missing or invalid authentication.","content":{"application/json":{"schema":{"$ref":"#/components/schemas/Error"}}}},"403":{"description":"Authenticated but not authorized (missing scope, foreign resource).","content":{"application/json":{"schema":{"$ref":"#/components/schemas/Error"}}}},"404":{"description":"Resource not found.","content":{"application/json":{"schema":{"$ref":"#/components/schemas/Error"}}}}}}},"/v1/collections/{collectionId}":{"get":{"tags":["Collections"],"summary":"Retrieve a collection","security":[{"ApiKeyAuth":[]}],"parameters":[{"schema":{"type":"string","format":"uuid","example":"019d1b6e-af9e-715b-80e9-6fc1240a585a"},"required":true,"name":"collectionId","in":"path"}],"responses":{"200":{"description":"The collection.","content":{"application/json":{"schema":{"$ref":"#/components/schemas/Collection"}}}},"400":{"description":"Invalid request (validation, missing params, bad shape).","content":{"application/json":{"schema":{"$ref":"#/components/schemas/Error"}}}},"401":{"description":"Missing or invalid authentication.","content":{"application/json":{"schema":{"$ref":"#/components/schemas/Error"}}}},"403":{"description":"Authenticated but not authorized (missing scope, foreign resource).","content":{"application/json":{"schema":{"$ref":"#/components/schemas/Error"}}}},"404":{"description":"Resource not found.","content":{"application/json":{"schema":{"$ref":"#/components/schemas/Error"}}}}}}},"/v1/collections/{collectionId}/records":{"get":{"tags":["Records"],"summary":"List records in a collection","description":"Returns records with cursor-based pagination. Pass `after` (UUID from `next_cursor` of the previous page) to fetch the next page. Supports `search` for fuzzy text and `filter` as URL-encoded JSON. `offset` is accepted as legacy fallback but `after` is preferred.","security":[{"ApiKeyAuth":[]}],"parameters":[{"schema":{"type":"string","format":"uuid","example":"019d1b6e-af9e-715b-80e9-6fc1240a585a"},"required":true,"name":"collectionId","in":"path"},{"schema":{"type":"integer","minimum":1,"maximum":500,"default":100},"required":false,"name":"limit","in":"query"},{"schema":{"type":"string","format":"uuid"},"required":false,"name":"after","in":"query"},{"schema":{"type":"string","enum":["asc","desc"],"default":"desc"},"required":false,"name":"order","in":"query"},{"schema":{"type":["integer","null"],"minimum":0},"required":false,"name":"offset","in":"query"},{"schema":{"type":"string","minLength":1,"maxLength":256},"required":false,"name":"search","in":"query"},{"schema":{"type":"string"},"required":false,"name":"groupByFieldId","in":"query"},{"schema":{"type":"string"},"required":false,"name":"groupKey","in":"query"},{"schema":{"type":"string"},"required":false,"name":"filter","in":"query"},{"schema":{"type":"string"},"required":false,"name":"sort","in":"query"}],"responses":{"200":{"description":"Paginated records.","content":{"application/json":{"schema":{"$ref":"#/components/schemas/RecordList"}}}},"400":{"description":"Invalid request (validation, missing params, bad shape).","content":{"application/json":{"schema":{"$ref":"#/components/schemas/Error"}}}},"401":{"description":"Missing or invalid authentication.","content":{"application/json":{"schema":{"$ref":"#/components/schemas/Error"}}}},"403":{"description":"Authenticated but not authorized (missing scope, foreign resource).","content":{"application/json":{"schema":{"$ref":"#/components/schemas/Error"}}}},"404":{"description":"Resource not found.","content":{"application/json":{"schema":{"$ref":"#/components/schemas/Error"}}}}}},"post":{"tags":["Records"],"summary":"Create a record","security":[{"ApiKeyAuth":[]}],"parameters":[{"schema":{"type":"string","format":"uuid","example":"019d1b6e-af9e-715b-80e9-6fc1240a585a"},"required":true,"name":"collectionId","in":"path"}],"requestBody":{"content":{"application/json":{"schema":{"$ref":"#/components/schemas/CreateRecordBody"}}}},"responses":{"201":{"description":"The created record.","content":{"application/json":{"schema":{"$ref":"#/components/schemas/Record"}}}},"400":{"description":"Invalid request (validation, missing params, bad shape).","content":{"application/json":{"schema":{"$ref":"#/components/schemas/Error"}}}},"401":{"description":"Missing or invalid authentication.","content":{"application/json":{"schema":{"$ref":"#/components/schemas/Error"}}}},"403":{"description":"Authenticated but not authorized (missing scope, foreign resource).","content":{"application/json":{"schema":{"$ref":"#/components/schemas/Error"}}}},"404":{"description":"Resource not found.","content":{"application/json":{"schema":{"$ref":"#/components/schemas/Error"}}}},"409":{"description":"Phone number already exists in this collection.","content":{"application/json":{"schema":{"$ref":"#/components/schemas/Error"}}}}}}},"/v1/me":{"get":{"tags":["Auth"],"summary":"Inspect the current authentication context","description":"Returns the organization, authentication method, and scopes associated with the caller. Useful for clients (Claude Code, MCP servers, mobile apps) to confirm access at startup, and as the stable workspace identifier to key an external system against. Requires no scope — any valid credential can introspect itself.","security":[{"ApiKeyAuth":[]}],"responses":{"200":{"description":"Current auth context.","content":{"application/json":{"schema":{"$ref":"#/components/schemas/Me"}}}},"400":{"description":"Invalid request (validation, missing params, bad shape).","content":{"application/json":{"schema":{"$ref":"#/components/schemas/Error"}}}},"401":{"description":"Missing or invalid authentication.","content":{"application/json":{"schema":{"$ref":"#/components/schemas/Error"}}}},"403":{"description":"Authenticated but not authorized (missing scope, foreign resource).","content":{"application/json":{"schema":{"$ref":"#/components/schemas/Error"}}}},"404":{"description":"Resource not found.","content":{"application/json":{"schema":{"$ref":"#/components/schemas/Error"}}}}}}},"/v1/whatsapp/accounts":{"get":{"tags":["WhatsApp"],"summary":"List WhatsApp Business accounts","security":[{"ApiKeyAuth":[]}],"responses":{"200":{"description":"All accounts in the org.","content":{"application/json":{"schema":{"$ref":"#/components/schemas/WhatsAppAccountListResponse"}}}},"400":{"description":"Invalid request (validation, missing params, bad shape).","content":{"application/json":{"schema":{"$ref":"#/components/schemas/Error"}}}},"401":{"description":"Missing or invalid authentication.","content":{"application/json":{"schema":{"$ref":"#/components/schemas/Error"}}}},"403":{"description":"Authenticated but not authorized (missing scope, foreign resource).","content":{"application/json":{"schema":{"$ref":"#/components/schemas/Error"}}}},"404":{"description":"Resource not found.","content":{"application/json":{"schema":{"$ref":"#/components/schemas/Error"}}}}}}},"/whatsapp/{version}/{waba_id}/message_templates":{"get":{"tags":["WhatsApp Cloud API (compatible)"],"summary":"List templates (Meta Cloud API compatible)","description":"Mirror of Meta's `GET /{waba_id}/message_templates`. Query parameters (`fields`, `limit`, `status`, `name`, cursors) are forwarded verbatim and Meta's paginated response is returned as-is. Authenticate with a BlueMarlin API key (`bm_live_*`, scope `whatsapp:read`).","security":[{"ApiKeyAuth":[]}],"parameters":[{"schema":{"type":"string","example":"v25.0"},"required":true,"name":"version","in":"path"},{"schema":{"type":"string","description":"WhatsApp Business Account ID owning your numbers."},"required":true,"description":"WhatsApp Business Account ID owning your numbers.","name":"waba_id","in":"path"}],"responses":{"200":{"description":"Meta's template list response, verbatim (`data[]`, `paging`).","content":{"application/json":{"schema":{"type":"object","additionalProperties":{}}}}},"401":{"description":"Missing or invalid API key (Meta-style error shape)."},"403":{"description":"API key lacks the `whatsapp:read` scope."},"404":{"description":"WABA not found or not owned by the key's organization."}}},"post":{"tags":["WhatsApp Cloud API (compatible)"],"summary":"Create a template (Meta Cloud API compatible)","description":"Mirror of Meta's `POST /{waba_id}/message_templates`: same body (`name`, `language`, `category`, `components`), forwarded verbatim — Meta validates and queues the template for review. Requires scope `whatsapp:write`. The BlueMarlin template list resyncs automatically after creation; approval status updates arrive via Meta webhooks as usual.","security":[{"ApiKeyAuth":[]}],"parameters":[{"schema":{"type":"string","example":"v25.0"},"required":true,"name":"version","in":"path"},{"schema":{"type":"string"},"required":true,"name":"waba_id","in":"path"}],"requestBody":{"content":{"application/json":{"schema":{"type":"object","additionalProperties":{},"example":{"name":"order_shipped","language":"es","category":"UTILITY","components":[{"type":"BODY","text":"Tu pedido {{1}} ha sido enviado."}]}}}}},"responses":{"200":{"description":"Meta's response, verbatim (`id`, `status`, `category`).","content":{"application/json":{"schema":{"type":"object","additionalProperties":{}}}}},"401":{"description":"Missing or invalid API key (Meta-style error shape)."},"403":{"description":"API key lacks the `whatsapp:write` scope."},"404":{"description":"WABA not found or not owned by the key's organization."}}},"delete":{"tags":["WhatsApp Cloud API (compatible)"],"summary":"Delete a template (Meta Cloud API compatible)","description":"Mirror of Meta's `DELETE /{waba_id}/message_templates?name=...` (deletes every language of that template name; add `hsm_id` to target one). Requires scope `whatsapp:write`. The BlueMarlin template list resyncs automatically.","security":[{"ApiKeyAuth":[]}],"parameters":[{"schema":{"type":"string","example":"v25.0"},"required":true,"name":"version","in":"path"},{"schema":{"type":"string"},"required":true,"name":"waba_id","in":"path"},{"schema":{"type":"string","description":"Template name to delete."},"required":true,"description":"Template name to delete.","name":"name","in":"query"},{"schema":{"type":"string"},"required":false,"name":"hsm_id","in":"query"}],"responses":{"200":{"description":"Meta's response, verbatim (`{ success: true }`).","content":{"application/json":{"schema":{"type":"object","additionalProperties":{}}}}},"401":{"description":"Missing or invalid API key (Meta-style error shape)."},"403":{"description":"API key lacks the `whatsapp:write` scope."},"404":{"description":"WABA not found or not owned by the key's organization."}}}},"/v1/webhooks":{"get":{"tags":["Webhooks"],"summary":"Retrieve the organization's outbound webhook configuration","security":[{"ApiKeyAuth":[]}],"responses":{"200":{"description":"Webhook config.","content":{"application/json":{"schema":{"$ref":"#/components/schemas/WebhookConfig"}}}},"400":{"description":"Invalid request (validation, missing params, bad shape).","content":{"application/json":{"schema":{"$ref":"#/components/schemas/Error"}}}},"401":{"description":"Missing or invalid authentication.","content":{"application/json":{"schema":{"$ref":"#/components/schemas/Error"}}}},"403":{"description":"Authenticated but not authorized (missing scope, foreign resource).","content":{"application/json":{"schema":{"$ref":"#/components/schemas/Error"}}}},"404":{"description":"Resource not found.","content":{"application/json":{"schema":{"$ref":"#/components/schemas/Error"}}}}}},"put":{"tags":["Webhooks"],"summary":"Update the outbound webhook configuration","description":"Configures the URL, subscribed event types, and enabled flag. Non-localhost URLs must use HTTPS. Rotate the signing secret via POST /v1/webhooks/regenerate-secret. Omitting `events` leaves the current subscription untouched; an empty array disables every event without disabling the webhook. Legacy values (`whatsapp`, `messages`) are read back as the full event list.","security":[{"ApiKeyAuth":[]}],"requestBody":{"content":{"application/json":{"schema":{"$ref":"#/components/schemas/UpdateWebhookBody"}}}},"responses":{"200":{"description":"Updated config.","content":{"application/json":{"schema":{"$ref":"#/components/schemas/WebhookConfig"}}}},"400":{"description":"Invalid request (validation, missing params, bad shape).","content":{"application/json":{"schema":{"$ref":"#/components/schemas/Error"}}}},"401":{"description":"Missing or invalid authentication.","content":{"application/json":{"schema":{"$ref":"#/components/schemas/Error"}}}},"403":{"description":"Authenticated but not authorized (missing scope, foreign resource).","content":{"application/json":{"schema":{"$ref":"#/components/schemas/Error"}}}},"404":{"description":"Resource not found.","content":{"application/json":{"schema":{"$ref":"#/components/schemas/Error"}}}}}}},"/v1/webhooks/test":{"post":{"tags":["Webhooks"],"summary":"Send a test event to the configured webhook URL","description":"Delivers a synthetic event of the given type to the configured URL. Useful to verify signature validation on the receiver.","security":[{"ApiKeyAuth":[]}],"requestBody":{"content":{"application/json":{"schema":{"$ref":"#/components/schemas/TestWebhookBody"}}}},"responses":{"200":{"description":"Delivery result.","content":{"application/json":{"schema":{"$ref":"#/components/schemas/TestWebhookResponse"}}}},"400":{"description":"Invalid request (validation, missing params, bad shape).","content":{"application/json":{"schema":{"$ref":"#/components/schemas/Error"}}}},"401":{"description":"Missing or invalid authentication.","content":{"application/json":{"schema":{"$ref":"#/components/schemas/Error"}}}},"403":{"description":"Authenticated but not authorized (missing scope, foreign resource).","content":{"application/json":{"schema":{"$ref":"#/components/schemas/Error"}}}},"404":{"description":"Resource not found.","content":{"application/json":{"schema":{"$ref":"#/components/schemas/Error"}}}}}}},"/v1/search":{"get":{"tags":["Search"],"summary":"Global search across records","description":"Returns the top matching records across every collection in the org, ordered by full-text relevance with recency as a tiebreaker. Every word matches as a prefix, so it can be called while the user types. When `q` is omitted or holds no letter or digit, returns the most-recently-updated records instead — useful as the command-palette's idle state. Each result carries the collection's name/icon so the renderer doesn't have to cross-reference.","security":[{"ApiKeyAuth":[]}],"parameters":[{"schema":{"type":"string","maxLength":256},"required":false,"name":"q","in":"query"},{"schema":{"type":"integer","minimum":1,"maximum":50,"default":20},"required":false,"name":"limit","in":"query"}],"responses":{"200":{"description":"Ranked search hits.","content":{"application/json":{"schema":{"$ref":"#/components/schemas/SearchList"}}}},"400":{"description":"Invalid request (validation, missing params, bad shape).","content":{"application/json":{"schema":{"$ref":"#/components/schemas/Error"}}}},"401":{"description":"Missing or invalid authentication.","content":{"application/json":{"schema":{"$ref":"#/components/schemas/Error"}}}},"403":{"description":"Authenticated but not authorized (missing scope, foreign resource).","content":{"application/json":{"schema":{"$ref":"#/components/schemas/Error"}}}},"404":{"description":"Resource not found.","content":{"application/json":{"schema":{"$ref":"#/components/schemas/Error"}}}}}}},"/v1/collections":{"get":{"tags":["Collections"],"summary":"List collections","description":"Returns the collections the caller can see: everything shared with the team, plus — for a signed-in member — their own private ones. An API key sees shared collections only.","security":[{"ApiKeyAuth":[]}],"responses":{"200":{"description":"A list of collections.","content":{"application/json":{"schema":{"$ref":"#/components/schemas/CollectionList"}}}},"400":{"description":"Invalid request (validation, missing params, bad shape).","content":{"application/json":{"schema":{"$ref":"#/components/schemas/Error"}}}},"401":{"description":"Missing or invalid authentication.","content":{"application/json":{"schema":{"$ref":"#/components/schemas/Error"}}}},"403":{"description":"Authenticated but not authorized (missing scope, foreign resource).","content":{"application/json":{"schema":{"$ref":"#/components/schemas/Error"}}}},"404":{"description":"Resource not found.","content":{"application/json":{"schema":{"$ref":"#/components/schemas/Error"}}}}}}},"/v1/collections/{collectionId}/records/count":{"get":{"tags":["Records"],"summary":"Count records in a collection","description":"Returns the total number of records matching `search` and `filter`, without fetching them.","security":[{"ApiKeyAuth":[]}],"parameters":[{"schema":{"type":"string","format":"uuid","example":"019d1b6e-af9e-715b-80e9-6fc1240a585a"},"required":true,"name":"collectionId","in":"path"},{"schema":{"type":"string","minLength":1,"maxLength":256},"required":false,"name":"search","in":"query"},{"schema":{"type":"string"},"required":false,"name":"filter","in":"query"}],"responses":{"200":{"description":"Total matching records.","content":{"application/json":{"schema":{"$ref":"#/components/schemas/RecordCount"}}}},"400":{"description":"Invalid request (validation, missing params, bad shape).","content":{"application/json":{"schema":{"$ref":"#/components/schemas/Error"}}}},"401":{"description":"Missing or invalid authentication.","content":{"application/json":{"schema":{"$ref":"#/components/schemas/Error"}}}},"403":{"description":"Authenticated but not authorized (missing scope, foreign resource).","content":{"application/json":{"schema":{"$ref":"#/components/schemas/Error"}}}},"404":{"description":"Resource not found.","content":{"application/json":{"schema":{"$ref":"#/components/schemas/Error"}}}}}}},"/v1/collections/{collectionId}/records/{recordId}":{"get":{"tags":["Records"],"summary":"Retrieve a record","security":[{"ApiKeyAuth":[]}],"parameters":[{"schema":{"type":"string","format":"uuid","example":"019d1b6e-af9e-715b-80e9-6fc1240a585a"},"required":true,"name":"collectionId","in":"path"},{"schema":{"type":"string","format":"uuid","example":"019d1b6e-af9e-715b-80e9-6fc1240a585a"},"required":true,"name":"recordId","in":"path"}],"responses":{"200":{"description":"The record.","content":{"application/json":{"schema":{"$ref":"#/components/schemas/Record"}}}},"400":{"description":"Invalid request (validation, missing params, bad shape).","content":{"application/json":{"schema":{"$ref":"#/components/schemas/Error"}}}},"401":{"description":"Missing or invalid authentication.","content":{"application/json":{"schema":{"$ref":"#/components/schemas/Error"}}}},"403":{"description":"Authenticated but not authorized (missing scope, foreign resource).","content":{"application/json":{"schema":{"$ref":"#/components/schemas/Error"}}}},"404":{"description":"Resource not found.","content":{"application/json":{"schema":{"$ref":"#/components/schemas/Error"}}}}}},"patch":{"tags":["Records"],"summary":"Update a record","description":"Partially updates a record. `properties` is shallow-merged; omitted keys are preserved. Send `If-Match` with the record's `updatedAt` to make the write conditional: if someone else saved it in the meantime the call is refused with 409 `record_changed` instead of overwriting them.","security":[{"ApiKeyAuth":[]}],"parameters":[{"schema":{"type":"string","format":"uuid","example":"019d1b6e-af9e-715b-80e9-6fc1240a585a"},"required":true,"name":"collectionId","in":"path"},{"schema":{"type":"string","format":"uuid","example":"019d1b6e-af9e-715b-80e9-6fc1240a585a"},"required":true,"name":"recordId","in":"path"},{"schema":{"type":"string","description":"The `updatedAt` the edit started from. Omit for last-write-wins."},"required":false,"description":"The `updatedAt` the edit started from. Omit for last-write-wins.","name":"If-Match","in":"header"}],"requestBody":{"content":{"application/json":{"schema":{"$ref":"#/components/schemas/UpdateRecordBody"}}}},"responses":{"200":{"description":"The updated record.","content":{"application/json":{"schema":{"$ref":"#/components/schemas/Record"}}}},"400":{"description":"Invalid request (validation, missing params, bad shape).","content":{"application/json":{"schema":{"$ref":"#/components/schemas/Error"}}}},"401":{"description":"Missing or invalid authentication.","content":{"application/json":{"schema":{"$ref":"#/components/schemas/Error"}}}},"403":{"description":"Authenticated but not authorized (missing scope, foreign resource).","content":{"application/json":{"schema":{"$ref":"#/components/schemas/Error"}}}},"404":{"description":"Resource not found.","content":{"application/json":{"schema":{"$ref":"#/components/schemas/Error"}}}},"409":{"description":"Phone number already exists, or the record changed under an `If-Match`.","content":{"application/json":{"schema":{"$ref":"#/components/schemas/Error"}}}}}},"delete":{"tags":["Records"],"summary":"Delete a record","security":[{"ApiKeyAuth":[]}],"parameters":[{"schema":{"type":"string","format":"uuid","example":"019d1b6e-af9e-715b-80e9-6fc1240a585a"},"required":true,"name":"collectionId","in":"path"},{"schema":{"type":"string","format":"uuid","example":"019d1b6e-af9e-715b-80e9-6fc1240a585a"},"required":true,"name":"recordId","in":"path"}],"responses":{"200":{"description":"Deletion confirmation.","content":{"application/json":{"schema":{"$ref":"#/components/schemas/DeletedResponse"}}}},"400":{"description":"Invalid request (validation, missing params, bad shape).","content":{"application/json":{"schema":{"$ref":"#/components/schemas/Error"}}}},"401":{"description":"Missing or invalid authentication.","content":{"application/json":{"schema":{"$ref":"#/components/schemas/Error"}}}},"403":{"description":"Authenticated but not authorized (missing scope, foreign resource).","content":{"application/json":{"schema":{"$ref":"#/components/schemas/Error"}}}},"404":{"description":"Resource not found.","content":{"application/json":{"schema":{"$ref":"#/components/schemas/Error"}}}}}}},"/v1/records/{recordId}/tags":{"get":{"tags":["Tags"],"summary":"List tags attached to a record","security":[{"ApiKeyAuth":[]}],"parameters":[{"schema":{"type":"string","format":"uuid","example":"019d1b6e-af9e-715b-80e9-6fc1240a585a"},"required":true,"name":"recordId","in":"path"}],"responses":{"200":{"description":"The record's tags.","content":{"application/json":{"schema":{"$ref":"#/components/schemas/TagList"}}}},"400":{"description":"Invalid request (validation, missing params, bad shape).","content":{"application/json":{"schema":{"$ref":"#/components/schemas/Error"}}}},"401":{"description":"Missing or invalid authentication.","content":{"application/json":{"schema":{"$ref":"#/components/schemas/Error"}}}},"403":{"description":"Authenticated but not authorized (missing scope, foreign resource).","content":{"application/json":{"schema":{"$ref":"#/components/schemas/Error"}}}},"404":{"description":"Resource not found.","content":{"application/json":{"schema":{"$ref":"#/components/schemas/Error"}}}}}},"put":{"tags":["Tags"],"summary":"Replace the set of tags attached to a record","security":[{"ApiKeyAuth":[]}],"parameters":[{"schema":{"type":"string","format":"uuid","example":"019d1b6e-af9e-715b-80e9-6fc1240a585a"},"required":true,"name":"recordId","in":"path"}],"requestBody":{"content":{"application/json":{"schema":{"$ref":"#/components/schemas/SetRecordTagsBody"}}}},"responses":{"200":{"description":"Confirmation.","content":{"application/json":{"schema":{"$ref":"#/components/schemas/TagList"}}}},"400":{"description":"Invalid request (validation, missing params, bad shape).","content":{"application/json":{"schema":{"$ref":"#/components/schemas/Error"}}}},"401":{"description":"Missing or invalid authentication.","content":{"application/json":{"schema":{"$ref":"#/components/schemas/Error"}}}},"403":{"description":"Authenticated but not authorized (missing scope, foreign resource).","content":{"application/json":{"schema":{"$ref":"#/components/schemas/Error"}}}},"404":{"description":"Resource not found.","content":{"application/json":{"schema":{"$ref":"#/components/schemas/Error"}}}}}},"post":{"tags":["Tags"],"summary":"Attach a tag to a record","security":[{"ApiKeyAuth":[]}],"parameters":[{"schema":{"type":"string","format":"uuid","example":"019d1b6e-af9e-715b-80e9-6fc1240a585a"},"required":true,"name":"recordId","in":"path"}],"requestBody":{"content":{"application/json":{"schema":{"$ref":"#/components/schemas/AddRecordTagBody"}}}},"responses":{"201":{"description":"The attached tag.","content":{"application/json":{"schema":{"$ref":"#/components/schemas/Tag"}}}},"400":{"description":"Invalid request (validation, missing params, bad shape).","content":{"application/json":{"schema":{"$ref":"#/components/schemas/Error"}}}},"401":{"description":"Missing or invalid authentication.","content":{"application/json":{"schema":{"$ref":"#/components/schemas/Error"}}}},"403":{"description":"Authenticated but not authorized (missing scope, foreign resource).","content":{"application/json":{"schema":{"$ref":"#/components/schemas/Error"}}}},"404":{"description":"Resource not found.","content":{"application/json":{"schema":{"$ref":"#/components/schemas/Error"}}}}}}},"/v1/records/{recordId}/tags/{tagId}":{"delete":{"tags":["Tags"],"summary":"Detach a tag from a record","security":[{"ApiKeyAuth":[]}],"parameters":[{"schema":{"type":"string","format":"uuid","example":"019d1b6e-af9e-715b-80e9-6fc1240a585a"},"required":true,"name":"recordId","in":"path"},{"schema":{"type":"string","format":"uuid","example":"019d1b6e-af9e-715b-80e9-6fc1240a585a"},"required":true,"name":"tagId","in":"path"}],"responses":{"200":{"description":"Detach confirmation.","content":{"application/json":{"schema":{"$ref":"#/components/schemas/DeletedResponse"}}}},"400":{"description":"Invalid request (validation, missing params, bad shape).","content":{"application/json":{"schema":{"$ref":"#/components/schemas/Error"}}}},"401":{"description":"Missing or invalid authentication.","content":{"application/json":{"schema":{"$ref":"#/components/schemas/Error"}}}},"403":{"description":"Authenticated but not authorized (missing scope, foreign resource).","content":{"application/json":{"schema":{"$ref":"#/components/schemas/Error"}}}},"404":{"description":"Resource not found.","content":{"application/json":{"schema":{"$ref":"#/components/schemas/Error"}}}}}}},"/v1/tags":{"get":{"tags":["Tags"],"summary":"List tags","security":[{"ApiKeyAuth":[]}],"responses":{"200":{"description":"All tags in the organization.","content":{"application/json":{"schema":{"$ref":"#/components/schemas/TagList"}}}},"400":{"description":"Invalid request (validation, missing params, bad shape).","content":{"application/json":{"schema":{"$ref":"#/components/schemas/Error"}}}},"401":{"description":"Missing or invalid authentication.","content":{"application/json":{"schema":{"$ref":"#/components/schemas/Error"}}}},"403":{"description":"Authenticated but not authorized (missing scope, foreign resource).","content":{"application/json":{"schema":{"$ref":"#/components/schemas/Error"}}}},"404":{"description":"Resource not found.","content":{"application/json":{"schema":{"$ref":"#/components/schemas/Error"}}}}}},"post":{"tags":["Tags"],"summary":"Create a tag","security":[{"ApiKeyAuth":[]}],"requestBody":{"content":{"application/json":{"schema":{"$ref":"#/components/schemas/CreateTagBody"}}}},"responses":{"201":{"description":"The created tag.","content":{"application/json":{"schema":{"$ref":"#/components/schemas/Tag"}}}},"400":{"description":"Invalid request (validation, missing params, bad shape).","content":{"application/json":{"schema":{"$ref":"#/components/schemas/Error"}}}},"401":{"description":"Missing or invalid authentication.","content":{"application/json":{"schema":{"$ref":"#/components/schemas/Error"}}}},"403":{"description":"Authenticated but not authorized (missing scope, foreign resource).","content":{"application/json":{"schema":{"$ref":"#/components/schemas/Error"}}}},"404":{"description":"Resource not found.","content":{"application/json":{"schema":{"$ref":"#/components/schemas/Error"}}}},"409":{"description":"A tag with this name already exists.","content":{"application/json":{"schema":{"$ref":"#/components/schemas/Error"}}}}}}},"/v1/tags/{tagId}":{"patch":{"tags":["Tags"],"summary":"Update a tag","security":[{"ApiKeyAuth":[]}],"parameters":[{"schema":{"type":"string","format":"uuid","example":"019d1b6e-af9e-715b-80e9-6fc1240a585a"},"required":true,"name":"tagId","in":"path"}],"requestBody":{"content":{"application/json":{"schema":{"$ref":"#/components/schemas/UpdateTagBody"}}}},"responses":{"200":{"description":"The updated tag.","content":{"application/json":{"schema":{"$ref":"#/components/schemas/Tag"}}}},"400":{"description":"Invalid request (validation, missing params, bad shape).","content":{"application/json":{"schema":{"$ref":"#/components/schemas/Error"}}}},"401":{"description":"Missing or invalid authentication.","content":{"application/json":{"schema":{"$ref":"#/components/schemas/Error"}}}},"403":{"description":"Authenticated but not authorized (missing scope, foreign resource).","content":{"application/json":{"schema":{"$ref":"#/components/schemas/Error"}}}},"404":{"description":"Resource not found.","content":{"application/json":{"schema":{"$ref":"#/components/schemas/Error"}}}}}},"delete":{"tags":["Tags"],"summary":"Delete a tag","security":[{"ApiKeyAuth":[]}],"parameters":[{"schema":{"type":"string","format":"uuid","example":"019d1b6e-af9e-715b-80e9-6fc1240a585a"},"required":true,"name":"tagId","in":"path"}],"responses":{"200":{"description":"Deletion confirmation.","content":{"application/json":{"schema":{"$ref":"#/components/schemas/DeletedResponse"}}}},"400":{"description":"Invalid request (validation, missing params, bad shape).","content":{"application/json":{"schema":{"$ref":"#/components/schemas/Error"}}}},"401":{"description":"Missing or invalid authentication.","content":{"application/json":{"schema":{"$ref":"#/components/schemas/Error"}}}},"403":{"description":"Authenticated but not authorized (missing scope, foreign resource).","content":{"application/json":{"schema":{"$ref":"#/components/schemas/Error"}}}},"404":{"description":"Resource not found.","content":{"application/json":{"schema":{"$ref":"#/components/schemas/Error"}}}}}}},"/v1/conversations/{conversationId}":{"get":{"tags":["Conversations"],"summary":"Retrieve a conversation","security":[{"ApiKeyAuth":[]}],"parameters":[{"schema":{"type":"string","format":"uuid","example":"019d1b6e-af9e-715b-80e9-6fc1240a585a"},"required":true,"name":"conversationId","in":"path"}],"responses":{"200":{"description":"The conversation.","content":{"application/json":{"schema":{"$ref":"#/components/schemas/Conversation"}}}},"400":{"description":"Invalid request (validation, missing params, bad shape).","content":{"application/json":{"schema":{"$ref":"#/components/schemas/Error"}}}},"401":{"description":"Missing or invalid authentication.","content":{"application/json":{"schema":{"$ref":"#/components/schemas/Error"}}}},"403":{"description":"Authenticated but not authorized (missing scope, foreign resource).","content":{"application/json":{"schema":{"$ref":"#/components/schemas/Error"}}}},"404":{"description":"Resource not found.","content":{"application/json":{"schema":{"$ref":"#/components/schemas/Error"}}}}}}},"/v1/conversations/{conversationId}/messages/{messageId}/media":{"get":{"tags":["Conversations"],"summary":"Get a presigned URL for a message's media","description":"Only works if the media has already been downloaded from WhatsApp. Use POST first for new media.","security":[{"ApiKeyAuth":[]}],"parameters":[{"schema":{"type":"string","format":"uuid","example":"019d1b6e-af9e-715b-80e9-6fc1240a585a"},"required":true,"name":"conversationId","in":"path"},{"schema":{"type":"string","format":"uuid","example":"019d1b6e-af9e-715b-80e9-6fc1240a585a"},"required":true,"name":"messageId","in":"path"}],"responses":{"200":{"description":"Presigned URL.","content":{"application/json":{"schema":{"$ref":"#/components/schemas/MediaUrlResponse"}}}},"400":{"description":"Invalid request (validation, missing params, bad shape).","content":{"application/json":{"schema":{"$ref":"#/components/schemas/Error"}}}},"401":{"description":"Missing or invalid authentication.","content":{"application/json":{"schema":{"$ref":"#/components/schemas/Error"}}}},"403":{"description":"Authenticated but not authorized (missing scope, foreign resource).","content":{"application/json":{"schema":{"$ref":"#/components/schemas/Error"}}}},"404":{"description":"Resource not found.","content":{"application/json":{"schema":{"$ref":"#/components/schemas/Error"}}}}}},"post":{"tags":["Conversations"],"summary":"Download media from WhatsApp and store it","description":"Fetches the media bytes from Meta, stores them in object storage, and returns a presigned URL.","security":[{"ApiKeyAuth":[]}],"parameters":[{"schema":{"type":"string","format":"uuid","example":"019d1b6e-af9e-715b-80e9-6fc1240a585a"},"required":true,"name":"conversationId","in":"path"},{"schema":{"type":"string","format":"uuid","example":"019d1b6e-af9e-715b-80e9-6fc1240a585a"},"required":true,"name":"messageId","in":"path"}],"responses":{"200":{"description":"Presigned URL.","content":{"application/json":{"schema":{"$ref":"#/components/schemas/MediaUrlResponse"}}}},"400":{"description":"Invalid request (validation, missing params, bad shape).","content":{"application/json":{"schema":{"$ref":"#/components/schemas/Error"}}}},"401":{"description":"Missing or invalid authentication.","content":{"application/json":{"schema":{"$ref":"#/components/schemas/Error"}}}},"403":{"description":"Authenticated but not authorized (missing scope, foreign resource).","content":{"application/json":{"schema":{"$ref":"#/components/schemas/Error"}}}},"404":{"description":"Resource not found.","content":{"application/json":{"schema":{"$ref":"#/components/schemas/Error"}}}}}}},"/v1/conversations/{conversationId}/archive":{"post":{"tags":["Conversations"],"summary":"Archive a conversation","security":[{"ApiKeyAuth":[]}],"parameters":[{"schema":{"type":"string","format":"uuid","example":"019d1b6e-af9e-715b-80e9-6fc1240a585a"},"required":true,"name":"conversationId","in":"path"}],"responses":{"200":{"description":"Archived.","content":{"application/json":{"schema":{"$ref":"#/components/schemas/ConversationActionResponse"}}}},"400":{"description":"Invalid request (validation, missing params, bad shape).","content":{"application/json":{"schema":{"$ref":"#/components/schemas/Error"}}}},"401":{"description":"Missing or invalid authentication.","content":{"application/json":{"schema":{"$ref":"#/components/schemas/Error"}}}},"403":{"description":"Authenticated but not authorized (missing scope, foreign resource).","content":{"application/json":{"schema":{"$ref":"#/components/schemas/Error"}}}},"404":{"description":"Resource not found.","content":{"application/json":{"schema":{"$ref":"#/components/schemas/Error"}}}}}}},"/v1/conversations/{conversationId}/unarchive":{"post":{"tags":["Conversations"],"summary":"Unarchive a conversation","security":[{"ApiKeyAuth":[]}],"parameters":[{"schema":{"type":"string","format":"uuid","example":"019d1b6e-af9e-715b-80e9-6fc1240a585a"},"required":true,"name":"conversationId","in":"path"}],"responses":{"200":{"description":"Unarchived.","content":{"application/json":{"schema":{"type":"object","properties":{"object":{"type":"string","enum":["conversation_action"]},"success":{"type":"boolean"}},"required":["object","success"]}}}},"400":{"description":"Invalid request (validation, missing params, bad shape).","content":{"application/json":{"schema":{"$ref":"#/components/schemas/Error"}}}},"401":{"description":"Missing or invalid authentication.","content":{"application/json":{"schema":{"$ref":"#/components/schemas/Error"}}}},"403":{"description":"Authenticated but not authorized (missing scope, foreign resource).","content":{"application/json":{"schema":{"$ref":"#/components/schemas/Error"}}}},"404":{"description":"Resource not found.","content":{"application/json":{"schema":{"$ref":"#/components/schemas/Error"}}}}}}},"/v1/conversations/{conversationId}/read":{"post":{"tags":["Conversations"],"summary":"Mark a conversation as read","security":[{"ApiKeyAuth":[]}],"parameters":[{"schema":{"type":"string","format":"uuid","example":"019d1b6e-af9e-715b-80e9-6fc1240a585a"},"required":true,"name":"conversationId","in":"path"}],"responses":{"200":{"description":"Marked as read.","content":{"application/json":{"schema":{"type":"object","properties":{"object":{"type":"string","enum":["conversation_action"]},"success":{"type":"boolean"}},"required":["object","success"]}}}},"400":{"description":"Invalid request (validation, missing params, bad shape).","content":{"application/json":{"schema":{"$ref":"#/components/schemas/Error"}}}},"401":{"description":"Missing or invalid authentication.","content":{"application/json":{"schema":{"$ref":"#/components/schemas/Error"}}}},"403":{"description":"Authenticated but not authorized (missing scope, foreign resource).","content":{"application/json":{"schema":{"$ref":"#/components/schemas/Error"}}}},"404":{"description":"Resource not found.","content":{"application/json":{"schema":{"$ref":"#/components/schemas/Error"}}}}}}},"/v1/conversations/{conversationId}/unread":{"post":{"tags":["Conversations"],"summary":"Mark a conversation as unread","security":[{"ApiKeyAuth":[]}],"parameters":[{"schema":{"type":"string","format":"uuid","example":"019d1b6e-af9e-715b-80e9-6fc1240a585a"},"required":true,"name":"conversationId","in":"path"}],"responses":{"200":{"description":"Marked as unread.","content":{"application/json":{"schema":{"type":"object","properties":{"object":{"type":"string","enum":["conversation_action"]},"success":{"type":"boolean"}},"required":["object","success"]}}}},"400":{"description":"Invalid request (validation, missing params, bad shape).","content":{"application/json":{"schema":{"$ref":"#/components/schemas/Error"}}}},"401":{"description":"Missing or invalid authentication.","content":{"application/json":{"schema":{"$ref":"#/components/schemas/Error"}}}},"403":{"description":"Authenticated but not authorized (missing scope, foreign resource).","content":{"application/json":{"schema":{"$ref":"#/components/schemas/Error"}}}},"404":{"description":"Resource not found.","content":{"application/json":{"schema":{"$ref":"#/components/schemas/Error"}}}}}}},"/v1/conversations/{conversationId}/assign":{"post":{"tags":["Conversations"],"summary":"Assign (or unassign) a conversation to a member","security":[{"ApiKeyAuth":[]}],"parameters":[{"schema":{"type":"string","format":"uuid","example":"019d1b6e-af9e-715b-80e9-6fc1240a585a"},"required":true,"name":"conversationId","in":"path"}],"requestBody":{"content":{"application/json":{"schema":{"$ref":"#/components/schemas/AssignConversationBody"}}}},"responses":{"200":{"description":"Assignment saved.","content":{"application/json":{"schema":{"type":"object","properties":{"object":{"type":"string","enum":["conversation_action"]},"success":{"type":"boolean"}},"required":["object","success"]}}}},"400":{"description":"Invalid request (validation, missing params, bad shape).","content":{"application/json":{"schema":{"$ref":"#/components/schemas/Error"}}}},"401":{"description":"Missing or invalid authentication.","content":{"application/json":{"schema":{"$ref":"#/components/schemas/Error"}}}},"403":{"description":"Authenticated but not authorized (missing scope, foreign resource).","content":{"application/json":{"schema":{"$ref":"#/components/schemas/Error"}}}},"404":{"description":"Resource not found.","content":{"application/json":{"schema":{"$ref":"#/components/schemas/Error"}}}}}}},"/v1/conversations/{conversationId}/handler":{"post":{"tags":["Conversations"],"summary":"Set the conversation handler (AI vs human)","description":"handler=human takes the conversation over for the calling member (the AI stops answering, the member becomes the assignee). handler=ai hands it back to the AI.","security":[{"ApiKeyAuth":[]}],"parameters":[{"schema":{"type":"string","format":"uuid","example":"019d1b6e-af9e-715b-80e9-6fc1240a585a"},"required":true,"name":"conversationId","in":"path"}],"requestBody":{"content":{"application/json":{"schema":{"$ref":"#/components/schemas/SetHandlerBody"}}}},"responses":{"200":{"description":"Handler updated.","content":{"application/json":{"schema":{"type":"object","properties":{"object":{"type":"string","enum":["conversation_action"]},"success":{"type":"boolean"}},"required":["object","success"]}}}},"400":{"description":"Invalid request (validation, missing params, bad shape).","content":{"application/json":{"schema":{"$ref":"#/components/schemas/Error"}}}},"401":{"description":"Missing or invalid authentication.","content":{"application/json":{"schema":{"$ref":"#/components/schemas/Error"}}}},"403":{"description":"Authenticated but not authorized (missing scope, foreign resource).","content":{"application/json":{"schema":{"$ref":"#/components/schemas/Error"}}}},"404":{"description":"Resource not found.","content":{"application/json":{"schema":{"$ref":"#/components/schemas/Error"}}}}}}},"/v1/messages":{"get":{"tags":["Messages"],"summary":"Search messages across the organization","description":"Cross-conversation message list with optional filters.","security":[{"ApiKeyAuth":[]}],"parameters":[{"schema":{"type":"integer","minimum":1,"maximum":200,"default":50},"required":false,"name":"limit","in":"query"},{"schema":{"type":"string","format":"uuid"},"required":false,"name":"after","in":"query"},{"schema":{"type":["integer","null"],"minimum":0},"required":false,"name":"offset","in":"query"},{"schema":{"type":"string","minLength":1,"maxLength":256},"required":false,"name":"search","in":"query"},{"schema":{"type":"string","enum":["incoming","outgoing"]},"required":false,"name":"direction","in":"query"},{"schema":{"type":"string","maxLength":40},"required":false,"name":"accountPhoneNumber","in":"query"},{"schema":{"type":"string","format":"uuid","example":"019d1b6e-af9e-715b-80e9-6fc1240a585a"},"required":false,"name":"senderUserId","in":"query"}],"responses":{"200":{"description":"Messages matching filters.","content":{"application/json":{"schema":{"$ref":"#/components/schemas/MessageListAcrossConversations"}}}},"400":{"description":"Invalid request (validation, missing params, bad shape).","content":{"application/json":{"schema":{"$ref":"#/components/schemas/Error"}}}},"401":{"description":"Missing or invalid authentication.","content":{"application/json":{"schema":{"$ref":"#/components/schemas/Error"}}}},"403":{"description":"Authenticated but not authorized (missing scope, foreign resource).","content":{"application/json":{"schema":{"$ref":"#/components/schemas/Error"}}}},"404":{"description":"Resource not found.","content":{"application/json":{"schema":{"$ref":"#/components/schemas/Error"}}}}}},"post":{"tags":["Messages"],"summary":"Send a message","description":"Send a message either to an existing conversation (include `conversationId`) or to a WhatsApp account + contact phone (include `accountId` and `contactPhoneNumber`) — in the second case the conversation is created lazily. Payload shapes are the same four variants supported by POST /v1/conversations/{conversationId}/messages.","security":[{"ApiKeyAuth":[]}],"requestBody":{"content":{"application/json":{"schema":{"$ref":"#/components/schemas/SendMessageTopLevelBody"}}}},"responses":{"201":{"description":"The sent message and conversation id.","content":{"application/json":{"schema":{"$ref":"#/components/schemas/SentMessageResponse"}}}},"400":{"description":"Invalid request (validation, missing params, bad shape).","content":{"application/json":{"schema":{"$ref":"#/components/schemas/Error"}}}},"401":{"description":"Missing or invalid authentication.","content":{"application/json":{"schema":{"$ref":"#/components/schemas/Error"}}}},"403":{"description":"Authenticated but not authorized (missing scope, foreign resource).","content":{"application/json":{"schema":{"$ref":"#/components/schemas/Error"}}}},"404":{"description":"Resource not found.","content":{"application/json":{"schema":{"$ref":"#/components/schemas/Error"}}}}}}},"/v1/whatsapp/accounts/{accountId}":{"get":{"tags":["WhatsApp"],"summary":"Retrieve a WhatsApp Business account","security":[{"ApiKeyAuth":[]}],"parameters":[{"schema":{"type":"string","format":"uuid","example":"019d1b6e-af9e-715b-80e9-6fc1240a585a"},"required":true,"name":"accountId","in":"path"}],"responses":{"200":{"description":"The account.","content":{"application/json":{"schema":{"$ref":"#/components/schemas/WhatsAppAccount"}}}},"400":{"description":"Invalid request (validation, missing params, bad shape).","content":{"application/json":{"schema":{"$ref":"#/components/schemas/Error"}}}},"401":{"description":"Missing or invalid authentication.","content":{"application/json":{"schema":{"$ref":"#/components/schemas/Error"}}}},"403":{"description":"Authenticated but not authorized (missing scope, foreign resource).","content":{"application/json":{"schema":{"$ref":"#/components/schemas/Error"}}}},"404":{"description":"Resource not found.","content":{"application/json":{"schema":{"$ref":"#/components/schemas/Error"}}}}}}},"/v1/whatsapp/accounts/{accountId}/templates":{"get":{"tags":["WhatsApp"],"summary":"List templates cached for a WhatsApp account","description":"Reads from the local template cache so this endpoint is fast and safe to call on every send. Filter by `status` and/or `category`.","security":[{"ApiKeyAuth":[]}],"parameters":[{"schema":{"type":"string","format":"uuid","example":"019d1b6e-af9e-715b-80e9-6fc1240a585a"},"required":true,"name":"accountId","in":"path"},{"schema":{"type":"string"},"required":false,"name":"status","in":"query"},{"schema":{"type":"string"},"required":false,"name":"category","in":"query"}],"responses":{"200":{"description":"Cached templates.","content":{"application/json":{"schema":{"$ref":"#/components/schemas/TemplateList"}}}},"400":{"description":"Invalid request (validation, missing params, bad shape).","content":{"application/json":{"schema":{"$ref":"#/components/schemas/Error"}}}},"401":{"description":"Missing or invalid authentication.","content":{"application/json":{"schema":{"$ref":"#/components/schemas/Error"}}}},"403":{"description":"Authenticated but not authorized (missing scope, foreign resource).","content":{"application/json":{"schema":{"$ref":"#/components/schemas/Error"}}}},"404":{"description":"Resource not found.","content":{"application/json":{"schema":{"$ref":"#/components/schemas/Error"}}}}}},"post":{"tags":["WhatsApp"],"summary":"Create a WhatsApp message template","description":"Submits the template to Meta and persists the local row. The template starts in `PENDING` status and Meta updates it via webhook.","security":[{"ApiKeyAuth":[]}],"parameters":[{"schema":{"type":"string","format":"uuid","example":"019d1b6e-af9e-715b-80e9-6fc1240a585a"},"required":true,"name":"accountId","in":"path"}],"requestBody":{"content":{"application/json":{"schema":{"$ref":"#/components/schemas/CreateWhatsAppTemplateBody"}}}},"responses":{"201":{"description":"Created template.","content":{"application/json":{"schema":{"type":"object","additionalProperties":{}}}}},"400":{"description":"Invalid request (validation, missing params, bad shape).","content":{"application/json":{"schema":{"$ref":"#/components/schemas/Error"}}}},"401":{"description":"Missing or invalid authentication.","content":{"application/json":{"schema":{"$ref":"#/components/schemas/Error"}}}},"403":{"description":"Authenticated but not authorized (missing scope, foreign resource).","content":{"application/json":{"schema":{"$ref":"#/components/schemas/Error"}}}},"404":{"description":"Resource not found.","content":{"application/json":{"schema":{"$ref":"#/components/schemas/Error"}}}},"409":{"description":"A template with this name already exists.","content":{"application/json":{"schema":{"$ref":"#/components/schemas/Error"}}}},"429":{"description":"Rate limited by Meta.","content":{"application/json":{"schema":{"$ref":"#/components/schemas/Error"}}}}}}},"/v1/whatsapp/accounts/{accountId}/templates/{templateId}":{"get":{"tags":["WhatsApp"],"summary":"Retrieve a WhatsApp template","security":[{"ApiKeyAuth":[]}],"parameters":[{"schema":{"type":"string","format":"uuid","example":"019d1b6e-af9e-715b-80e9-6fc1240a585a"},"required":true,"name":"accountId","in":"path"},{"schema":{"type":"string","format":"uuid","example":"019d1b6e-af9e-715b-80e9-6fc1240a585a"},"required":true,"name":"templateId","in":"path"}],"responses":{"200":{"description":"The template.","content":{"application/json":{"schema":{"type":"object","additionalProperties":{}}}}},"400":{"description":"Invalid request (validation, missing params, bad shape).","content":{"application/json":{"schema":{"$ref":"#/components/schemas/Error"}}}},"401":{"description":"Missing or invalid authentication.","content":{"application/json":{"schema":{"$ref":"#/components/schemas/Error"}}}},"403":{"description":"Authenticated but not authorized (missing scope, foreign resource).","content":{"application/json":{"schema":{"$ref":"#/components/schemas/Error"}}}},"404":{"description":"Resource not found.","content":{"application/json":{"schema":{"$ref":"#/components/schemas/Error"}}}}}},"patch":{"tags":["WhatsApp"],"summary":"Update a WhatsApp template (Meta API + local cache)","security":[{"ApiKeyAuth":[]}],"parameters":[{"schema":{"type":"string","format":"uuid","example":"019d1b6e-af9e-715b-80e9-6fc1240a585a"},"required":true,"name":"accountId","in":"path"},{"schema":{"type":"string","format":"uuid","example":"019d1b6e-af9e-715b-80e9-6fc1240a585a"},"required":true,"name":"templateId","in":"path"}],"requestBody":{"content":{"application/json":{"schema":{"$ref":"#/components/schemas/UpdateWhatsAppTemplateBody"}}}},"responses":{"200":{"description":"Updated template.","content":{"application/json":{"schema":{"type":"object","additionalProperties":{}}}}},"400":{"description":"Invalid request (validation, missing params, bad shape).","content":{"application/json":{"schema":{"$ref":"#/components/schemas/Error"}}}},"401":{"description":"Missing or invalid authentication.","content":{"application/json":{"schema":{"$ref":"#/components/schemas/Error"}}}},"403":{"description":"Authenticated but not authorized (missing scope, foreign resource).","content":{"application/json":{"schema":{"$ref":"#/components/schemas/Error"}}}},"404":{"description":"Resource not found.","content":{"application/json":{"schema":{"$ref":"#/components/schemas/Error"}}}},"429":{"description":"Rate limited by Meta.","content":{"application/json":{"schema":{"$ref":"#/components/schemas/Error"}}}}}},"delete":{"tags":["WhatsApp"],"summary":"Delete a WhatsApp template (all language variants)","security":[{"ApiKeyAuth":[]}],"parameters":[{"schema":{"type":"string","format":"uuid","example":"019d1b6e-af9e-715b-80e9-6fc1240a585a"},"required":true,"name":"accountId","in":"path"},{"schema":{"type":"string","format":"uuid","example":"019d1b6e-af9e-715b-80e9-6fc1240a585a"},"required":true,"name":"templateId","in":"path"}],"responses":{"200":{"description":"Deletion confirmation.","content":{"application/json":{"schema":{"$ref":"#/components/schemas/DeletedResponse"}}}},"400":{"description":"Invalid request (validation, missing params, bad shape).","content":{"application/json":{"schema":{"$ref":"#/components/schemas/Error"}}}},"401":{"description":"Missing or invalid authentication.","content":{"application/json":{"schema":{"$ref":"#/components/schemas/Error"}}}},"403":{"description":"Authenticated but not authorized (missing scope, foreign resource).","content":{"application/json":{"schema":{"$ref":"#/components/schemas/Error"}}}},"404":{"description":"Resource not found.","content":{"application/json":{"schema":{"$ref":"#/components/schemas/Error"}}}},"429":{"description":"Rate limited by Meta.","content":{"application/json":{"schema":{"$ref":"#/components/schemas/Error"}}}}}}},"/v1/whatsapp/accounts/{accountId}/templates/sync":{"post":{"tags":["WhatsApp"],"summary":"Synchronize templates from Meta API to local cache","security":[{"ApiKeyAuth":[]}],"parameters":[{"schema":{"type":"string","format":"uuid","example":"019d1b6e-af9e-715b-80e9-6fc1240a585a"},"required":true,"name":"accountId","in":"path"}],"responses":{"200":{"description":"Sync result.","content":{"application/json":{"schema":{"$ref":"#/components/schemas/WhatsAppTemplateSyncResponse"}}}},"400":{"description":"Invalid request (validation, missing params, bad shape).","content":{"application/json":{"schema":{"$ref":"#/components/schemas/Error"}}}},"401":{"description":"Missing or invalid authentication.","content":{"application/json":{"schema":{"$ref":"#/components/schemas/Error"}}}},"403":{"description":"Authenticated but not authorized (missing scope, foreign resource).","content":{"application/json":{"schema":{"$ref":"#/components/schemas/Error"}}}},"404":{"description":"Resource not found.","content":{"application/json":{"schema":{"$ref":"#/components/schemas/Error"}}}},"429":{"description":"Rate limited by Meta.","content":{"application/json":{"schema":{"$ref":"#/components/schemas/Error"}}}}}}},"/v1/contact-restrictions":{"get":{"tags":["Contact Restrictions"],"summary":"List contact restrictions","security":[{"ApiKeyAuth":[]}],"parameters":[{"schema":{"type":"integer","minimum":1,"maximum":200,"default":100},"required":false,"name":"limit","in":"query"},{"schema":{"type":"string","format":"uuid"},"required":false,"name":"after","in":"query"},{"schema":{"type":["integer","null"],"minimum":0},"required":false,"name":"offset","in":"query"},{"schema":{"type":"string","enum":["all","marketing"]},"required":false,"name":"type","in":"query"}],"responses":{"200":{"description":"Restrictions list.","content":{"application/json":{"schema":{"$ref":"#/components/schemas/ContactRestrictionList"}}}},"400":{"description":"Invalid request (validation, missing params, bad shape).","content":{"application/json":{"schema":{"$ref":"#/components/schemas/Error"}}}},"401":{"description":"Missing or invalid authentication.","content":{"application/json":{"schema":{"$ref":"#/components/schemas/Error"}}}},"403":{"description":"Authenticated but not authorized (missing scope, foreign resource).","content":{"application/json":{"schema":{"$ref":"#/components/schemas/Error"}}}},"404":{"description":"Resource not found.","content":{"application/json":{"schema":{"$ref":"#/components/schemas/Error"}}}}}},"post":{"tags":["Contact Restrictions"],"summary":"Add a contact restriction","security":[{"ApiKeyAuth":[]}],"requestBody":{"content":{"application/json":{"schema":{"type":"object","properties":{"phoneNumber":{"type":"string","minLength":1,"maxLength":40},"type":{"type":"string","enum":["all","marketing"]},"reason":{"type":"string","maxLength":500}},"required":["phoneNumber"]}}}},"responses":{"201":{"description":"Created.","content":{"application/json":{"schema":{"type":"object","properties":{"object":{"type":"string","enum":["contact_restriction"]},"success":{"type":"boolean"}},"required":["object","success"]}}}},"400":{"description":"Invalid request (validation, missing params, bad shape).","content":{"application/json":{"schema":{"$ref":"#/components/schemas/Error"}}}},"401":{"description":"Missing or invalid authentication.","content":{"application/json":{"schema":{"$ref":"#/components/schemas/Error"}}}},"403":{"description":"Authenticated but not authorized (missing scope, foreign resource).","content":{"application/json":{"schema":{"$ref":"#/components/schemas/Error"}}}},"404":{"description":"Resource not found.","content":{"application/json":{"schema":{"$ref":"#/components/schemas/Error"}}}}}}},"/v1/contact-restrictions/{phoneNumber}":{"get":{"tags":["Contact Restrictions"],"summary":"Get the restriction status for a phone number","security":[{"ApiKeyAuth":[]}],"parameters":[{"schema":{"type":"string"},"required":true,"name":"phoneNumber","in":"path"}],"responses":{"200":{"description":"Current restriction type (null if none).","content":{"application/json":{"schema":{"type":"object","properties":{"type":{"type":["string","null"],"enum":["all","marketing",null]}},"required":["type"]}}}},"400":{"description":"Invalid request (validation, missing params, bad shape).","content":{"application/json":{"schema":{"$ref":"#/components/schemas/Error"}}}},"401":{"description":"Missing or invalid authentication.","content":{"application/json":{"schema":{"$ref":"#/components/schemas/Error"}}}},"403":{"description":"Authenticated but not authorized (missing scope, foreign resource).","content":{"application/json":{"schema":{"$ref":"#/components/schemas/Error"}}}},"404":{"description":"Resource not found.","content":{"application/json":{"schema":{"$ref":"#/components/schemas/Error"}}}}}},"delete":{"tags":["Contact Restrictions"],"summary":"Remove a restriction","security":[{"ApiKeyAuth":[]}],"parameters":[{"schema":{"type":"string"},"required":true,"name":"phoneNumber","in":"path"},{"schema":{"type":"string","enum":["all","marketing"]},"required":false,"name":"type","in":"query"}],"responses":{"200":{"description":"Removed.","content":{"application/json":{"schema":{"type":"object","properties":{"object":{"type":"string","enum":["contact_restriction"]},"success":{"type":"boolean"}},"required":["object","success"]}}}},"400":{"description":"Invalid request (validation, missing params, bad shape).","content":{"application/json":{"schema":{"$ref":"#/components/schemas/Error"}}}},"401":{"description":"Missing or invalid authentication.","content":{"application/json":{"schema":{"$ref":"#/components/schemas/Error"}}}},"403":{"description":"Authenticated but not authorized (missing scope, foreign resource).","content":{"application/json":{"schema":{"$ref":"#/components/schemas/Error"}}}},"404":{"description":"Resource not found.","content":{"application/json":{"schema":{"$ref":"#/components/schemas/Error"}}}}}}},"/v1/members":{"get":{"tags":["Members"],"summary":"List organization members","security":[{"ApiKeyAuth":[]}],"responses":{"200":{"description":"Members in the org.","content":{"application/json":{"schema":{"$ref":"#/components/schemas/MemberList"}}}},"400":{"description":"Invalid request (validation, missing params, bad shape).","content":{"application/json":{"schema":{"$ref":"#/components/schemas/Error"}}}},"401":{"description":"Missing or invalid authentication.","content":{"application/json":{"schema":{"$ref":"#/components/schemas/Error"}}}},"403":{"description":"Authenticated but not authorized (missing scope, foreign resource).","content":{"application/json":{"schema":{"$ref":"#/components/schemas/Error"}}}},"404":{"description":"Resource not found.","content":{"application/json":{"schema":{"$ref":"#/components/schemas/Error"}}}}}}},"/v1/files/download-url":{"get":{"tags":["Files"],"summary":"Get a presigned URL for a storage key owned by the organization","description":"Security: only storage keys beginning with `organizations/{orgId}/` are accepted.","security":[{"ApiKeyAuth":[]}],"parameters":[{"schema":{"type":"string"},"required":true,"name":"key","in":"query"}],"responses":{"200":{"description":"Presigned URL.","content":{"application/json":{"schema":{"type":"object","properties":{"url":{"type":"string","format":"uri"}},"required":["url"]}}}},"400":{"description":"Invalid request (validation, missing params, bad shape).","content":{"application/json":{"schema":{"$ref":"#/components/schemas/Error"}}}},"401":{"description":"Missing or invalid authentication.","content":{"application/json":{"schema":{"$ref":"#/components/schemas/Error"}}}},"403":{"description":"Authenticated but not authorized (missing scope, foreign resource).","content":{"application/json":{"schema":{"$ref":"#/components/schemas/Error"}}}},"404":{"description":"Resource not found.","content":{"application/json":{"schema":{"$ref":"#/components/schemas/Error"}}}}}}},"/v1/files/bulk-external":{"post":{"tags":["Files"],"summary":"Attach external URLs as files in bulk","description":"Inserts attachments where each entry references an external HTTPS URL (no upload to R2). Use `mode: append` to add to existing files for each (recordId, fieldId), or `mode: replace_field` to first remove existing external attachments for those groups (R2-backed files are preserved). Designed for sync jobs that periodically refresh attachments from a third-party feed.","security":[{"ApiKeyAuth":[]}],"requestBody":{"content":{"application/json":{"schema":{"$ref":"#/components/schemas/BulkExternalAttachBody"}}}},"responses":{"200":{"description":"Attached files plus a count of any rows removed in replace mode.","content":{"application/json":{"schema":{"$ref":"#/components/schemas/BulkExternalAttachResponse"}}}},"400":{"description":"Invalid request (validation, missing params, bad shape).","content":{"application/json":{"schema":{"$ref":"#/components/schemas/Error"}}}},"401":{"description":"Missing or invalid authentication.","content":{"application/json":{"schema":{"$ref":"#/components/schemas/Error"}}}},"403":{"description":"Authenticated but not authorized (missing scope, foreign resource).","content":{"application/json":{"schema":{"$ref":"#/components/schemas/Error"}}}},"404":{"description":"Resource not found.","content":{"application/json":{"schema":{"$ref":"#/components/schemas/Error"}}}}}}},"/v1/media/upload-url":{"post":{"tags":["Files"],"summary":"Presigned upload URL for WhatsApp message media","description":"Validates against WhatsApp's media limits (type + size). Use the returned `storageKey` when sending a media message via POST /v1/conversations/:id/messages.","security":[{"ApiKeyAuth":[]}],"requestBody":{"content":{"application/json":{"schema":{"type":"object","properties":{"filename":{"type":"string","minLength":1,"maxLength":255},"contentType":{"type":"string","minLength":1,"maxLength":255},"size":{"type":"integer","exclusiveMinimum":0,"maximum":104857600}},"required":["filename","contentType","size"]}}}},"responses":{"200":{"description":"Upload URL.","content":{"application/json":{"schema":{"type":"object","properties":{"uploadUrl":{"type":"string","format":"uri"},"storageKey":{"type":"string"},"mediaType":{"type":"string"}},"required":["uploadUrl","storageKey","mediaType"]}}}},"400":{"description":"Invalid request (validation, missing params, bad shape).","content":{"application/json":{"schema":{"$ref":"#/components/schemas/Error"}}}},"401":{"description":"Missing or invalid authentication.","content":{"application/json":{"schema":{"$ref":"#/components/schemas/Error"}}}},"403":{"description":"Authenticated but not authorized (missing scope, foreign resource).","content":{"application/json":{"schema":{"$ref":"#/components/schemas/Error"}}}},"404":{"description":"Resource not found.","content":{"application/json":{"schema":{"$ref":"#/components/schemas/Error"}}}}}}},"/v1/webhooks/regenerate-secret":{"post":{"tags":["Webhooks"],"summary":"Regenerate the outbound webhook signing secret","description":"Returns the new signing secret. The previous secret is invalidated immediately — update consumers promptly.","security":[{"ApiKeyAuth":[]}],"responses":{"200":{"description":"New signing secret.","content":{"application/json":{"schema":{"$ref":"#/components/schemas/RegenerateWebhookSecretResponse"}}}},"400":{"description":"Invalid request (validation, missing params, bad shape).","content":{"application/json":{"schema":{"$ref":"#/components/schemas/Error"}}}},"401":{"description":"Missing or invalid authentication.","content":{"application/json":{"schema":{"$ref":"#/components/schemas/Error"}}}},"403":{"description":"Authenticated but not authorized (missing scope, foreign resource).","content":{"application/json":{"schema":{"$ref":"#/components/schemas/Error"}}}},"404":{"description":"Resource not found.","content":{"application/json":{"schema":{"$ref":"#/components/schemas/Error"}}}}}}}},"webhooks":{"message.sent":{"post":{"tags":["Webhooks"],"summary":"An outbound message was sent","description":"Fires for every 1:1 outbound WhatsApp message: a teammate replying from the inbox, the AI agent, an automation, a call to this API, or a message sent from the WhatsApp Business phone app. Read `source` to tell them apart.\n\nCampaign sends are deliberately excluded — a 50,000-recipient campaign would otherwise become 50,000 webhooks. Use the campaign endpoints for those.\n\nInbound messages do NOT arrive here: they come through `whatsapp.message`, in Meta's own format.\n\n**Delivery.** POST, `application/json`, answer within 30 s. Any non-2xx (or a timeout) is retried with growing backoff — 14 attempts, the last about 15 hours after the first; return `410 Gone` to stop retries permanently. At-least-once and unordered — deduplicate on `id` and, when order matters, compare `created`.\n\n**Verify first.** Check `X-Webhook-Signature-256` against the RAW body before parsing it.","parameters":[{"schema":{"type":"string","description":"The event type — same value as the webhook name.","example":"message.sent"},"required":true,"description":"The event type — same value as the webhook name.","name":"X-BlueMarlin-Event","in":"header"},{"schema":{"type":"string","format":"date-time","description":"When this delivery was queued (ISO 8601)."},"required":true,"description":"When this delivery was queued (ISO 8601).","name":"X-BlueMarlin-Timestamp","in":"header"},{"schema":{"type":"string","description":"1 on the first try, then 2, 3… on retries.","example":"1"},"required":true,"description":"1 on the first try, then 2, 3… on retries.","name":"X-BlueMarlin-Delivery-Attempt","in":"header"},{"schema":{"type":"string","description":"`sha256=<hex>`: HMAC-SHA256 of the raw body with your webhook secret (enabling the webhook creates one).","example":"sha256=5d41402abc4b2a76b9719d911017c592…"},"required":true,"description":"`sha256=<hex>`: HMAC-SHA256 of the raw body with your webhook secret (enabling the webhook creates one).","name":"X-Webhook-Signature-256","in":"header"},{"schema":{"type":"string","enum":["BlueMarlin-Webhook/1.0"]},"required":true,"name":"User-Agent","in":"header"}],"requestBody":{"content":{"application/json":{"schema":{"allOf":[{"$ref":"#/components/schemas/WebhookEventEnvelope"},{"type":"object","properties":{"type":{"type":"string","enum":["message.sent"]},"data":{"$ref":"#/components/schemas/MessageSentEventData"}}}]}}}},"responses":{"200":{"description":"Any 2xx acknowledges the event. Anything else is retried."}}}},"contact.unsubscribed":{"post":{"tags":["Webhooks"],"summary":"A contact was blocked or opted out","description":"A do-not-contact restriction was created or widened — the contact replied STOP, tapped an opt-out button, or a member blocked them.\n\nYou do not need this to stay compliant: BlueMarlin enforces restrictions server-side on every send path, including the Meta-compatible proxy, and refuses with HTTP 403 (`contact_blocked` / `contact_opted_out`). The event is for keeping your own records in step.\n\n**Delivery.** POST, `application/json`, answer within 30 s. Any non-2xx (or a timeout) is retried with growing backoff — 14 attempts, the last about 15 hours after the first; return `410 Gone` to stop retries permanently. At-least-once and unordered — deduplicate on `id` and, when order matters, compare `created`.\n\n**Verify first.** Check `X-Webhook-Signature-256` against the RAW body before parsing it.","parameters":[{"schema":{"type":"string","description":"The event type — same value as the webhook name.","example":"message.sent"},"required":true,"description":"The event type — same value as the webhook name.","name":"X-BlueMarlin-Event","in":"header"},{"schema":{"type":"string","format":"date-time","description":"When this delivery was queued (ISO 8601)."},"required":true,"description":"When this delivery was queued (ISO 8601).","name":"X-BlueMarlin-Timestamp","in":"header"},{"schema":{"type":"string","description":"1 on the first try, then 2, 3… on retries.","example":"1"},"required":true,"description":"1 on the first try, then 2, 3… on retries.","name":"X-BlueMarlin-Delivery-Attempt","in":"header"},{"schema":{"type":"string","description":"`sha256=<hex>`: HMAC-SHA256 of the raw body with your webhook secret (enabling the webhook creates one).","example":"sha256=5d41402abc4b2a76b9719d911017c592…"},"required":true,"description":"`sha256=<hex>`: HMAC-SHA256 of the raw body with your webhook secret (enabling the webhook creates one).","name":"X-Webhook-Signature-256","in":"header"},{"schema":{"type":"string","enum":["BlueMarlin-Webhook/1.0"]},"required":true,"name":"User-Agent","in":"header"}],"requestBody":{"content":{"application/json":{"schema":{"allOf":[{"$ref":"#/components/schemas/WebhookEventEnvelope"},{"type":"object","properties":{"type":{"type":"string","enum":["contact.unsubscribed"]},"data":{"$ref":"#/components/schemas/ContactRestrictionEventData"}}}]}}}},"responses":{"200":{"description":"Any 2xx acknowledges the event. Anything else is retried."}}}},"contact.resubscribed":{"post":{"tags":["Webhooks"],"summary":"A block or opt-out was lifted","description":"The counterpart to `contact.unsubscribed`. It exists because a lifted restriction leaves no row behind: a client polling `GET /v1/contact-restrictions` cannot tell 'still blocked' from 'unblocked', so without this event a local copy drifts and never recovers.\n\n**Delivery.** POST, `application/json`, answer within 30 s. Any non-2xx (or a timeout) is retried with growing backoff — 14 attempts, the last about 15 hours after the first; return `410 Gone` to stop retries permanently. At-least-once and unordered — deduplicate on `id` and, when order matters, compare `created`.\n\n**Verify first.** Check `X-Webhook-Signature-256` against the RAW body before parsing it.","parameters":[{"schema":{"type":"string","description":"The event type — same value as the webhook name.","example":"message.sent"},"required":true,"description":"The event type — same value as the webhook name.","name":"X-BlueMarlin-Event","in":"header"},{"schema":{"type":"string","format":"date-time","description":"When this delivery was queued (ISO 8601)."},"required":true,"description":"When this delivery was queued (ISO 8601).","name":"X-BlueMarlin-Timestamp","in":"header"},{"schema":{"type":"string","description":"1 on the first try, then 2, 3… on retries.","example":"1"},"required":true,"description":"1 on the first try, then 2, 3… on retries.","name":"X-BlueMarlin-Delivery-Attempt","in":"header"},{"schema":{"type":"string","description":"`sha256=<hex>`: HMAC-SHA256 of the raw body with your webhook secret (enabling the webhook creates one).","example":"sha256=5d41402abc4b2a76b9719d911017c592…"},"required":true,"description":"`sha256=<hex>`: HMAC-SHA256 of the raw body with your webhook secret (enabling the webhook creates one).","name":"X-Webhook-Signature-256","in":"header"},{"schema":{"type":"string","enum":["BlueMarlin-Webhook/1.0"]},"required":true,"name":"User-Agent","in":"header"}],"requestBody":{"content":{"application/json":{"schema":{"allOf":[{"$ref":"#/components/schemas/WebhookEventEnvelope"},{"type":"object","properties":{"type":{"type":"string","enum":["contact.resubscribed"]},"data":{"$ref":"#/components/schemas/ContactRestrictionEventData"}}}]}}}},"responses":{"200":{"description":"Any 2xx acknowledges the event. Anything else is retried."}}}},"whatsapp.message":{"post":{"tags":["Webhooks"],"summary":"Inbound WhatsApp message (relayed from Meta)","description":"Meta's own webhook payload, forwarded **byte for byte**. The body is exactly what the WhatsApp Cloud API would have sent you, so existing Cloud API code works unchanged and the top-level `object` is `whatsapp_business_account`. See Meta's webhook reference for the field catalogue.\n\nCovers every inbound message, including button taps and list replies.\n\n**Delivery.** POST, `application/json`, answer within 30 s. Any non-2xx (or a timeout) is retried with growing backoff — 14 attempts, the last about 15 hours after the first; return `410 Gone` to stop retries permanently. At-least-once and unordered — deduplicate on `id` and, when order matters, compare `created`.\n\n**Verify first.** Check `X-Webhook-Signature-256` against the RAW body before parsing it.","parameters":[{"schema":{"type":"string","description":"The event type — same value as the webhook name.","example":"message.sent"},"required":true,"description":"The event type — same value as the webhook name.","name":"X-BlueMarlin-Event","in":"header"},{"schema":{"type":"string","format":"date-time","description":"When this delivery was queued (ISO 8601)."},"required":true,"description":"When this delivery was queued (ISO 8601).","name":"X-BlueMarlin-Timestamp","in":"header"},{"schema":{"type":"string","description":"1 on the first try, then 2, 3… on retries.","example":"1"},"required":true,"description":"1 on the first try, then 2, 3… on retries.","name":"X-BlueMarlin-Delivery-Attempt","in":"header"},{"schema":{"type":"string","description":"`sha256=<hex>`: HMAC-SHA256 of the raw body with your webhook secret (enabling the webhook creates one).","example":"sha256=5d41402abc4b2a76b9719d911017c592…"},"required":true,"description":"`sha256=<hex>`: HMAC-SHA256 of the raw body with your webhook secret (enabling the webhook creates one).","name":"X-Webhook-Signature-256","in":"header"},{"schema":{"type":"string","enum":["BlueMarlin-Webhook/1.0"]},"required":true,"name":"User-Agent","in":"header"}],"requestBody":{"content":{"application/json":{"schema":{"type":"object","properties":{"object":{"type":"string","enum":["whatsapp_business_account"]},"entry":{"type":"array","items":{"type":"object","additionalProperties":{}}}},"required":["object","entry"],"description":"Meta Cloud API webhook payload, verbatim."}}}},"responses":{"200":{"description":"Any 2xx acknowledges the event. Anything else is retried."}}}},"whatsapp.status":{"post":{"tags":["Webhooks"],"summary":"Message delivery receipt (relayed from Meta)","description":"Meta's own webhook payload, forwarded **byte for byte**. The body is exactly what the WhatsApp Cloud API would have sent you, so existing Cloud API code works unchanged and the top-level `object` is `whatsapp_business_account`. See Meta's webhook reference for the field catalogue.\n\nBy far the highest-volume event — subscribe to it only if you actually process receipts.\n\n**Delivery.** POST, `application/json`, answer within 30 s. Any non-2xx (or a timeout) is retried with growing backoff — 14 attempts, the last about 15 hours after the first; return `410 Gone` to stop retries permanently. At-least-once and unordered — deduplicate on `id` and, when order matters, compare `created`.\n\n**Verify first.** Check `X-Webhook-Signature-256` against the RAW body before parsing it.","parameters":[{"schema":{"type":"string","description":"The event type — same value as the webhook name.","example":"message.sent"},"required":true,"description":"The event type — same value as the webhook name.","name":"X-BlueMarlin-Event","in":"header"},{"schema":{"type":"string","format":"date-time","description":"When this delivery was queued (ISO 8601)."},"required":true,"description":"When this delivery was queued (ISO 8601).","name":"X-BlueMarlin-Timestamp","in":"header"},{"schema":{"type":"string","description":"1 on the first try, then 2, 3… on retries.","example":"1"},"required":true,"description":"1 on the first try, then 2, 3… on retries.","name":"X-BlueMarlin-Delivery-Attempt","in":"header"},{"schema":{"type":"string","description":"`sha256=<hex>`: HMAC-SHA256 of the raw body with your webhook secret (enabling the webhook creates one).","example":"sha256=5d41402abc4b2a76b9719d911017c592…"},"required":true,"description":"`sha256=<hex>`: HMAC-SHA256 of the raw body with your webhook secret (enabling the webhook creates one).","name":"X-Webhook-Signature-256","in":"header"},{"schema":{"type":"string","enum":["BlueMarlin-Webhook/1.0"]},"required":true,"name":"User-Agent","in":"header"}],"requestBody":{"content":{"application/json":{"schema":{"type":"object","properties":{"object":{"type":"string","enum":["whatsapp_business_account"]},"entry":{"type":"array","items":{"type":"object","additionalProperties":{}}}},"required":["object","entry"],"description":"Meta Cloud API webhook payload, verbatim."}}}},"responses":{"200":{"description":"Any 2xx acknowledges the event. Anything else is retried."}}}},"whatsapp.template":{"post":{"tags":["Webhooks"],"summary":"Template status change (relayed from Meta)","description":"Meta's own webhook payload, forwarded **byte for byte**. The body is exactly what the WhatsApp Cloud API would have sent you, so existing Cloud API code works unchanged and the top-level `object` is `whatsapp_business_account`. See Meta's webhook reference for the field catalogue.\n\nCarries all four of Meta's template webhooks; read `entry[].changes[].field` to tell them apart. These are WABA-scoped, so there is no `metadata.phone_number_id`.\n\n**Delivery.** POST, `application/json`, answer within 30 s. Any non-2xx (or a timeout) is retried with growing backoff — 14 attempts, the last about 15 hours after the first; return `410 Gone` to stop retries permanently. At-least-once and unordered — deduplicate on `id` and, when order matters, compare `created`.\n\n**Verify first.** Check `X-Webhook-Signature-256` against the RAW body before parsing it.","parameters":[{"schema":{"type":"string","description":"The event type — same value as the webhook name.","example":"message.sent"},"required":true,"description":"The event type — same value as the webhook name.","name":"X-BlueMarlin-Event","in":"header"},{"schema":{"type":"string","format":"date-time","description":"When this delivery was queued (ISO 8601)."},"required":true,"description":"When this delivery was queued (ISO 8601).","name":"X-BlueMarlin-Timestamp","in":"header"},{"schema":{"type":"string","description":"1 on the first try, then 2, 3… on retries.","example":"1"},"required":true,"description":"1 on the first try, then 2, 3… on retries.","name":"X-BlueMarlin-Delivery-Attempt","in":"header"},{"schema":{"type":"string","description":"`sha256=<hex>`: HMAC-SHA256 of the raw body with your webhook secret (enabling the webhook creates one).","example":"sha256=5d41402abc4b2a76b9719d911017c592…"},"required":true,"description":"`sha256=<hex>`: HMAC-SHA256 of the raw body with your webhook secret (enabling the webhook creates one).","name":"X-Webhook-Signature-256","in":"header"},{"schema":{"type":"string","enum":["BlueMarlin-Webhook/1.0"]},"required":true,"name":"User-Agent","in":"header"}],"requestBody":{"content":{"application/json":{"schema":{"type":"object","properties":{"object":{"type":"string","enum":["whatsapp_business_account"]},"entry":{"type":"array","items":{"type":"object","additionalProperties":{}}}},"required":["object","entry"],"description":"Meta Cloud API webhook payload, verbatim."}}}},"responses":{"200":{"description":"Any 2xx acknowledges the event. Anything else is retried."}}}}}}