Search for a command to run...
API reference
messages:sendSend through a connected client using a Meta-compatible payload. IntelliPortal resolves the channel from client_ref and normalizes authentication, errors, request logging, and test mode.
/messages/sendSend from your backend with a key that has messages:send. The property named by type is forwarded in Meta's shape.
curl -X POST https://api.intelliconcierge.dev/v1/messages/send \
-H "Authorization: Bearer $INTELLI_API_KEY" \
-H "Content-Type: application/json" \
-d '{
"client_ref": "customer_42",
"to": "+254700000000",
"type": "text",
"text": { "body": "Your order is ready." }
}'| Property | Type | Description |
|---|---|---|
client_refrequired | string | Your stable identifier for the connected client and channel used to send. |
torequired | string | WhatsApp recipient phone number or Instagram-scoped user ID from a webhook. |
typerequired | enum | Selects the matching payload object: text, image, video, audio, document, sticker, location, contacts, interactive, or reaction. |
<type>required | object | The payload whose property name matches type. Its shape mirrors Meta's Cloud API. |
dry_run | boolean | Validate without delivery. With an ik_test_ key, sends are simulated unless `to` is one of your registered test recipients — those deliver for real. |
Media can use a public HTTPS link or an uploaded Meta media id. Free-form sends require an open 24-hour customer-service window.
| type | Payload essentials |
|---|---|
| text | body; optional preview_url |
| image | link or id; optional caption |
| video | link or id; optional caption |
| audio | link or id |
| document | link or id; optional caption and filename |
| sticker | link or id |
| location | latitude, longitude; optional name and address |
| contacts | Array of Meta contact objects |
| interactive | button, list, cta_url, and other Meta interactive shapes |
| reaction | message_id and emoji; an empty emoji removes the reaction |
{
"client_ref": "customer_42",
"to": "+254700000000",
"type": "text",
"text": {
"body": "Your order is ready.",
"preview_url": false
}
}/messages/send-templateTemplates are WhatsApp-only and can open a conversation outside the 24-hour service window. Reference a template you registered through the Templates API by template_ref and pass plain values — IntelliPortal resolves the template, checks that Meta has approved it, and builds the components for you. Only templates with status APPROVED are sendable; anything else returns template_not_approved instead of an opaque Meta error.
Prefer full control? Send a raw Meta template object instead — name, language, and components exactly as Meta's Cloud API takes them. Use one form or the other, not both.
curl -X POST https://api.intelliconcierge.dev/v1/messages/send-template \
-H "Authorization: Bearer $INTELLI_API_KEY" \
-H "Content-Type: application/json" \
-d '{
"client_ref": "customer_42",
"to": "+254700000000",
"template_ref": "order_ready",
"parameters": ["1842", "$12.50"]
}'| Property | Type | Description |
|---|---|---|
client_refrequired | string | Your stable identifier for the connected client whose WhatsApp Business Account holds the template. |
torequired | string | WhatsApp recipient phone number. |
template_refrequired | string | Template id from the Templates API, or the template name. Add language when the same name exists in several languages. |
language | string | Language code (e.g. en_US) to disambiguate template_ref by name. |
parameters | array | object | Body variable values. An array fills {{1}}, {{2}}… in order; an object matches named variables ({"name": "Ada"}) or numbered keys ({"1": "1842"}). |
header | object | Header variable, when the template has one: {"text": ...} for a text header, {"link": ...} or {"id": ...} for image / video / document (optional "filename" for documents), {"location": {...}} for location headers. |
buttons | array | Values for dynamic URL buttons, in button order — one string per {{1}} placeholder in a button URL. |
cards | array | Carousel templates only: one object per card, in card order — {"header": {"link" | "id": ...}, "parameters": [...], "buttons": [...]}. Every card needs its header media at send time; quick replies take the payload your webhook receives on tap, dynamic URLs take their suffix. |
dry_run | boolean | Validate without delivery. With an ik_test_ key, sends are simulated unless `to` is a registered test recipient. The dry-run response echoes the assembled template object. |
{
"client_ref": "customer_42",
"to": "+254700000000",
"template_ref": "order_ready",
"parameters": ["1842", "$12.50"]
}Template send errors
400 with a code your integration can branch on:| template_not_approved | The template exists but Meta hasn't approved it (or rejected it — the reason is included). |
| template_not_found | No template with that id or name on this client's WhatsApp Business Account. |
| ambiguous_template | The name exists in several languages — pass language or use the template id. |
| missing_parameters | The template body has variables you didn't supply; the message lists them. |
| parameter_count_mismatch | A positional parameters array has the wrong length. |
| missing_header | The template has a dynamic header and header wasn't supplied. |
| missing_button_parameters | The template has dynamic URL buttons and buttons wasn't supplied. |
| empty_parameter | A supplied body, header, button or card value is blank — Meta rejects empty parameters. |
| invalid_header_link | header.link isn't a public http(s) URL Meta could fetch. |
| missing_cards | The template is a carousel and cards wasn't supplied, or its length doesn't match the card count. |
| missing_card_buttons | A card's buttons list doesn't cover its quick replies and dynamic URL buttons; the error names the card. |
| template_layout_unsupported | Authentication templates and copy-code buttons can't be assembled from template_ref yet — send them as a raw template object. |
/mediaTrade bytes for a media id you can reference from sends — header.id on a template, image.id and friends on a free-form message. Meta keeps the upload for 30 days, so one upload serves many sends.
Prefer an id over link in production: a linked URL is re-fetched by Meta at every delivery, after the API has already accepted the message — if the fetch fails, the message silently dies with a failed status on your webhook. WhatsApp-only: Instagram takes media as a public URL on the send itself.
curl -X POST https://api.intelliconcierge.dev/v1/media \
-H "Authorization: Bearer $INTELLI_API_KEY" \
-F client_ref=customer_42 \
-F file=@menu.jpg{
"success": true,
"media_id": "1013859600285441",
"kind": "image",
"expires_in_days": 30
}Accepted uploads: image jpeg/png (≤5 MB), video mp4/3gpp (≤16 MB), audio aac/mp4/mpeg/amr/ogg (≤16 MB), documents pdf, Office formats and txt (≤100 MB), sticker webp (≤500 KB). Anything else returns media_type_unsupported or media_too_large before reaching Meta.
Do not send a separate channel parameter. IntelliPortal looks up the client and routes through its connected channel.
Instagram takes text and media
to to the Instagram-scoped sender ID from a webhook. Text is limited to 1,000 characters and must be sent inside Instagram's reply window.image, video, audio and document are supported and must carry link — Instagram fetches media from a public HTTPS URL and cannot use an uploaded media id, which returns media_link_required. A caption or filename has nowhere to go on an Instagram attachment, so the send succeeds and reports it under warnings; send the caption as its own text message if you need it.sticker, location, contacts, interactive and reaction have no Instagram equivalent and return channel_type_unsupported.Store message_id to correlate later delivery status webhooks. Also capture the X-Request-Id response header for operational tracing.
{
"success": true,
"message_id": "wamid.xxx"
}{
"success": true,
"message_id": "wamid.TEST_xxx",
"dry_run": true
}A warnings array appears when the message went out but the channel could not carry part of what you sent — a caption on an Instagram attachment, for instance. The send still succeeded; nothing needs retrying. Log these, because they mean content you supplied never reached the recipient.
{
"success": true,
"message_id": "mid.xxx",
"warnings": [
"caption is not supported on Instagram attachments and was not sent — send it as a separate text message if you need it."
]
}Retry-After on 429. Do not immediately repeat requests rejected by Meta without correcting the cause.402 trial_expired blocks live delivery after trial grace; dry-runs remain available for debugging.Copy the integration contract into your assistant, then let it tailor the implementation to your stack.
You are helping me build outbound messaging into my product using the Intelli Partner API (base URL: https://api.intelliconcierge.dev/v1). The WhatsApp/Instagram accounts I send from are already connected through Intelli; every request carries my Intelli API key and a client_ref naming the connected account.
Before writing code, ask me:
1. My backend framework/language.
2. Which channels I use (WhatsApp, Instagram or both) and whether I send templates, free-form messages, or both.
Environment variables (server-side only — the API key must NEVER reach the browser):
- INTELLI_API_KEY: ik_test_... keys simulate every send except to my registered test recipients (my own phone / Instagram username), which receive the real message; ik_live_... delivers for real. Auth header: Authorization: Bearer $INTELLI_API_KEY (scope messages:send).
A) Free-form messages — POST /messages/send
- Body: {"client_ref": "...", "to": "...", "type": "...", "<type>": {...}} mirroring Meta's Cloud API shape. WhatsApp types: text, image, video, audio, document, sticker, location, contacts, interactive, reaction. Media takes link (public HTTPS) or id (uploaded — see C).
- WhatsApp free-form only works inside the 24-hour customer-service window; outside it, send an approved template.
- Instagram: to is the Instagram-scoped user ID from a webhook; text (max 1000 chars) plus image/video/audio/document by link only. caption/filename can't be carried — the send succeeds and reports them under warnings.
- Never send a channel parameter: Intelli routes by client_ref.
B) Template messages — POST /messages/send-template
- Preferred shape: {"client_ref", "to", "template_ref": "<template id or name>", "language": "en_US" (only to disambiguate a name), "parameters": [...] or {...}, "header": {...}, "buttons": [...]}.
- parameters fills body variables: an array for positional {{1}}, {{2}}…, an object for named variables. header only when the template has a dynamic header: {"text": ...} or {"link"|"id": ...} (+ "filename" for documents). buttons: values for dynamic URL buttons, in order.
- Carousel templates add "cards": one object per card, in card order — {"header": {"link"|"id": ...}, "parameters": [...], "buttons": [...]}. Every card needs its header media at send time; quick replies take the payload my webhook receives on tap, dynamic URLs their suffix.
- Intelli resolves the template and refuses anything not APPROVED (400 template_not_approved, with Meta's rejection reason when there is one) and any missing or blank value (missing_parameters, parameter_count_mismatch, missing_header, missing_button_parameters, missing_cards, missing_card_buttons, empty_parameter, invalid_header_link — each error names exactly what's missing). Gate my own UI on the template being APPROVED.
- Full-control alternative: a raw Meta "template" object instead of template_ref (exactly one of the two). Authentication templates and copy-code buttons currently require the raw form (template_layout_unsupported otherwise).
C) Send media — POST /media (multipart/form-data: client_ref, file)
- Returns {"media_id", "kind", "expires_in_days": 30}. Reference it as header.id on template sends or image.id / video.id / … on free-form sends.
- Prefer an id over a link in production: Meta re-fetches a link at EVERY delivery, after the API has already returned 200 — an unreachable link kills the message asynchronously (a failed status on my webhook), which looks like silent non-delivery.
- Limits are enforced with clear errors: image jpeg/png ≤5 MB, video mp4/3gpp ≤16 MB, audio ≤16 MB, document pdf/Office/txt ≤100 MB, sticker webp ≤500 KB. WhatsApp-only — Instagram media travels by URL on the send.
D) Test mode
- dry_run: true validates everything but never delivers; the response carries dry_run: true and, on template_ref sends, the fully assembled template object so I can inspect what a live send would carry.
- With an ik_test_ key, only my registered test recipients receive real messages; any other recipient gets a simulated response with a hint. I register recipients in the Intelli portal (Sandbox → Test recipients — one WhatsApp number and one Instagram username).
E) Responses and errors
- Success: {"success": true, "message_id": "..."} — store message_id to correlate message.status webhooks (sent / delivered / read / failed). Delivery is asynchronous: 200 means Meta ACCEPTED the message, not that it arrived.
- A warnings array means the message went out but a field couldn't be carried — log it.
- Failures use one envelope with a stable code to branch on. 422 meta_rejected carries Meta's own code/message and sometimes a hint. Honor Retry-After on 429. 402 trial_expired blocks live sends after the trial; dry-run keeps working.
Write idiomatic, production-quality code for my stack, retrying only where the rules above allow it.