{"openapi":"3.0.3","info":{"title":"Blooio API v2","description":"RESTful API for iMessage automation. Send messages, manage contacts, groups, and webhooks.","version":"2.0.0","contact":{"name":"Blooio Support","url":"https://blooio.com","email":"support@blooio.com"}},"servers":[{"url":"https://backend.blooio.com/v2/api","description":"Production"}],"security":[{"BearerAuth":[]}],"tags":[{"name":"Account","description":"Authentication and account information"},{"name":"Numbers","description":"Manage phone numbers linked to your account"},{"name":"Contact Card","description":"Manage and share your iMessage contact card (Name & Photo)"},{"name":"Contacts","description":"Manage contacts (phone numbers and emails)"},{"name":"Groups","description":"Manage contact groups"},{"name":"Group Members","description":"Manage group membership"},{"name":"Webhooks","description":"Manage webhook subscriptions"},{"name":"Webhook Logs","description":"View and replay webhook deliveries"},{"name":"Chats","description":"View conversations and messages"},{"name":"Messages","description":"View individual messages and their status"},{"name":"Typing Indicators","description":"Control typing indicators for conversations"},{"name":"Read Receipts","description":"Mark conversations as read"},{"name":"Reactions","description":"Add or remove reactions to messages. Supports classic iMessage tapbacks (love, like, dislike, laugh, emphasize, question) and emoji reactions (e.g. +😂, -😂). Emoji reactions require macOS 14 (Sonoma) or later on the device."},{"name":"Polls","description":"Send native iMessage polls and retrieve poll results with vote counts. Poll events are delivered via separate webhook event types (poll.received, poll.created, poll.voted) and require webhook_type 'poll' or 'all'."},{"name":"Chat Background","description":"Set, get, and remove conversation backgrounds"},{"name":"Location","description":"FindMy contact location tracking"},{"name":"FaceTime","description":"Initiate FaceTime calls"},{"name":"Phone Numbers","description":"Phone number validation, formatting, and NANPA geocoding. Requires an Enterprise plan (Dedicated Enterprise)."}],"paths":{"/me":{"get":{"tags":["Account"],"summary":"Get current authentication context","description":"Returns details about the authenticated API key or dashboard user, including organization info, devices, and usage statistics.","operationId":"getMe","responses":{"200":{"description":"Authentication context","content":{"application/json":{"schema":{"$ref":"#/components/schemas/MeResponse"}}}},"401":{"$ref":"#/components/responses/Unauthorized"}}}},"/me/numbers":{"get":{"tags":["Numbers"],"summary":"List phone numbers","description":"List all phone numbers bound to this API key with their availability status. Use the returned phone numbers as the `:number` path parameter for other `/me/numbers/` endpoints.","operationId":"listNumbers","responses":{"200":{"description":"List of phone numbers","content":{"application/json":{"schema":{"type":"object","properties":{"numbers":{"type":"array","items":{"type":"object","properties":{"phone_number":{"type":"string","example":"+15551234567"},"is_active":{"type":"boolean"},"last_active":{"type":"string","format":"date-time","nullable":true}}}}}}}}},"401":{"$ref":"#/components/responses/Unauthorized"}}}},"/me/numbers/{number}/contact-card":{"get":{"tags":["Contact Card"],"summary":"Get contact card (Coming Soon)","description":"⚠️ **COMING SOON** - This endpoint is temporarily disabled while we stabilize this feature.\n\nGet the personal contact card (Name & Photo) for the specified phone number. This is the identity that gets shared with contacts in iMessage.","operationId":"getMyContactCard","parameters":[{"name":"number","in":"path","required":true,"description":"E.164 phone number (URL-encoded, e.g., %2B15551234567)","schema":{"type":"string"}}],"responses":{"200":{"description":"Contact card data","content":{"application/json":{"schema":{"type":"object","properties":{"phone_number":{"type":"string"},"first_name":{"type":"string","nullable":true},"last_name":{"type":"string","nullable":true},"name":{"type":"string","nullable":true,"description":"Display name"},"avatar":{"type":"string","nullable":true,"description":"Base64-encoded JPEG/PNG image"},"has_wallpaper":{"type":"boolean"},"sharing":{"type":"object","properties":{"enabled":{"type":"boolean","description":"Whether Name & Photo sharing is enabled"},"audience":{"type":"integer","description":"0 = Contacts Only, 1 = Always Ask"},"name_format":{"type":"integer","description":"0 = First & Last, 1 = First Only"}}}}}}}},"400":{"$ref":"#/components/responses/BadRequest"},"401":{"$ref":"#/components/responses/Unauthorized"},"403":{"description":"Phone number not accessible with this API key"},"404":{"$ref":"#/components/responses/NotFound"}}},"put":{"tags":["Contact Card"],"summary":"Update contact card","description":"Update the personal contact card (Name & Photo) for the specified phone number. All fields are optional — only provided fields are updated.","operationId":"updateMyContactCard","parameters":[{"name":"number","in":"path","required":true,"description":"E.164 phone number (URL-encoded, e.g., %2B15551234567)","schema":{"type":"string"}}],"requestBody":{"required":true,"content":{"application/json":{"schema":{"type":"object","properties":{"first_name":{"type":"string","description":"First name"},"last_name":{"type":"string","description":"Last name"},"avatar":{"type":"string","description":"Profile photo as base64-encoded JPEG/PNG"},"sharing":{"type":"object","properties":{"enabled":{"type":"boolean","description":"Enable/disable Name & Photo sharing"},"audience":{"type":"integer","description":"0 = Contacts Only, 1 = Always Ask"},"name_format":{"type":"integer","description":"0 = First & Last, 1 = First Only"}}}}}}}},"responses":{"200":{"description":"Contact card updated","content":{"application/json":{"schema":{"type":"object","properties":{"success":{"type":"boolean","example":true},"phone_number":{"type":"string"},"first_name":{"type":"string","nullable":true},"last_name":{"type":"string","nullable":true}}}}}},"400":{"$ref":"#/components/responses/BadRequest"},"401":{"$ref":"#/components/responses/Unauthorized"},"403":{"description":"Phone number not accessible with this API key"},"404":{"$ref":"#/components/responses/NotFound"},"502":{"description":"Temporary communication error"}}}},"/contacts":{"get":{"tags":["Contacts"],"summary":"List contacts","description":"List all contacts for the organization with optional search and pagination.","operationId":"listContacts","parameters":[{"$ref":"#/components/parameters/LimitParam"},{"$ref":"#/components/parameters/OffsetParam"},{"name":"q","in":"query","description":"Search query (matches identifier or name)","schema":{"type":"string"}},{"name":"sort","in":"query","description":"Sort order","schema":{"type":"string","enum":["recent","oldest","name_asc","name_desc"],"default":"recent"}}],"responses":{"200":{"description":"List of contacts","content":{"application/json":{"schema":{"type":"object","properties":{"contacts":{"type":"array","items":{"$ref":"#/components/schemas/Contact"}},"pagination":{"$ref":"#/components/schemas/Pagination"}}}}}},"401":{"$ref":"#/components/responses/Unauthorized"}}},"post":{"tags":["Contacts"],"summary":"Create a contact","description":"Create a new contact with a phone number (E.164 format) or email address.","operationId":"createContact","requestBody":{"required":true,"content":{"application/json":{"schema":{"type":"object","required":["identifier"],"properties":{"identifier":{"type":"string","description":"Phone number (E.164 format, e.g., +15551234567) or email address","example":"+15551234567"},"name":{"type":"string","description":"Display name for the contact","example":"John Doe"}}}}}},"responses":{"201":{"description":"Contact created","content":{"application/json":{"schema":{"$ref":"#/components/schemas/Contact"}}}},"400":{"$ref":"#/components/responses/BadRequest"},"401":{"$ref":"#/components/responses/Unauthorized"},"409":{"description":"Contact already exists","content":{"application/json":{"schema":{"$ref":"#/components/schemas/Error"}}}}}}},"/contacts/{contactId}":{"get":{"tags":["Contacts"],"summary":"Get a contact","description":"Get details for a specific contact by phone number or email.","operationId":"getContact","parameters":[{"$ref":"#/components/parameters/ContactIdParam"}],"responses":{"200":{"description":"Contact details","content":{"application/json":{"schema":{"$ref":"#/components/schemas/Contact"}}}},"400":{"$ref":"#/components/responses/BadRequest"},"401":{"$ref":"#/components/responses/Unauthorized"},"404":{"$ref":"#/components/responses/NotFound"}}},"patch":{"tags":["Contacts"],"summary":"Update a contact","description":"Update a contact's name.","operationId":"updateContact","parameters":[{"$ref":"#/components/parameters/ContactIdParam"}],"requestBody":{"required":true,"content":{"application/json":{"schema":{"type":"object","properties":{"name":{"type":"string","nullable":true,"description":"New display name (null to clear)","example":"Jane Doe"}}}}}},"responses":{"200":{"description":"Contact updated","content":{"application/json":{"schema":{"$ref":"#/components/schemas/Contact"}}}},"400":{"$ref":"#/components/responses/BadRequest"},"401":{"$ref":"#/components/responses/Unauthorized"},"404":{"$ref":"#/components/responses/NotFound"}}},"delete":{"tags":["Contacts"],"summary":"Delete a contact","description":"Soft-delete a contact.","operationId":"deleteContact","parameters":[{"$ref":"#/components/parameters/ContactIdParam"}],"responses":{"200":{"description":"Contact deleted","content":{"application/json":{"schema":{"$ref":"#/components/schemas/DeleteResponse"}}}},"400":{"$ref":"#/components/responses/BadRequest"},"401":{"$ref":"#/components/responses/Unauthorized"},"404":{"$ref":"#/components/responses/NotFound"}}}},"/contacts/{contactId}/capabilities":{"get":{"tags":["Contacts"],"summary":"Check contact capabilities","description":"Check if a contact supports iMessage and/or SMS.","operationId":"getContactCapabilities","parameters":[{"$ref":"#/components/parameters/ContactIdParam"}],"responses":{"200":{"description":"Contact capabilities","content":{"application/json":{"schema":{"type":"object","properties":{"contact":{"type":"string","description":"Normalized contact identifier"},"type":{"type":"string","enum":["phone","email"]},"capabilities":{"type":"object","properties":{"imessage":{"type":"boolean","description":"Whether iMessage is available"},"sms":{"type":"boolean","description":"Whether SMS is available (phone only)"},"facetime":{"type":"boolean","description":"Whether FaceTime is available"}}},"last_checked":{"type":"integer","format":"int64","description":"Timestamp when capabilities were checked"}}}}}},"400":{"$ref":"#/components/responses/BadRequest"},"401":{"$ref":"#/components/responses/Unauthorized"},"503":{"description":"No active number available to check capabilities","content":{"application/json":{"schema":{"$ref":"#/components/schemas/Error"}}}}}}},"/contacts/{contactId}/tags":{"get":{"tags":["Contacts"],"summary":"List contact tags","description":"List all tags assigned to a contact.","operationId":"listContactTags","parameters":[{"$ref":"#/components/parameters/ContactIdParam"}],"responses":{"200":{"description":"List of tags","content":{"application/json":{"schema":{"type":"object","properties":{"tags":{"type":"array","items":{"type":"object","properties":{"tag":{"type":"string","description":"The tag value","example":"vip"},"created_at":{"type":"integer","format":"int64","description":"Timestamp when the tag was added (ms since epoch)"}}}}}}}}},"400":{"$ref":"#/components/responses/BadRequest"},"401":{"$ref":"#/components/responses/Unauthorized"},"404":{"$ref":"#/components/responses/NotFound"}}},"post":{"tags":["Contacts"],"summary":"Add tags to a contact","description":"Add one or more tags to a contact. If a tag already exists on the contact, it is re-activated (idempotent). Tags are free-form strings.","operationId":"addContactTags","parameters":[{"$ref":"#/components/parameters/ContactIdParam"}],"requestBody":{"required":true,"content":{"application/json":{"schema":{"type":"object","required":["tags"],"properties":{"tags":{"type":"array","items":{"type":"string"},"description":"Tags to add","example":["vip","priority"]}}}}}},"responses":{"201":{"description":"Tags added","content":{"application/json":{"schema":{"type":"object","properties":{"success":{"type":"boolean","example":true},"tags_added":{"type":"array","items":{"type":"string"},"description":"Tags that were added","example":["vip","priority"]}}}}}},"400":{"$ref":"#/components/responses/BadRequest"},"401":{"$ref":"#/components/responses/Unauthorized"},"404":{"$ref":"#/components/responses/NotFound"}}}},"/contacts/{contactId}/tags/{tag}":{"delete":{"tags":["Contacts"],"summary":"Remove a tag from a contact","description":"Remove a specific tag from a contact. The tag is soft-deleted and can be re-added later.","operationId":"removeContactTag","parameters":[{"$ref":"#/components/parameters/ContactIdParam"},{"name":"tag","in":"path","required":true,"description":"The tag to remove (URL-encode if it contains special characters)","schema":{"type":"string"},"example":"vip"}],"responses":{"200":{"description":"Tag removed","content":{"application/json":{"schema":{"$ref":"#/components/schemas/DeleteResponse"}}}},"400":{"$ref":"#/components/responses/BadRequest"},"401":{"$ref":"#/components/responses/Unauthorized"},"404":{"$ref":"#/components/responses/NotFound"}}}},"/location/contacts":{"get":{"tags":["Location"],"summary":"List contact locations","description":"Returns cached FindMy contact locations available through your blooio account. Each entry includes the contact's handle (phone/email), coordinates, and last update time.","operationId":"listLocationContacts","responses":{"200":{"description":"List of contact locations","content":{"application/json":{"schema":{"type":"object","properties":{"friends":{"type":"array","items":{"$ref":"#/components/schemas/ContactLocation"}}}}}}},"401":{"$ref":"#/components/responses/Unauthorized"},"404":{"description":"No active number is available for this API key"}}}},"/location/contacts/{handle}":{"get":{"tags":["Location"],"summary":"Get contact location","description":"Returns the cached location for a specific contact identified by phone number or email.","operationId":"getLocationContact","parameters":[{"name":"handle","in":"path","required":true,"schema":{"type":"string"},"description":"Contact's phone number (E.164) or email address"}],"responses":{"200":{"description":"Contact location data","content":{"application/json":{"schema":{"$ref":"#/components/schemas/ContactLocation"}}}},"400":{"$ref":"#/components/responses/BadRequest"},"401":{"$ref":"#/components/responses/Unauthorized"},"404":{"description":"No location found for this contact"}}}},"/location/contacts/refresh":{"post":{"tags":["Location"],"summary":"Refresh contact locations","description":"Triggers a refresh of cached FindMy contact locations. Updated results may take 15-20 seconds to appear.","operationId":"refreshLocationContacts","responses":{"200":{"description":"Refresh triggered, returns updated locations","content":{"application/json":{"schema":{"type":"object","properties":{"success":{"type":"boolean"},"friends":{"type":"array","items":{"$ref":"#/components/schemas/ContactLocation"}}}}}}},"401":{"$ref":"#/components/responses/Unauthorized"}}}},"/facetime/calls":{"post":{"tags":["FaceTime"],"summary":"Initiate a FaceTime call (Coming Soon)","description":"**Coming Soon** -- This endpoint is temporarily disabled while we stabilize the FaceTime call flow.\n\nInitiates a FaceTime call to the specified phone number or email address. Returns a shareable FaceTime link that anyone can use to join the call. The call will ring the contact and auto-admit the first person who joins via the link.","operationId":"callFaceTime","requestBody":{"required":true,"content":{"application/json":{"schema":{"type":"object","required":["handle"],"properties":{"handle":{"type":"string","description":"Phone number (E.164) or email address to call","example":"+15551234567"}}}}}},"responses":{"200":{"description":"FaceTime call initiated","content":{"application/json":{"schema":{"type":"object","properties":{"success":{"type":"boolean"},"link":{"type":"string","description":"Shareable FaceTime link","example":"https://facetime.apple.com/join#v=1&p=xxx"},"handle":{"type":"string","description":"The handle that was called"}}}}}},"400":{"$ref":"#/components/responses/BadRequest"},"401":{"$ref":"#/components/responses/Unauthorized"}}}},"/groups":{"get":{"tags":["Groups"],"summary":"List groups","description":"List all groups for the organization with optional search and pagination.","operationId":"listGroups","parameters":[{"$ref":"#/components/parameters/LimitParam"},{"$ref":"#/components/parameters/OffsetParam"},{"name":"q","in":"query","description":"Search query (matches group name)","schema":{"type":"string"}},{"name":"sort","in":"query","description":"Sort order","schema":{"type":"string","enum":["recent","oldest","name_asc","name_desc"],"default":"recent"}}],"responses":{"200":{"description":"List of groups","content":{"application/json":{"schema":{"type":"object","properties":{"groups":{"type":"array","items":{"$ref":"#/components/schemas/Group"}},"pagination":{"$ref":"#/components/schemas/Pagination"}}}}}},"401":{"$ref":"#/components/responses/Unauthorized"}}},"post":{"tags":["Groups"],"summary":"Create a group","description":"Create a new group. There are two modes:\n\n**1. Link to existing iMessage chat:** Provide `chat_guid` to join an existing group chat that was created outside the API. The `members` list records who is in the group but does NOT add them to the linked iMessage chat. Multiple groups can have the same participants if they have different `chat_guid`s.\n\n**2. Create new group:** Omit `chat_guid` to create a new group. When you send the first message, a new iMessage chat will be created. Note: iMessage only allows one chat per unique participant set when created via API.","operationId":"createGroup","requestBody":{"required":true,"content":{"application/json":{"schema":{"type":"object","required":["name"],"properties":{"name":{"type":"string","description":"Group name (max 255 characters)","example":"Sales Team"},"chat_guid":{"type":"string","description":"BlueBubbles chat GUID to link this group to an existing iMessage chat. Use this to join groups created elsewhere. You can get this from the BlueBubbles API or from inbound message webhooks.","example":"iMessage;+;chat123456789"},"members":{"type":"array","items":{"type":"string"},"description":"Phone numbers or emails of contacts in the group. When linking via chat_guid, this is for record-keeping only (members are not added to the linked iMessage chat).","example":["+15551234567","+15559876543"]}}}}}},"responses":{"201":{"description":"Group created","content":{"application/json":{"schema":{"allOf":[{"$ref":"#/components/schemas/Group"},{"type":"object","properties":{"added_members":{"type":"array","items":{"type":"string"},"description":"List of member identifiers that were added to the group"},"created_contacts":{"type":"array","items":{"type":"string"},"description":"List of contacts that were auto-created"}}}]}}}},"400":{"$ref":"#/components/responses/BadRequest"},"401":{"$ref":"#/components/responses/Unauthorized"},"409":{"description":"Conflict. Either: (1) A group with this chat_guid already exists, or (2) A group with the same participants already exists on this allocation.","content":{"application/json":{"schema":{"$ref":"#/components/schemas/Error"}}}}}}},"/groups/{groupId}":{"get":{"tags":["Groups"],"summary":"Get a group","description":"Get details for a specific group.","operationId":"getGroup","parameters":[{"$ref":"#/components/parameters/GroupIdParam"}],"responses":{"200":{"description":"Group details","content":{"application/json":{"schema":{"$ref":"#/components/schemas/Group"}}}},"401":{"$ref":"#/components/responses/Unauthorized"},"404":{"$ref":"#/components/responses/NotFound"}}},"patch":{"tags":["Groups"],"summary":"Update a group","description":"Update a group's name. If the group has a linked `chat_guid`, the display name will also be updated in the linked iMessage chat. Note: iMessage only allows one chat per unique participant set, so renaming simply changes the display name on the existing chat thread.","operationId":"updateGroup","parameters":[{"$ref":"#/components/parameters/GroupIdParam"}],"requestBody":{"required":true,"content":{"application/json":{"schema":{"type":"object","properties":{"name":{"type":"string","description":"New group name","example":"Marketing Team"}}}}}},"responses":{"200":{"description":"Group updated successfully.","content":{"application/json":{"schema":{"allOf":[{"$ref":"#/components/schemas/Group"},{"type":"object","properties":{"device_sync":{"$ref":"#/components/schemas/DeviceSyncResult"}}}]}}}},"400":{"$ref":"#/components/responses/BadRequest"},"401":{"$ref":"#/components/responses/Unauthorized"},"404":{"$ref":"#/components/responses/NotFound"}}},"delete":{"tags":["Groups"],"summary":"Delete a group","description":"Soft-delete a group. Members are automatically removed. If the group is linked to an existing iMessage chat, the number also leaves that chat.","operationId":"deleteGroup","parameters":[{"$ref":"#/components/parameters/GroupIdParam"}],"responses":{"200":{"description":"Group deleted","content":{"application/json":{"schema":{"type":"object","properties":{"success":{"type":"boolean"},"deleted_at":{"type":"integer","format":"int64"}}}}}},"401":{"$ref":"#/components/responses/Unauthorized"},"404":{"$ref":"#/components/responses/NotFound"},"502":{"description":"Failed to leave linked iMessage chat","content":{"application/json":{"schema":{"$ref":"#/components/schemas/Error"}}}},"503":{"description":"No active number available to leave group chat","content":{"application/json":{"schema":{"$ref":"#/components/schemas/Error"}}}}}}},"/groups/{groupId}/members":{"get":{"tags":["Group Members"],"summary":"List group members","description":"List all members of a group.","operationId":"listGroupMembers","parameters":[{"$ref":"#/components/parameters/GroupIdParam"},{"$ref":"#/components/parameters/LimitParam"},{"$ref":"#/components/parameters/OffsetParam"}],"responses":{"200":{"description":"List of group members","content":{"application/json":{"schema":{"type":"object","properties":{"group_id":{"type":"string","description":"The group ID"},"group_name":{"type":"string","nullable":true,"description":"The group name"},"icon_url":{"type":"string","nullable":true,"description":"URL of the group icon/photo"},"members":{"type":"array","items":{"$ref":"#/components/schemas/GroupMember"}},"pagination":{"$ref":"#/components/schemas/Pagination"}}}}}},"401":{"$ref":"#/components/responses/Unauthorized"},"404":{"$ref":"#/components/responses/NotFound"}}},"post":{"tags":["Group Members"],"summary":"Add a member to a group (Coming Soon)","description":"⚠️ **COMING SOON** - This endpoint is temporarily disabled while we stabilize this feature.\n\nAdd an existing contact to a group. If the group is linked to an existing iMessage chat, also adds the participant to that chat.","operationId":"addGroupMember","parameters":[{"$ref":"#/components/parameters/GroupIdParam"}],"requestBody":{"required":true,"content":{"application/json":{"schema":{"type":"object","required":["contact_id"],"properties":{"contact_id":{"type":"string","description":"Contact identifier (phone number or email)","example":"+15551234567"}}}}}},"responses":{"200":{"description":"Contact is already a member","content":{"application/json":{"schema":{"type":"object","properties":{"message":{"type":"string"},"member":{"$ref":"#/components/schemas/GroupMember"}}}}}},"201":{"description":"Member added","content":{"application/json":{"schema":{"type":"object","properties":{"member":{"$ref":"#/components/schemas/GroupMember"},"contact_created":{"type":"boolean","description":"Whether a new contact was created for this member"}}}}}},"400":{"$ref":"#/components/responses/BadRequest"},"401":{"$ref":"#/components/responses/Unauthorized"},"404":{"$ref":"#/components/responses/NotFound"},"501":{"description":"Coming soon - endpoint temporarily disabled","content":{"application/json":{"schema":{"type":"object","properties":{"error":{"type":"string","example":"Coming soon"},"message":{"type":"string","example":"Adding group members is coming soon"}}}}}}}}},"/groups/{groupId}/members/{contactId}":{"delete":{"tags":["Group Members"],"summary":"Remove a member from a group (Coming Soon)","description":"⚠️ **COMING SOON** - This endpoint is temporarily disabled while we stabilize this feature.\n\nRemove a contact from a group. If the group is linked to an existing iMessage chat, also removes the participant from that chat. If the contact being removed is the organization's own phone number, leaves the group chat instead.","operationId":"removeGroupMember","parameters":[{"$ref":"#/components/parameters/GroupIdParam"},{"$ref":"#/components/parameters/ContactIdParam"}],"responses":{"200":{"description":"Member removed","content":{"application/json":{"schema":{"type":"object","properties":{"success":{"type":"boolean"},"removed_at":{"type":"integer","format":"int64"}}}}}},"400":{"$ref":"#/components/responses/BadRequest"},"401":{"$ref":"#/components/responses/Unauthorized"},"404":{"$ref":"#/components/responses/NotFound"},"501":{"description":"Coming soon - endpoint temporarily disabled","content":{"application/json":{"schema":{"type":"object","properties":{"error":{"type":"string","example":"Coming soon"},"message":{"type":"string","example":"Removing group members is coming soon"}}}}}}}}},"/groups/{groupId}/icon":{"post":{"tags":["Groups"],"summary":"Set group icon","description":"Set the group icon/photo. Requires the group to have a linked chat_guid. Uses multipart/form-data.\n\nThe uploaded image is stored in Blooio storage and synced to the linked iMessage chat before the request returns.","operationId":"setGroupIcon","parameters":[{"$ref":"#/components/parameters/GroupIdParam"}],"requestBody":{"required":true,"content":{"multipart/form-data":{"schema":{"type":"object","required":["icon"],"properties":{"icon":{"type":"string","format":"binary","description":"The icon image file to set as the group photo"}}}}}},"responses":{"200":{"description":"Group icon set successfully","content":{"application/json":{"schema":{"$ref":"#/components/schemas/GroupIconResponse"}}}},"400":{"$ref":"#/components/responses/BadRequest"},"401":{"$ref":"#/components/responses/Unauthorized"},"404":{"$ref":"#/components/responses/NotFound"},"502":{"description":"Failed to update the linked iMessage chat","content":{"application/json":{"schema":{"$ref":"#/components/schemas/Error"}}}},"503":{"description":"No active number available","content":{"application/json":{"schema":{"$ref":"#/components/schemas/Error"}}}}}},"delete":{"tags":["Groups"],"summary":"Remove group icon","description":"Remove the group icon/photo. Requires the group to have a linked chat_guid.\n\nThe icon is removed from both Blooio storage and the linked iMessage chat before the request returns.","operationId":"removeGroupIcon","parameters":[{"$ref":"#/components/parameters/GroupIdParam"}],"responses":{"200":{"description":"Group icon removed successfully","content":{"application/json":{"schema":{"$ref":"#/components/schemas/GroupIconResponse"}}}},"400":{"$ref":"#/components/responses/BadRequest"},"401":{"$ref":"#/components/responses/Unauthorized"},"404":{"$ref":"#/components/responses/NotFound"},"502":{"description":"Failed to update the linked iMessage chat","content":{"application/json":{"schema":{"$ref":"#/components/schemas/Error"}}}},"503":{"description":"No active number available","content":{"application/json":{"schema":{"$ref":"#/components/schemas/Error"}}}}}}},"/webhooks":{"get":{"tags":["Webhooks"],"summary":"List webhooks","description":"List all webhooks for the organization.","operationId":"listWebhooks","responses":{"200":{"description":"List of webhooks","content":{"application/json":{"schema":{"type":"object","properties":{"webhooks":{"type":"array","items":{"$ref":"#/components/schemas/Webhook"}}}}}}},"401":{"$ref":"#/components/responses/Unauthorized"}}},"post":{"tags":["Webhooks"],"summary":"Create a webhook","description":"Create a new webhook subscription.","operationId":"createWebhook","requestBody":{"required":true,"content":{"application/json":{"schema":{"type":"object","required":["webhook_url"],"properties":{"webhook_url":{"type":"string","format":"uri","description":"URL to receive webhook events","example":"https://example.com/webhook"},"webhook_type":{"type":"string","enum":["message","status","all"],"default":"message","description":"Type of events to receive"},"valid_until":{"type":"integer","format":"int64","description":"Expiration timestamp (-1 for no expiration)","default":-1}}}}}},"responses":{"200":{"description":"Webhook already exists (idempotent)","content":{"application/json":{"schema":{"type":"object","properties":{"webhook_id":{"type":"string"},"webhook_url":{"type":"string"},"scope":{"type":"string","enum":["api_key","organization"]},"message":{"type":"string"}}}}}},"201":{"description":"Webhook created. The signing_secret is shown only once - store it securely.","content":{"application/json":{"schema":{"type":"object","properties":{"webhook_id":{"type":"string"},"webhook_url":{"type":"string","format":"uri"},"webhook_type":{"type":"string","enum":["message","status","all"]},"scope":{"type":"string","enum":["api_key","organization"]},"created_at":{"type":"integer","format":"int64"},"signing_secret":{"type":"string","description":"The webhook signing secret. Store this securely - it will not be shown again."}}}}}},"400":{"$ref":"#/components/responses/BadRequest"},"401":{"$ref":"#/components/responses/Unauthorized"},"409":{"description":"Webhook limit reached (max 64 per organization)","content":{"application/json":{"schema":{"$ref":"#/components/schemas/Error"}}}}}}},"/webhooks/{webhookId}":{"get":{"tags":["Webhooks"],"summary":"Get a webhook","description":"Get details for a specific webhook.","operationId":"getWebhook","parameters":[{"$ref":"#/components/parameters/WebhookIdParam"}],"responses":{"200":{"description":"Webhook details","content":{"application/json":{"schema":{"$ref":"#/components/schemas/Webhook"}}}},"401":{"$ref":"#/components/responses/Unauthorized"},"404":{"$ref":"#/components/responses/NotFound"}}},"patch":{"tags":["Webhooks"],"summary":"Update a webhook","description":"Update a webhook's configuration.","operationId":"updateWebhook","parameters":[{"$ref":"#/components/parameters/WebhookIdParam"}],"requestBody":{"required":true,"content":{"application/json":{"schema":{"type":"object","properties":{"webhook_type":{"type":"string","enum":["message","status","all"],"description":"Type of events to receive"},"valid_until":{"type":"integer","format":"int64","description":"Expiration timestamp. Use -1 or null for no expiration."},"deprecate":{"type":"boolean","description":"Set to true to deprecate, false to undeprecate"}}}}}},"responses":{"200":{"description":"Webhook updated","content":{"application/json":{"schema":{"$ref":"#/components/schemas/Webhook"}}}},"400":{"$ref":"#/components/responses/BadRequest"},"401":{"$ref":"#/components/responses/Unauthorized"},"404":{"$ref":"#/components/responses/NotFound"}}},"delete":{"tags":["Webhooks"],"summary":"Delete a webhook","description":"Permanently delete a webhook.","operationId":"deleteWebhook","parameters":[{"$ref":"#/components/parameters/WebhookIdParam"}],"responses":{"200":{"description":"Webhook deleted","content":{"application/json":{"schema":{"type":"object","properties":{"success":{"type":"boolean"},"message":{"type":"string","example":"Webhook deleted"}}}}}},"401":{"$ref":"#/components/responses/Unauthorized"},"404":{"$ref":"#/components/responses/NotFound"}}}},"/webhooks/{webhookId}/secret/rotate":{"post":{"tags":["Webhooks"],"summary":"Rotate webhook signing secret","description":"Generate a new signing secret for the webhook. The new secret is returned only once in this response - store it securely. The old secret becomes invalid immediately.","operationId":"rotateWebhookSecret","parameters":[{"$ref":"#/components/parameters/WebhookIdParam"}],"responses":{"200":{"description":"Secret rotated successfully. The new secret is shown only once.","content":{"application/json":{"schema":{"type":"object","properties":{"webhook_id":{"type":"string"},"signing_secret":{"type":"string","description":"The new signing secret. Store this securely - it will not be shown again."},"rotated_at":{"type":"integer","format":"int64","description":"Timestamp when the secret was rotated"},"rotated_by":{"type":"string","description":"Identifier of who rotated the secret"},"rotation_count":{"type":"integer","description":"Total number of times this secret has been rotated"}}}}}},"401":{"$ref":"#/components/responses/Unauthorized"},"404":{"$ref":"#/components/responses/NotFound"}}}},"/webhooks/{webhookId}/logs":{"get":{"tags":["Webhook Logs"],"summary":"List webhook logs","description":"List delivery logs for a specific webhook.","operationId":"listWebhookLogs","parameters":[{"$ref":"#/components/parameters/WebhookIdParam"},{"$ref":"#/components/parameters/LimitParam"},{"$ref":"#/components/parameters/OffsetParam"},{"name":"sort","in":"query","description":"Sort order by attempted time","schema":{"type":"string","enum":["asc","desc"],"default":"desc"}},{"name":"status","in":"query","description":"Filter by exact HTTP status code","schema":{"type":"integer"}},{"name":"min_status","in":"query","description":"Minimum HTTP status code","schema":{"type":"integer"}},{"name":"max_status","in":"query","description":"Maximum HTTP status code","schema":{"type":"integer"}}],"responses":{"200":{"description":"List of webhook logs","content":{"application/json":{"schema":{"type":"object","properties":{"logs":{"type":"array","items":{"$ref":"#/components/schemas/WebhookLog"}},"pagination":{"type":"object","properties":{"total":{"type":"integer","description":"Total number of matching logs"},"limit":{"type":"integer"},"offset":{"type":"integer"},"returned":{"type":"integer","description":"Number of logs returned in this response"},"has_more":{"type":"boolean","description":"Whether there are more logs to fetch"}}}}}}}},"401":{"$ref":"#/components/responses/Unauthorized"},"404":{"$ref":"#/components/responses/NotFound"}}}},"/webhooks/{webhookId}/logs/{eventId}/replay":{"post":{"tags":["Webhook Logs"],"summary":"Replay a webhook event","description":"Re-send a webhook event to the configured URL.","operationId":"replayWebhookEvent","parameters":[{"$ref":"#/components/parameters/WebhookIdParam"},{"name":"eventId","in":"path","required":true,"description":"Event ID to replay","schema":{"type":"string"}}],"responses":{"200":{"description":"Event replayed","content":{"application/json":{"schema":{"type":"object","properties":{"success":{"type":"boolean","description":"Whether the replay received a 2xx response"},"replay_event_id":{"type":"string","description":"New event ID for this replay attempt"},"original_event_id":{"type":"string","description":"The original event ID that was replayed"},"webhook_id":{"type":"string"},"webhook_url":{"type":"string"},"response_status":{"type":"integer","description":"HTTP status code from replay attempt"},"duration_ms":{"type":"integer","description":"Time taken for the replay request in milliseconds"},"response_data":{"type":"object","description":"Response details from the replay attempt","properties":{"body":{"description":"Response body (if parseable)"},"headers":{"type":"object"},"size":{"type":"integer"},"contentType":{"type":"string"},"duration":{"type":"integer"},"error":{"type":"string","nullable":true},"errorType":{"type":"string","nullable":true}}}}}}}},"401":{"$ref":"#/components/responses/Unauthorized"},"404":{"$ref":"#/components/responses/NotFound"}}}},"/chats":{"get":{"tags":["Chats"],"summary":"List chats","description":"List all unique conversations for the organization, sorted by most recent message.","operationId":"listChats","parameters":[{"$ref":"#/components/parameters/LimitParam"},{"$ref":"#/components/parameters/OffsetParam"},{"name":"q","in":"query","description":"Search query (matches phone/email or contact name)","schema":{"type":"string"}},{"name":"sort","in":"query","description":"Sort order","schema":{"type":"string","enum":["recent","oldest"],"default":"recent"}}],"responses":{"200":{"description":"List of chats","content":{"application/json":{"schema":{"type":"object","properties":{"chats":{"type":"array","items":{"$ref":"#/components/schemas/Chat"}},"pagination":{"$ref":"#/components/schemas/Pagination"}}}}}},"401":{"$ref":"#/components/responses/Unauthorized"}}}},"/chats/{chatId}":{"get":{"tags":["Chats"],"summary":"Get a chat","description":"Get details for a specific conversation.","operationId":"getChat","parameters":[{"$ref":"#/components/parameters/ChatIdParam"}],"responses":{"200":{"description":"Chat details","content":{"application/json":{"schema":{"$ref":"#/components/schemas/ChatDetail"}}}},"400":{"$ref":"#/components/responses/BadRequest"},"401":{"$ref":"#/components/responses/Unauthorized"},"404":{"$ref":"#/components/responses/NotFound"}}}},"/chats/{chatId}/messages":{"get":{"tags":["Messages"],"summary":"List messages in a chat","description":"List all messages in a conversation with optional filtering.","operationId":"listChatMessages","parameters":[{"$ref":"#/components/parameters/ChatIdParam"},{"$ref":"#/components/parameters/LimitParam"},{"$ref":"#/components/parameters/OffsetParam"},{"name":"sort","in":"query","description":"Sort order by time","schema":{"type":"string","enum":["asc","desc"],"default":"desc"}},{"name":"direction","in":"query","description":"Filter by message direction","schema":{"type":"string","enum":["inbound","outbound"]}},{"name":"since","in":"query","description":"Only messages sent after this timestamp (ms)","schema":{"type":"integer","format":"int64"}},{"name":"until","in":"query","description":"Only messages sent before this timestamp (ms)","schema":{"type":"integer","format":"int64"}}],"responses":{"200":{"description":"List of messages","content":{"application/json":{"schema":{"type":"object","properties":{"chat_id":{"type":"string"},"messages":{"type":"array","items":{"$ref":"#/components/schemas/Message"}},"pagination":{"$ref":"#/components/schemas/Pagination"}}}}}},"400":{"$ref":"#/components/responses/BadRequest"},"401":{"$ref":"#/components/responses/Unauthorized"},"404":{"$ref":"#/components/responses/NotFound"}}},"post":{"tags":["Messages"],"summary":"Send a message","description":"Send a message to a chat. The chatId can be: (1) E.164 phone number, (2) email address, (3) group ID (grp_xxxx), or (4) comma-separated list of phone/email for multi-recipient chats. For multi-recipient, an unnamed group is automatically created or reused if the exact participant combination already exists. For explicit groups, the group must be linked to an existing iMessage chat.","operationId":"sendMessage","parameters":[{"$ref":"#/components/parameters/ChatIdParam"},{"name":"Idempotency-Key","in":"header","description":"Unique key to prevent duplicate message sends. If the same key is used again, the original message_id and status are returned.","schema":{"type":"string"}}],"requestBody":{"required":true,"content":{"application/json":{"schema":{"$ref":"#/components/schemas/SendMessageRequest"}}}},"responses":{"200":{"description":"Duplicate request (idempotency key matched)","content":{"application/json":{"schema":{"$ref":"#/components/schemas/SendMessageResponse"}}}},"202":{"description":"Message accepted for sending","content":{"application/json":{"schema":{"$ref":"#/components/schemas/SendMessageResponse"}}}},"400":{"$ref":"#/components/responses/BadRequest"},"401":{"$ref":"#/components/responses/Unauthorized"},"403":{"description":"Forbidden (e.g., emergency number, integration-assigned number)","content":{"application/json":{"schema":{"$ref":"#/components/schemas/Error"}}}},"404":{"$ref":"#/components/responses/NotFound"},"503":{"description":"No active number available","content":{"application/json":{"schema":{"$ref":"#/components/schemas/Error"}}}}}}},"/chats/{chatId}/messages/{messageId}":{"get":{"tags":["Messages"],"summary":"Get a message","description":"Get details for a specific message.","operationId":"getMessage","parameters":[{"$ref":"#/components/parameters/ChatIdParam"},{"$ref":"#/components/parameters/MessageIdParam"}],"responses":{"200":{"description":"Message details","content":{"application/json":{"schema":{"$ref":"#/components/schemas/MessageDetail"}}}},"400":{"$ref":"#/components/responses/BadRequest"},"401":{"$ref":"#/components/responses/Unauthorized"},"404":{"$ref":"#/components/responses/NotFound"}}}},"/chats/{chatId}/messages/{messageId}/status":{"get":{"tags":["Messages"],"summary":"Get message status","description":"Get delivery status for a specific message.","operationId":"getMessageStatus","parameters":[{"$ref":"#/components/parameters/ChatIdParam"},{"$ref":"#/components/parameters/MessageIdParam"}],"responses":{"200":{"description":"Message status","content":{"application/json":{"schema":{"$ref":"#/components/schemas/MessageStatus"}}}},"400":{"$ref":"#/components/responses/BadRequest"},"401":{"$ref":"#/components/responses/Unauthorized"},"404":{"$ref":"#/components/responses/NotFound"}}}},"/chats/{chatId}/messages/{messageId}/reactions":{"post":{"tags":["Reactions"],"summary":"Add or remove a reaction","description":"Add or remove a reaction to a message. Supports classic iMessage tapbacks (love, like, dislike, laugh, emphasize, question) and emoji reactions (e.g. +😂, -😂).\n\nThe messageId can be an explicit message ID (e.g., msg_xxx) or a relative index (-1 for last message, -2 for second-to-last, etc.). When using relative indices, you can optionally filter by message direction (inbound/outbound only).\n\nEmoji reactions require macOS 14 (Sonoma) or later on the device.","operationId":"addReaction","parameters":[{"$ref":"#/components/parameters/ChatIdParam"},{"name":"messageId","in":"path","required":true,"description":"Message ID (e.g., msg_xxx) or relative index (-1, -2, etc.)","schema":{"type":"string"},"examples":{"explicit":{"value":"msg_abc123def456","summary":"Explicit message ID"},"lastMessage":{"value":"-1","summary":"Last message in chat"},"secondToLast":{"value":"-2","summary":"Second-to-last message"}}}],"requestBody":{"required":true,"content":{"application/json":{"schema":{"$ref":"#/components/schemas/ReactionRequest"}}}},"responses":{"200":{"description":"Reaction added or removed successfully","content":{"application/json":{"schema":{"$ref":"#/components/schemas/ReactionResponse"}}}},"400":{"$ref":"#/components/responses/BadRequest"},"401":{"$ref":"#/components/responses/Unauthorized"},"404":{"$ref":"#/components/responses/NotFound"},"502":{"description":"Temporary communication error","content":{"application/json":{"schema":{"$ref":"#/components/schemas/Error"}}}},"503":{"description":"Device not available","content":{"application/json":{"schema":{"$ref":"#/components/schemas/Error"}}}}}}},"/chats/{chatId}/polls":{"post":{"tags":["Polls"],"summary":"Send a poll","description":"Send a native iMessage poll to a chat. The poll appears as an interactive ballot that recipients can vote on.","operationId":"sendPoll","parameters":[{"$ref":"#/components/parameters/ChatIdParam"}],"requestBody":{"required":true,"content":{"application/json":{"schema":{"type":"object","required":["options"],"properties":{"title":{"type":"string","description":"Poll question or title (optional)"},"options":{"type":"array","description":"Array of 2-10 option strings for the poll","items":{"type":"string"},"minItems":2,"maxItems":10}}}}}},"responses":{"200":{"description":"Poll sent successfully","content":{"application/json":{"schema":{"type":"object","properties":{"poll_id":{"type":"string","description":"Unique identifier for the poll"},"chat_id":{"type":"string"},"poll":{"type":"object","properties":{"title":{"type":"string"},"options":{"type":"array","items":{"type":"string"}}}},"sent_at":{"type":"number"}}}}}},"400":{"$ref":"#/components/responses/BadRequest"},"401":{"$ref":"#/components/responses/Unauthorized"},"502":{"description":"Device unreachable","content":{"application/json":{"schema":{"$ref":"#/components/schemas/Error"}}}},"503":{"description":"No active device available","content":{"application/json":{"schema":{"$ref":"#/components/schemas/Error"}}}}}}},"/chats/{chatId}/polls/{pollId}":{"get":{"tags":["Polls"],"summary":"Get poll results","description":"Retrieve a poll's definition and aggregated vote counts. The pollId is the poll_id returned in the poll.received or poll.created webhook event.","operationId":"getPollResults","parameters":[{"$ref":"#/components/parameters/ChatIdParam"},{"name":"pollId","in":"path","required":true,"description":"The poll ID","schema":{"type":"string"}}],"responses":{"200":{"description":"Poll results","content":{"application/json":{"schema":{"type":"object","properties":{"poll_id":{"type":"string"},"chat_id":{"type":"string"},"title":{"type":"string"},"options":{"type":"array","items":{"type":"object","properties":{"text":{"type":"string"},"votes":{"type":"integer"}}}},"total_votes":{"type":"integer"}}}}}},"400":{"$ref":"#/components/responses/BadRequest"},"401":{"$ref":"#/components/responses/Unauthorized"},"404":{"$ref":"#/components/responses/NotFound"}}}},"/chats/{chatId}/typing":{"post":{"tags":["Typing Indicators"],"summary":"Start typing indicator","description":"Start the typing indicator for a chat. The indicator shows the recipient that you are typing.","operationId":"startTyping","parameters":[{"$ref":"#/components/parameters/ChatIdParam"}],"responses":{"200":{"description":"Typing indicator started","content":{"application/json":{"schema":{"$ref":"#/components/schemas/TypingResponse"}}}},"400":{"$ref":"#/components/responses/BadRequest"},"401":{"$ref":"#/components/responses/Unauthorized"},"502":{"description":"Temporary communication error","content":{"application/json":{"schema":{"$ref":"#/components/schemas/Error"}}}},"503":{"description":"No active number available","content":{"application/json":{"schema":{"$ref":"#/components/schemas/Error"}}}}}},"delete":{"tags":["Typing Indicators"],"summary":"Stop typing indicator","description":"Stop the typing indicator for a chat.","operationId":"stopTyping","parameters":[{"$ref":"#/components/parameters/ChatIdParam"}],"responses":{"200":{"description":"Typing indicator stopped","content":{"application/json":{"schema":{"$ref":"#/components/schemas/TypingResponse"}}}},"400":{"$ref":"#/components/responses/BadRequest"},"401":{"$ref":"#/components/responses/Unauthorized"},"502":{"description":"Temporary communication error","content":{"application/json":{"schema":{"$ref":"#/components/schemas/Error"}}}},"503":{"description":"No active number available","content":{"application/json":{"schema":{"$ref":"#/components/schemas/Error"}}}}}}},"/chats/{chatId}/read":{"post":{"tags":["Read Receipts"],"summary":"Mark chat as read","description":"Mark all messages in a chat as read. This sends a read receipt to the sender.","operationId":"markChatRead","parameters":[{"$ref":"#/components/parameters/ChatIdParam"}],"responses":{"200":{"description":"Chat marked as read","content":{"application/json":{"schema":{"$ref":"#/components/schemas/ReadResponse"}}}},"400":{"$ref":"#/components/responses/BadRequest"},"401":{"$ref":"#/components/responses/Unauthorized"},"404":{"$ref":"#/components/responses/NotFound"},"502":{"description":"Temporary communication error","content":{"application/json":{"schema":{"$ref":"#/components/schemas/Error"}}}}}}},"/chats/{chatId}/contact-card":{"post":{"tags":["Contact Card"],"summary":"Share contact card","description":"Stage the contact card (Name & Photo) for sharing in a chat. The contact card will be piggybacked onto the next outgoing message (text or attachment) sent to this chat. This is idempotent — calling it multiple times is harmless.","operationId":"shareContactCard","parameters":[{"$ref":"#/components/parameters/ChatIdParam"}],"responses":{"200":{"description":"Contact card staged for sharing","content":{"application/json":{"schema":{"type":"object","properties":{"success":{"type":"boolean","example":true},"chat_id":{"type":"string","description":"Normalized chat identifier"},"message":{"type":"string","example":"Contact card staged. It will be sent with the next outgoing message in this chat."}}}}}},"401":{"$ref":"#/components/responses/Unauthorized"},"502":{"description":"Temporary communication error","content":{"application/json":{"schema":{"$ref":"#/components/schemas/Error"}}}},"503":{"description":"No active number available","content":{"application/json":{"schema":{"$ref":"#/components/schemas/Error"}}}}}}},"/chats/{chatId}/background":{"get":{"tags":["Chat Background"],"summary":"Get chat background","description":"Get the current background image metadata for a conversation. Works for both 1-on-1 and group chats.","operationId":"getChatBackground","parameters":[{"$ref":"#/components/parameters/ChatIdParam"}],"responses":{"200":{"description":"Chat background details","content":{"application/json":{"schema":{"$ref":"#/components/schemas/ChatBackgroundResponse"}}}},"401":{"$ref":"#/components/responses/Unauthorized"},"502":{"description":"Temporary communication error with device","content":{"application/json":{"schema":{"$ref":"#/components/schemas/Error"}}}},"503":{"description":"No active number available","content":{"application/json":{"schema":{"$ref":"#/components/schemas/Error"}}}}}},"put":{"tags":["Chat Background"],"summary":"Set chat background","description":"Set or update the background image for a conversation. Works for both 1-on-1 and group chats.\n\nThe uploaded image is converted into a PosterKit-compatible archive and applied to the iMessage conversation on the linked device. Supported formats: JPEG, PNG, GIF, WebP, HEIC/HEIF. Maximum file size: 10 MB.","operationId":"setChatBackground","parameters":[{"$ref":"#/components/parameters/ChatIdParam"}],"requestBody":{"required":true,"content":{"multipart/form-data":{"schema":{"type":"object","required":["background"],"properties":{"background":{"type":"string","format":"binary","description":"The image file to set as the chat background"}}}}}},"responses":{"200":{"description":"Chat background set successfully","content":{"application/json":{"schema":{"$ref":"#/components/schemas/ChatBackgroundResponse"}}}},"400":{"$ref":"#/components/responses/BadRequest"},"401":{"$ref":"#/components/responses/Unauthorized"},"502":{"description":"Failed to set background on device","content":{"application/json":{"schema":{"$ref":"#/components/schemas/Error"}}}},"503":{"description":"No active number available","content":{"application/json":{"schema":{"$ref":"#/components/schemas/Error"}}}}}},"delete":{"tags":["Chat Background"],"summary":"Remove chat background","description":"Remove the background image from a conversation, reverting to the default appearance.","operationId":"removeChatBackground","parameters":[{"$ref":"#/components/parameters/ChatIdParam"}],"responses":{"200":{"description":"Chat background removed successfully","content":{"application/json":{"schema":{"$ref":"#/components/schemas/ChatBackgroundResponse"}}}},"401":{"$ref":"#/components/responses/Unauthorized"},"502":{"description":"Failed to remove background on device","content":{"application/json":{"schema":{"$ref":"#/components/schemas/Error"}}}},"503":{"description":"No active number available","content":{"application/json":{"schema":{"$ref":"#/components/schemas/Error"}}}}}}},"/phone-numbers/lookup":{"get":{"tags":["Phone Numbers"],"summary":"Look up a phone number","description":"Returns detailed information about a phone number including validation, formatting (E.164, national, international), number type, and NANPA geocoding (city, state/province) for North American numbers. The geocoding data is sourced from different database with 240,000+ NPA-NXX entries.\n\n**Requires an Enterprise plan** (Dedicated Enterprise). Returns 403 if your organization does not have an active enterprise subscription.","operationId":"lookupPhoneNumber","parameters":[{"name":"number","in":"query","required":true,"description":"Phone number to look up. Can be E.164 format (+12125551234), national format (2125551234), or with formatting ((212) 555-1234).","schema":{"type":"string","example":"+12125551234"}}],"responses":{"200":{"description":"Phone number information","content":{"application/json":{"schema":{"$ref":"#/components/schemas/PhoneNumberLookupResult"}}}},"400":{"$ref":"#/components/responses/BadRequest"},"401":{"$ref":"#/components/responses/Unauthorized"},"403":{"description":"Enterprise plan required. Upgrade to Dedicated Enterprise to access this endpoint.","content":{"application/json":{"schema":{"$ref":"#/components/schemas/Error"}}}}}},"post":{"tags":["Phone Numbers"],"summary":"Look up a phone number","description":"Same as the GET endpoint, but accepts the phone number in the request body. Useful when the number contains characters that are difficult to URL-encode.\n\n**Requires an Enterprise plan** (Dedicated Enterprise).","operationId":"lookupPhoneNumberPost","requestBody":{"required":true,"content":{"application/json":{"schema":{"type":"object","required":["number"],"properties":{"number":{"type":"string","description":"Phone number to look up","example":"+12125551234"}}}}}},"responses":{"200":{"description":"Phone number information","content":{"application/json":{"schema":{"$ref":"#/components/schemas/PhoneNumberLookupResult"}}}},"400":{"$ref":"#/components/responses/BadRequest"},"401":{"$ref":"#/components/responses/Unauthorized"},"403":{"description":"Enterprise plan required","content":{"application/json":{"schema":{"$ref":"#/components/schemas/Error"}}}}}}},"/phone-numbers/batch":{"post":{"tags":["Phone Numbers"],"summary":"Batch look up phone numbers","description":"Look up multiple phone numbers in a single request. Returns the same detailed information as the single lookup endpoint for each number. Maximum 100 numbers per request.\n\n**Requires an Enterprise plan** (Dedicated Enterprise).","operationId":"batchLookupPhoneNumbers","requestBody":{"required":true,"content":{"application/json":{"schema":{"type":"object","required":["numbers"],"properties":{"numbers":{"type":"array","items":{"type":"string"},"minItems":1,"maxItems":100,"description":"Array of phone numbers to look up","example":["+12125551234","+14155551234","+18582849901"]}}}}}},"responses":{"200":{"description":"Batch lookup results","content":{"application/json":{"schema":{"type":"object","properties":{"results":{"type":"array","items":{"$ref":"#/components/schemas/PhoneNumberLookupResult"}}}}}}},"400":{"$ref":"#/components/responses/BadRequest"},"401":{"$ref":"#/components/responses/Unauthorized"},"403":{"description":"Enterprise plan required","content":{"application/json":{"schema":{"$ref":"#/components/schemas/Error"}}}}}}}},"components":{"securitySchemes":{"BearerAuth":{"type":"http","scheme":"bearer","description":"API key authentication. Use your API key as the bearer token."}},"parameters":{"LimitParam":{"name":"limit","in":"query","description":"Maximum number of items to return (1-200)","schema":{"type":"integer","minimum":1,"maximum":200,"default":50}},"OffsetParam":{"name":"offset","in":"query","description":"Number of items to skip","schema":{"type":"integer","minimum":0,"default":0}},"ContactIdParam":{"name":"contactId","in":"path","required":true,"description":"Contact identifier (phone number in E.164 format or email, URL-encoded)","schema":{"type":"string"},"example":"%2B15551234567"},"GroupIdParam":{"name":"groupId","in":"path","required":true,"description":"Group ID","schema":{"type":"string","pattern":"^grp_[a-zA-Z0-9]+$"},"example":"grp_abc123def456"},"WebhookIdParam":{"name":"webhookId","in":"path","required":true,"description":"Webhook ID","schema":{"type":"string","pattern":"^wh_[a-zA-Z0-9]+$"},"example":"wh_abc123def456"},"ChatIdParam":{"name":"chatId","in":"path","required":true,"description":"Chat identifier. Can be: (1) phone number in E.164 format (e.g., +15551234567), (2) email address, (3) group ID (grp_xxxx), or (4) comma-separated list of phone numbers/emails for multi-recipient group chats (e.g., +15551234567,+15559876543). All values should be URL-encoded.","schema":{"type":"string"},"examples":{"phone":{"value":"%2B15551234567","summary":"Phone number (URL-encoded)"},"email":{"value":"user%40example.com","summary":"Email address (URL-encoded)"},"group":{"value":"grp_abc123def456","summary":"Group ID"},"multi":{"value":"%2B15551234567%2C%2B15559876543","summary":"Multiple recipients (comma-separated, URL-encoded)"}}},"MessageIdParam":{"name":"messageId","in":"path","required":true,"description":"Message ID","schema":{"type":"string"},"example":"msg_abc123def456"}},"responses":{"Unauthorized":{"description":"Authentication required or invalid","content":{"application/json":{"schema":{"$ref":"#/components/schemas/Error"}}}},"BadRequest":{"description":"Invalid request parameters","content":{"application/json":{"schema":{"$ref":"#/components/schemas/Error"}}}},"NotFound":{"description":"Resource not found","content":{"application/json":{"schema":{"$ref":"#/components/schemas/Error"}}}}},"schemas":{"PhoneNumberLookupResult":{"type":"object","properties":{"input":{"type":"string","description":"The original input string","example":"+12125551234"},"valid":{"type":"boolean","description":"Whether the phone number is valid"},"possible":{"type":"boolean","description":"Whether the phone number is a possible number (less strict than valid)"},"e164":{"type":"string","description":"E.164 formatted number","example":"+12125551234"},"national":{"type":"string","description":"National formatted number","example":"(212) 555-1234"},"international":{"type":"string","description":"International formatted number","example":"+1 212 555 1234"},"country_calling_code":{"type":"string","description":"Country calling code without +","example":"1"},"country":{"type":"string","nullable":true,"description":"ISO 3166-1 alpha-2 country code","example":"US"},"national_number":{"type":"string","description":"National number without country code","example":"2125551234"},"type":{"type":"string","nullable":true,"description":"Number type detected by libphonenumber","enum":["FIXED_LINE","MOBILE","FIXED_LINE_OR_MOBILE","TOLL_FREE","PREMIUM_RATE","SHARED_COST","VOIP","PERSONAL_NUMBER","PAGER","UAN","VOICEMAIL"],"example":"FIXED_LINE_OR_MOBILE"},"location":{"type":"object","nullable":true,"description":"NANPA geocoding location (only for North American numbers with country code 1)","properties":{"city":{"type":"string","nullable":true,"description":"City name","example":"New York"},"region":{"type":"string","nullable":true,"description":"State/province abbreviation","example":"NY"},"region_name":{"type":"string","nullable":true,"description":"Full state/province name","example":"New York"}}},"area_code":{"type":"string","description":"NPA area code (first 3 digits of national number, only for NANP numbers)","example":"212"},"exchange":{"type":"string","description":"NXX exchange code (digits 4-6 of national number, only for NANP numbers)","example":"555"},"area_code_region":{"type":"string","description":"General region for the area code (most common city, only for NANP numbers)","example":"New York, NY"}}},"ContactLocation":{"type":"object","properties":{"handle":{"type":"string","description":"Contact's phone number or email"},"coordinates":{"type":"array","items":{"type":"number"},"description":"GPS coordinates [latitude, longitude]"},"status":{"type":"string","description":"Location status (e.g., 'live', 'shallow', 'legacy')"},"last_updated":{"type":"integer","format":"int64","description":"Timestamp of last location update (epoch ms)"}}},"Error":{"type":"object","properties":{"error":{"type":"string","description":"Error message"},"status":{"type":"integer","description":"HTTP status code"}}},"Pagination":{"type":"object","properties":{"limit":{"type":"integer"},"offset":{"type":"integer"},"total":{"type":"integer"}}},"DeleteResponse":{"type":"object","properties":{"success":{"type":"boolean"},"deleted_at":{"type":"integer","format":"int64"}}},"MeResponse":{"type":"object","description":"Response depends on auth_type. For 'api_key': includes full API key details. For 'dashboard': includes user_id and organization info only.","properties":{"auth_type":{"type":"string","enum":["api_key","dashboard"],"description":"Type of authentication used"},"valid":{"type":"boolean","description":"Whether the API key is valid (only for api_key auth)"},"user_id":{"type":"string","nullable":true,"description":"User ID (only for dashboard auth)"},"api_key":{"type":"string","description":"The API key (only for api_key auth)"},"organization_id":{"type":"string","description":"Organization ID (only for api_key auth)"},"organization":{"type":"object","properties":{"organization_id":{"type":"string"},"name":{"type":"string"},"country_code":{"type":"string","nullable":true},"created_at":{"type":"integer","format":"int64"}}},"metadata":{"type":"object","description":"API key metadata (only for api_key auth)"},"integration_details":{"type":"object","nullable":true,"description":"Integration details if the API key is associated with an integration (only for api_key auth)"},"devices":{"type":"array","description":"List of devices associated with this API key (only for api_key auth)","items":{"type":"object","properties":{"phone_number":{"type":"string","nullable":true,"description":"Phone number assigned to this device (E.164 format)"},"is_active":{"type":"boolean"},"last_active":{"type":"integer","format":"int64","nullable":true}}}},"usage":{"type":"object","description":"Usage statistics (only for api_key auth)","properties":{"inbound_messages":{"type":"integer"},"outbound_messages":{"type":"integer"},"last_message_sent":{"type":"integer","format":"int64","nullable":true}}}}},"Contact":{"type":"object","properties":{"id":{"type":"string","description":"Contact identifier (phone or email)"},"contact_id":{"type":"string","description":"Internal contact ID"},"identifier":{"type":"string","description":"Phone number (E.164) or email"},"name":{"type":"string","nullable":true},"type":{"type":"string","enum":["phone","email"]},"created_at":{"type":"integer","format":"int64"},"last_message_time":{"type":"integer","format":"int64","nullable":true},"tags":{"type":"array","items":{"type":"string"}}}},"Group":{"type":"object","properties":{"group_id":{"type":"string"},"name":{"type":"string","nullable":true,"description":"Group name. Null for unnamed groups."},"chat_guid":{"type":"string","nullable":true,"description":"BlueBubbles chat GUID if linked to a device group chat"},"icon_url":{"type":"string","nullable":true,"description":"URL of the group icon/photo"},"member_count":{"type":"integer"},"message_count":{"type":"integer","description":"Total number of messages in this group"},"last_message_text":{"type":"string","nullable":true,"description":"Text of the most recent message in the group"},"last_message_time":{"type":"integer","format":"int64","nullable":true,"description":"Timestamp of the most recent message"},"last_message_direction":{"type":"string","enum":["inbound","outbound"],"nullable":true,"description":"Direction of the most recent message"},"created_at":{"type":"integer","format":"int64"}}},"GroupMember":{"type":"object","properties":{"id":{"type":"string","description":"Contact identifier (phone or email)"},"contact_id":{"type":"string"},"identifier":{"type":"string"},"name":{"type":"string","nullable":true},"added_at":{"type":"integer","format":"int64"}}},"GroupIconResponse":{"type":"object","description":"Response for group icon operations","properties":{"success":{"type":"boolean"},"group_id":{"type":"string"},"chat_guid":{"type":"string","description":"The BlueBubbles chat GUID"},"icon_url":{"type":"string","description":"URL of the uploaded icon (only present on set)"},"device_sync":{"type":"object","description":"Linked chat sync status","properties":{"chat_guid":{"type":"string"},"synced":{"type":"boolean","description":"Whether the icon change was synced to the linked iMessage chat. This will be true on successful set/remove operations."},"message":{"type":"string","description":"Status message about linked chat sync"}}},"message":{"type":"string"}}},"ChatBackgroundResponse":{"type":"object","description":"Response for chat background operations","properties":{"chat_id":{"type":"string","description":"Normalized chat identifier (phone number, email, or group ID)"},"has_background":{"type":"boolean","description":"Whether the chat currently has a background set"},"background_id":{"type":"string","nullable":true,"description":"Unique identifier for the current background, or null if none"},"background_version":{"type":"integer","nullable":true,"description":"Version number of the background (for cache invalidation)"},"changed":{"type":"boolean","description":"Whether the background was changed by this operation (only present on PUT)"}}},"DeviceSyncResult":{"type":"object","description":"Result of syncing the operation to a linked iMessage chat","properties":{"chat_guid":{"type":"string","description":"The linked iMessage chat GUID"},"action":{"type":"string","enum":["add_participant","remove_participant","leave"],"description":"The action that was performed for the linked chat"},"synced":{"type":"boolean","description":"Whether the sync was successful"},"error":{"type":"string","nullable":true,"description":"Error message if sync failed"}}},"Webhook":{"type":"object","properties":{"webhook_id":{"type":"string"},"webhook_url":{"type":"string","format":"uri"},"webhook_type":{"type":"string","enum":["message","status","all"]},"scope":{"type":"string","enum":["api_key","organization","integration"]},"api_key_name":{"type":"string","nullable":true,"description":"Name of the API key (if scope is api_key)"},"integration_name":{"type":"string","nullable":true,"description":"Name of the integration (if scope is integration)"},"created_at":{"type":"integer","format":"int64"},"deprecated_at":{"type":"integer","format":"int64","nullable":true},"valid_until":{"type":"integer","format":"int64","description":"-1 means no expiration"},"last_triggered":{"type":"integer","format":"int64","nullable":true},"failure_count":{"type":"integer"},"is_active":{"type":"boolean","description":"Whether the webhook is active (not deprecated)"}}},"WebhookLog":{"type":"object","properties":{"event_id":{"type":"string"},"scope":{"type":"string","enum":["api","integration","org"]},"attempted_time":{"type":"integer","format":"int64"},"response_received_at":{"type":"integer","format":"int64","nullable":true},"webhook_url":{"type":"string"},"event_body":{"$ref":"#/components/schemas/WebhookEventPayload"},"response_status":{"type":"integer","nullable":true,"description":"HTTP status code received from the webhook endpoint"},"response_json":{"type":"object","nullable":true,"description":"Response body from the webhook endpoint (if JSON)"},"metadata":{"type":"object","description":"Additional metadata about the webhook delivery","properties":{"organization_id":{"type":"string"},"event_name":{"type":"string"},"message_id":{"type":"string"},"is_replay":{"type":"boolean"},"original_event_id":{"type":"string"},"duration_ms":{"type":"integer"}}}}},"WebhookEventPayload":{"type":"object","description":"Webhook event payload. Structure varies by event type. All message events include group information when applicable.","properties":{"event":{"type":"string","description":"Event type (e.g., message.received, message.sent, message.delivered, message.failed, message.read)","example":"message.sent"},"message_id":{"type":"string","description":"Unique message identifier"},"external_id":{"type":"string","description":"Recipient identifier (phone number, email, or group ID)"},"status":{"type":"string","description":"Message status","enum":["queued","pending","sent","delivered","failed","read","received"]},"protocol":{"type":"string","nullable":true,"description":"Message protocol","enum":["imessage","sms","rcs","non-imessage"]},"timestamp":{"type":"integer","format":"int64","description":"Event timestamp in milliseconds"},"internal_id":{"type":"string","nullable":true,"description":"Phone number that sent/received the message"},"text":{"type":"string","nullable":true,"description":"Message text content"},"attachments":{"type":"array","nullable":true,"description":"Array of attachment objects","items":{"type":"object","properties":{"url":{"type":"string"},"name":{"type":"string","nullable":true}}}},"is_group":{"type":"boolean","description":"Whether this message is from/to a group chat. Always present."},"group_id":{"type":"string","nullable":true,"description":"Group ID (only present when is_group=true)"},"group_name":{"type":"string","nullable":true,"description":"Group display name (only present when is_group=true)"},"participants":{"type":"array","nullable":true,"description":"Array of group participants (only present when is_group=true)","items":{"type":"object","properties":{"contact_id":{"type":"string"},"identifier":{"type":"string"},"name":{"type":"string","nullable":true}}}},"sender":{"type":"string","nullable":true,"description":"Sender identifier (for inbound messages)"},"sent_at":{"type":"integer","format":"int64","nullable":true,"description":"Timestamp when message was sent (for message.sent events)"},"delivered_at":{"type":"integer","format":"int64","nullable":true,"description":"Timestamp when message was delivered (for message.delivered events)"},"read_at":{"type":"integer","format":"int64","nullable":true,"description":"Timestamp when message was read (for message.read events)"},"error_code":{"type":"string","nullable":true,"description":"Error code (for message.failed events)"},"error_message":{"type":"string","nullable":true,"description":"Error description (for message.failed events)"}}},"Chat":{"type":"object","properties":{"id":{"type":"string","description":"Chat identifier (phone number, email, or group ID)"},"type":{"type":"string","enum":["phone","email","group"]},"is_group":{"type":"boolean","description":"Whether this is a group chat"},"group_id":{"type":"string","nullable":true,"description":"Group ID (only for group chats)"},"group_name":{"type":"string","nullable":true,"description":"Group name (only for group chats)"},"member_count":{"type":"integer","description":"Number of members (only for group chats)"},"contact":{"type":"object","nullable":true,"description":"Contact info (only for non-group chats)","properties":{"contact_id":{"type":"string"},"name":{"type":"string","nullable":true},"identifier":{"type":"string"}}},"message_count":{"type":"integer"},"inbound_count":{"type":"integer"},"outbound_count":{"type":"integer"},"last_message_time":{"type":"integer","format":"int64"},"last_inbound_time":{"type":"integer","format":"int64","nullable":true},"last_outbound_time":{"type":"integer","format":"int64","nullable":true},"last_message":{"$ref":"#/components/schemas/LastMessage"}}},"ChatDetail":{"type":"object","properties":{"id":{"type":"string","description":"Chat identifier (phone number, email, or group ID)"},"type":{"type":"string","enum":["phone","email","group"]},"is_group":{"type":"boolean","description":"Whether this is a group chat"},"group_id":{"type":"string","nullable":true,"description":"Group ID (only for group chats)"},"group_name":{"type":"string","nullable":true,"description":"Group name (only for group chats)"},"member_count":{"type":"integer","description":"Number of members (only for group chats)"},"contact":{"type":"object","nullable":true,"description":"Contact info (only for non-group chats)","properties":{"contact_id":{"type":"string"},"name":{"type":"string","nullable":true},"identifier":{"type":"string"}}},"message_count":{"type":"integer"},"inbound_count":{"type":"integer"},"outbound_count":{"type":"integer"},"first_message_time":{"type":"integer","format":"int64"},"last_message_time":{"type":"integer","format":"int64"},"last_inbound_time":{"type":"integer","format":"int64","nullable":true},"last_outbound_time":{"type":"integer","format":"int64","nullable":true},"last_message":{"$ref":"#/components/schemas/LastMessage"}}},"LastMessage":{"type":"object","properties":{"message_id":{"type":"string"},"text":{"type":"string","nullable":true},"direction":{"type":"string","enum":["inbound","outbound"]},"time_sent":{"type":"integer","format":"int64"}}},"Message":{"type":"object","properties":{"message_id":{"type":"string"},"direction":{"type":"string","enum":["inbound","outbound"]},"external_id":{"type":"string","description":"Phone number or email of the contact, or group ID for group messages"},"internal_id":{"type":"string","nullable":true,"description":"Organization phone number (from-number) used for this message"},"text":{"type":"string","nullable":true},"attachments":{"type":"array","items":{"type":"object"}},"sender":{"type":"string","nullable":true,"description":"Sender's phone number or email for inbound group messages. Null for outbound messages and 1-1 chats."},"reactions":{"type":"array","description":"Reactions on this message (tapbacks and emoji reactions)","items":{"$ref":"#/components/schemas/Reaction"}},"time_sent":{"type":"integer","format":"int64"},"time_delivered":{"type":"integer","format":"int64","nullable":true},"status":{"type":"string","enum":["pending","queued","sent","delivered","failed","cancellation_requested","cancelled"],"nullable":true},"protocol":{"type":"string","enum":["imessage","sms","rcs","non-imessage"],"nullable":true},"error":{"type":"string","nullable":true}}},"MessageDetail":{"type":"object","properties":{"message_id":{"type":"string"},"chat_id":{"type":"string"},"direction":{"type":"string","enum":["inbound","outbound"]},"internal_id":{"type":"string","nullable":true,"description":"Organization phone number (from-number) used for this message"},"contact":{"type":"object","nullable":true,"properties":{"contact_id":{"type":"string"},"name":{"type":"string","nullable":true},"identifier":{"type":"string","description":"The contact's phone number or email"}}},"sender":{"type":"string","nullable":true,"description":"Sender's phone number or email for inbound group messages. Null for outbound messages and 1-1 chats."},"text":{"type":"string","nullable":true},"attachments":{"type":"array","items":{"type":"object"}},"reactions":{"type":"array","description":"Reactions on this message (tapbacks and emoji reactions)","items":{"$ref":"#/components/schemas/Reaction"}},"time_sent":{"type":"integer","format":"int64"},"time_delivered":{"type":"integer","format":"int64","nullable":true},"status":{"type":"string","enum":["pending","queued","sent","delivered","failed","cancellation_requested","cancelled"],"nullable":true},"protocol":{"type":"string","enum":["imessage","sms","rcs","non-imessage"],"nullable":true},"error":{"type":"string","nullable":true}}},"Reaction":{"type":"object","properties":{"reaction":{"type":"string","description":"The reaction value. Classic tapbacks: love, like, dislike, laugh, emphasize, question. Emoji reactions: the emoji character (e.g. 😂, 👍)."},"is_added":{"type":"boolean","description":"Whether the reaction is currently active (true) or was removed (false)"},"time_sent":{"type":"integer","format":"int64","description":"Timestamp when the reaction was sent (ms)"},"sender":{"type":"string","nullable":true,"description":"Phone number or email of who sent the reaction. Null when the reaction was sent by you (outbound)."}}},"SendMessageRequest":{"type":"object","description":"Request body for sending a message","properties":{"text":{"oneOf":[{"type":"string"},{"type":"array","items":{"type":"string"}}],"description":"Message text. Can be a single string or array of strings (each becomes a separate message)"},"attachments":{"type":"array","items":{"oneOf":[{"type":"string","description":"URL to the attachment"},{"type":"object","properties":{"url":{"type":"string"},"name":{"type":"string"}},"required":["url"]}]},"description":"Array of attachment URLs or objects with url/name"},"use_typing_indicator":{"type":"boolean","description":"Whether to show typing indicator before sending. Defaults to org preference."},"from_number":{"type":"string","description":"E.164 phone number to send from. For Twilio API keys, this is optional — if omitted, the first assigned Twilio number is auto-selected. For Blooio (iMessage) API keys, this selects a specific number from your pool. Must be a number assigned to your API key."},"share_contact":{"type":"boolean","description":"If true, the contact card (Name & Photo) will be shared with this message. The contact card is piggybacked onto the outgoing message. Defaults to false.","default":false},"parts":{"type":"array","description":"Ordered array of message parts. Two modes:\n\n  1. **Multipart mode** — parts sent as a single unified iMessage bubble (mix of text and attachment parts). This is the default.\n  2. **URL-balloon batch mode** — triggered when any part has a `link_preview` object. Each part becomes its own rich-link-preview iMessage; parts are sent sequentially in array order. In batch mode every part must be text-only with `text` being a single http(s) URL. Response contains `message_ids[]` + `count` instead of `message_id`.","items":{"type":"object","properties":{"text":{"type":"string","description":"Text content for this part. Mutually exclusive with 'url'."},"mention":{"type":"string","description":"Participant phone number or email to @-mention. Only valid with 'text'. The entire text of the part is rendered as the mention."},"url":{"type":"string","description":"URL to an attachment for this part. Mutually exclusive with 'text'."},"name":{"type":"string","description":"Filename for the attachment. Only valid with 'url'."},"link_preview":{"nullable":true,"description":"Optional. Per-part rich-link-preview override. When any part carries this, every part must be a text-only single-URL part (URL-balloon batch mode).","allOf":[{"$ref":"#/components/schemas/LinkPreview"}]}}}},"link_preview":{"nullable":true,"description":"Optional. Override the rich-link-preview image and/or title on URL messages. See the LinkPreview schema. When omitted, Blooio auto-generates the preview from the page's Open Graph tags.","allOf":[{"$ref":"#/components/schemas/LinkPreview"}]}}},"LinkPreview":{"type":"object","description":"Rich-link-preview overrides for URL messages (iMessage URL balloon). All fields are optional. Only applies when the message text (or the concatenated part text) is exactly a single http(s) URL. If omitted but the text is a URL, Blooio auto-fetches the page's Open Graph metadata to generate a preview. If the image download fails, the send still succeeds — Blooio silently falls back to the auto-generated preview.","properties":{"image_url":{"type":"string","format":"uri","description":"HTTPS URL to an image (png, jpg, webp, gif). Blooio downloads the image server-side and attaches it as the rich-link hero. Max 16 MB. If the download fails or returns a non-image MIME, the send falls back to auto-fetched OG metadata."},"title":{"type":"string","maxLength":200,"description":"Bold title line rendered in the iMessage bubble. Overrides the page's `<meta property=\"og:title\">`."}}},"SendMessageResponse":{"type":"object","description":"Response after sending a message","properties":{"message_id":{"type":"string","description":"ID of the sent message (single-message sends)"},"message_ids":{"type":"array","items":{"type":"string"},"description":"IDs of sent messages. Present when `text` is an array or when `parts` uses per-part `link_preview` (URL-balloon batch mode)."},"count":{"type":"integer","description":"Number of messages sent. Only present in URL-balloon batch mode."},"status":{"type":"string","enum":["queued","failed"],"description":"Initial status of the message(s)"},"group_id":{"type":"string","description":"Group ID when sending to multi-recipient (new or existing)"},"group_created":{"type":"boolean","description":"True if a new unnamed group was created for this multi-recipient message"},"participants":{"type":"array","items":{"type":"string"},"description":"List of participants (present for multi-recipient)"}}},"MessageStatus":{"type":"object","properties":{"message_id":{"type":"string"},"chat_id":{"type":"string"},"direction":{"type":"string","enum":["inbound","outbound"]},"status":{"type":"string","enum":["pending","queued","sent","delivered","failed","cancellation_requested","cancelled"],"nullable":true},"protocol":{"type":"string","enum":["imessage","sms","rcs","non-imessage"],"nullable":true},"time_sent":{"type":"integer","format":"int64"},"time_delivered":{"type":"integer","format":"int64","nullable":true},"error":{"type":"string","nullable":true}}},"TypingResponse":{"type":"object","properties":{"chat_id":{"type":"string","description":"Chat identifier"},"typing":{"type":"boolean","description":"Whether typing indicator is active"},"started_at":{"type":"integer","format":"int64","description":"Timestamp when typing started (only for start)"},"stopped_at":{"type":"integer","format":"int64","description":"Timestamp when typing stopped (only for stop)"}}},"ReadResponse":{"type":"object","properties":{"chat_id":{"type":"string","description":"Chat identifier"},"status":{"type":"string","enum":["read"],"description":"Read status"},"marked_at":{"type":"integer","format":"int64","description":"Timestamp when marked as read"}}},"ReactionRequest":{"type":"object","required":["reaction"],"properties":{"reaction":{"type":"string","description":"The reaction to add or remove. Must be prefixed with `+` to add or `-` to remove.\n\n**Classic tapbacks:** `+love`, `-love`, `+like`, `-like`, `+dislike`, `-dislike`, `+laugh`, `-laugh`, `+emphasize`, `-emphasize`, `+question`, `-question`\n\n**Emoji reactions:** Any emoji prefixed with `+` or `-` (e.g. `+😂`, `-😂`, `+👍`, `-🔥`). Emoji reactions require macOS 14 (Sonoma) or later on the device.","examples":["+love","-like","+😂","-🔥"]},"direction":{"type":"string","enum":["inbound","outbound"],"description":"Filter by message direction (only used when messageId is a relative index like -1, -2)"}}},"ReactionResponse":{"type":"object","properties":{"success":{"type":"boolean","description":"Whether the reaction was sent successfully"},"message_id":{"type":"string","description":"The ID of the message that was reacted to"},"reaction":{"type":"string","description":"The reaction that was added or removed. For classic tapbacks: love, like, dislike, laugh, emphasize, question. For emoji reactions: the emoji character (e.g. 😂, 👍, 🔥).","examples":["love","like","😂","👍"]},"action":{"type":"string","enum":["add","remove"],"description":"The action that was performed"}}}}}}