## Send a message `POST /send-api-message` Send a message to a phone number via iMessage or SMS. If the recipient has iMessage, the message is delivered as an iMessage (blue bubble). Otherwise, it falls back to SMS automatically. ### Request body - `message` (string) — The text content of the message. Required unless mediaAttachmentUrl or audioAttachmentUrl is supplied. - `phone` (string, required) — Recipient phone number. Accepts many formats — we normalize to E.164 (e.g. +15551234567). - `lineId` (string (UUID v4)) — Optional sender line override. Use the lineId value returned from GET /get-lines. When omitted, messages are load balanced across available lines. Rejected on trial accounts — see Trial Accounts. - `mediaAttachmentUrl` (string) — URL to an image, video, or contact card attachment. - `audioAttachmentUrl` (string) — URL to an audio file sent as a voice memo. - `enableAiVoiceMemo` (boolean) — When true, generates an AI voice memo from the message text using text-to-speech. - `shouldAutoCreateContact` (boolean) — Defaults to true. When enabled and a supported CRM is connected (HighLevel or HubSpot), automatically creates the contact in your CRM if they don't already exist. - `firstName` (string) — First name to persist on the auto-created Project Blue contact for this recipient. Applies to accounts without a connected CRM (external/API source). - `lastName` (string) — Last name to persist on the auto-created Project Blue contact for this recipient. Applies to accounts without a connected CRM (external/API source). - `email` (string) — Email address to persist on the auto-created Project Blue contact for this recipient. Applies to accounts without a connected CRM (external/API source). - `customFields` (object) — Arbitrary JSON metadata (e.g. order IDs, links) persisted on the auto-created contact and shown in the contact details panel in the Project Blue app. Plain object only; max 10,000 characters when JSON-serialized. - `idempotencyKey` (string) — 1 to 200 characters. Values outside those bounds are silently ignored, not rejected. When omitted, the key defaults to your user id plus the destination plus the message text, so an identical retry within the hour is collapsed rather than sent twice. The `phone` parameter is flexible — we accept formats like `(555) 123-4567`, `555.123.4567`, `+15551234567`, and more. All numbers are normalized to E.164 format before sending. When `lineId` is provided, we route through that line. If it is omitted, message sends continue to use default load balancing across your available lines. > **Identical sends are collapsed for an hour** > > With no `idempotencyKey`, the key defaults to your user id plus the destination plus the message text. Sending the same text to the same number twice within the hour returns `200` with `deduped: true` and **does not send a second message**. This is the usual reason a first integration looks like it succeeded but nothing arrived on the retry — change the text, or pass a distinct `idempotencyKey`, when a repeat is intentional. A replay returns `status: "done"` when the original send finished, or `status: "processing"` with `messageType` and `devicePhoneNumber` still `null` while it is in flight. Both carry `deduped: true`; a first-time send never does. **Request — Basic** ```bash curl -X POST https://api.tryprojectblue.com/send-api-message \ -H "Authorization: Bearer YOUR_API_KEY" \ -H "Content-Type: application/json" \ -d '{ "message": "Hey! Just following up on our conversation.", "phone": "+15551234567" }' ``` **Request — Media** Attach an image, video, or contact card by URL. ```bash curl -X POST https://api.tryprojectblue.com/send-api-message \ -H "Authorization: Bearer YOUR_API_KEY" \ -H "Content-Type: application/json" \ -d '{ "message": "Check out this property!", "phone": "+15551234567", "mediaAttachmentUrl": "https://example.com/property-photo.jpg" }' ``` **Request — AI voice memo** Generates speech from the message text — no audio file needed. ```bash curl -X POST https://api.tryprojectblue.com/send-api-message \ -H "Authorization: Bearer YOUR_API_KEY" \ -H "Content-Type: application/json" \ -d '{ "message": "Hey, wanted to quickly touch base about your appointment tomorrow.", "phone": "+15551234567", "enableAiVoiceMemo": true }' ``` **Request — Line override** Force a specific sending line instead of load balancing. ```bash curl -X POST https://api.tryprojectblue.com/send-api-message \ -H "Authorization: Bearer YOUR_API_KEY" \ -H "Content-Type: application/json" \ -d '{ "message": "Hi from a specific line.", "phone": "+15551234567", "lineId": "a3f8c2d1-b4e9-4f2a-c8d3-e1f0a2b3c4d5" }' ``` **Response — 200 OK** ```json { "success": true, "status": "done", "message": "Message added to queue", "messageType": "iMessage", "phone": "+15551234567", "devicePhoneNumber": "+15559876543", "mediaAttachmentUrl": null, "audioAttachmentUrl": null } ``` **Response — 200 — replayed** A retry collapsed onto an earlier send. Nothing was sent again. ```json { "success": true, "status": "done", "message": "Message added to queue", "messageType": "iMessage", "phone": "+15551234567", "devicePhoneNumber": "+15559876543", "mediaAttachmentUrl": null, "audioAttachmentUrl": null, "deduped": true } ``` **Response — 200 — in flight** The original send is still processing, so the channel is not resolved yet. ```json { "success": true, "status": "processing", "message": "Message added to queue", "phone": "+15551234567", "messageType": null, "devicePhoneNumber": null, "deduped": true } ``` **Response — 400 — invalid line** ```json { "error": "Invalid lineId. Expected a UUID v4 token returned from GET /get-lines." } ```