---
title: "Send one message"
method: POST
path: "/api/v1/messages"
tags: ["messages"]
---

# Send one message

`POST /api/v1/messages`

## Request

- Base URL: `https://app.blueticked.com`
- URL: `https://app.blueticked.com/api/v1/messages`
- Auth: HTTP bearer

Send one WhatsApp template, SMS, or email message to a contact, or create the contact in the same call with `to.create: true`.

Business-initiated WhatsApp messages (outside the 24-hour customer-service window) must use an approved template; within 24 hours of the customer's last message a free-form `body` is accepted instead of `template_id`.

Pass business_id for managed-business scoping; pass channel_id when you need to send from a specific WhatsApp number.

If channel_id is omitted for a WhatsApp template, Blueticked uses the number attached to that approved template.

Use Idempotency-Key on write calls to avoid duplicate sends.

The recipient is looked up in your contacts; one that is not there answers 404 not_found with `details.reason: "recipient_not_found"`. Pass `to.create: true` (optionally with `to.first_name` and `to.last_name`) to create the contact in the same call, exactly as POST /api/v1/contacts would: in the business you name, or your default business. It needs the `contacts:write` scope too. The answer says `contact_created`, and carries `contact_id` when the send created the contact or the key holds `contacts:read`.

A contact created by a send triggers your contact-created automations and the contact.created webhook, like any new contact. It has given no consent, so a marketing send with `to.create` is refused up front (`details.reason: "consent_required"`) and nothing is written; record the opt-in with POST /api/v1/contacts first. The contact is created only after every other check passes (the channel, the body, the wallet, the plan's contact limit, which answers 403 plan_limit_reached), so a refused send leaves none behind; if the send fails after it (the send queue unavailable, say), the contact stays.

Authorize WhatsApp sends with either `messages:send` or the least-privilege `whatsapp:send` scope.

Send a customer statement (template with PDF): Send a WhatsApp template that has a DOCUMENT header, attaching a per-recipient PDF by URL.

Send a template with a personalised link button: Send a WhatsApp template whose URL button ends in {{1}}, supplying the per-send link ending.

Send an SMS: Send a plain-text SMS to a contact (or create it with `to.create: true`), from a free-form body or a saved message template.

Send an email: Send a transactional email to a contact (or create it with `to.create: true`), with an optional subject line and reply-to.

Send a marketing email (HTML body): Send a marketing email with your own HTML body, rendered inside the branded shell with the required compliance footer.

Validate one message: Validate auth, contact lookup, channel readiness, template variables and opt-outs without sending.

## Headers

- `Idempotency-Key` string

## Request body

- object
  - `business_id` string, uuid, nullable
  - `channel` 'whatsapp' | 'sms' | 'email', required
  - `channel_id` string, uuid, nullable
  - `to` object, required
    - `phone` string: Phone in international form (+27821234567) or South African national form (082 123 4567); spaces and dashes are ignored. A number without a leading + or 00 is read as South African, so write a foreign number with its +. Stored and returned as E.164.
    - `email` string
    - `create` boolean
    - `first_name` string
    - `last_name` string
  - `body` string
  - `subject` string
  - `template_id` string, uuid
  - `template_variables` object
  - `template_buttons` object[]
    - `type` 'url', required
    - `index` integer, required
    - `url_suffix` string, required
  - `document` object
    - `id` string: The id of a document uploaded with POST /api/v1/documents, instead of `url`. It is sent only to the contact it was uploaded for; its own filename is used unless `filename` is given.
    - `url` string: https:// URL WhatsApp can fetch WITHOUT credentials, once, at send time. A short-lived pre-signed URL is the right shape for anything confidential — the file never has to be durably public.
    - `filename` string: The filename the recipient sees. WhatsApp caps this at 240. Required with `url`; optional with `id`.
  - `test_mode` boolean
  - `message_type` 'transactional' | 'marketing'
  - `html` string
  - `cc` string[]
  - `bcc` string[]
  - `reply_to` string

## Response `200`

Validated without queueing: returned when the request body sets test_mode:true. No provider send, no billing. A blu_test_ key on its own does NOT produce this: a sandbox key follows the normal path and returns 202 with status:"queued", simulating delivery from there. Set test_mode:true when you want the request validated and echoed back instead of queued.

- object
  - `test_mode` true, required
  - `status` 'validated', required
  - `channel` 'whatsapp' | 'sms' | 'email', required
  - `business_id` string, uuid, nullable, required
  - `to` string, required
  - `contact_id` string, uuid, nullable, required
  - `would_create_contact` boolean, required
  - `channel_id` string, uuid, nullable, required
  - `template_id` string, uuid, nullable, required
  - `template_name` string, nullable, required
  - `rendered_body_preview` string, required
  - `would_enqueue` boolean, required
  - `request_id` string, required

## Other responses

- `202`: Success
- `400`: Validation failed
- `401`: Unauthorized: missing or invalid API key
- `402`: Insufficient wallet balance
- `403`: Forbidden: scope, plan limit, or suspension
- `404`: Not found
- `409`: Conflict (idempotency-key reuse or resource in use)
- `413`: Request body above the endpoint's size limit
- `422`: Understood but refused: the request is well-formed, the action is not permitted for this resource
- `429`: Rate limited
- `500`: Internal error
- `502`: An upstream provider refused or failed the request
- `503`: Channel or send queue unavailable

---

[API](https://skmtc.dev/blueticked/apis/blueticked-api.md) · [All operations](https://skmtc.dev/blueticked/apis/blueticked-api/llms.txt) · [OpenAPI document](https://skmtc.dev/blueticked/apis/blueticked-api/revisions/07132284f4ff?raw)
