> For the complete documentation index, see [llms.txt](https://docs.zenfinder.ai/llms.txt). Markdown versions of documentation pages are available by appending `.md` to page URLs; this page is available as [Markdown](https://docs.zenfinder.ai/reference/messages.md).

# Messages

Send messages, read receipts and typing indicators.

## Send a message, read receipt, or typing indicator

> One polymorphic endpoint (mirrors the WhatsApp Cloud API's\
> \`POST /{PHONE\_NUMBER\_ID}/messages\`). The request body shape determines\
> the behavior — see the \`oneOf\` variants. A \`200\` response means the\
> message was \*\*accepted\*\*, not delivered; delivery status arrives\
> later via webhook.\
> \
> \- \`text\` / \`image\` / \`document\` / \`location\` / \`interactive\` are\
> &#x20; \*\*free-form\*\* and require an open service window (\`400 #131047\` if\
> &#x20; closed).\
> \- \`template\` may be sent \*\*any time\*\*, is billed per (category,\
> &#x20; recipient country), and supports \`?dry\_run=true\` to get a price\
> &#x20; quote without sending or charging anything.\
> \- A \`status:"read"\` body sends a read receipt for an inbound message.\
> \- A \`typing\_indicator\` body sends a typing indicator.<br>

```json
{"openapi":"3.0.3","info":{"title":"ZenFinder Business API","version":"1.0"},"tags":[{"name":"Messages","description":"Send messages, read receipts and typing indicators."}],"servers":[{"url":"https://api.zenfinder.ai/business/v1.0","description":"Production"}],"security":[{"bearerAuth":["zenfinder_business_messaging"]}],"components":{"securitySchemes":{"bearerAuth":{"type":"http","scheme":"bearer","bearerFormat":"zf_live_...","description":"API key minted from Business Manager, sent as `Authorization: Bearer\n<key>`. Scopes: `zenfinder_business_messaging`,\n`zenfinder_business_management`.\n"}},"parameters":{"PhoneNumberId":{"name":"phoneNumberId","in":"path","required":true,"description":"The sender's opaque `PHONE_NUMBER_ID` (never the MSISDN itself).","schema":{"type":"string"}},"IdempotencyKey":{"name":"Idempotency-Key","in":"header","required":false,"description":"Replaying the same key within 24h for this token+endpoint returns the original response instead of re-executing.","schema":{"type":"string"}}},"schemas":{"TextMessage":{"type":"object","required":["to","type","text"],"properties":{"messaging_product":{"type":"string","enum":["zenfinder","whatsapp"]},"to":{"type":"string","description":"Recipient in E.164 (leading `+` and country code required)"},"type":{"type":"string","enum":["text"]},"text":{"type":"object","required":["body"],"properties":{"body":{"type":"string","maxLength":4096},"preview_url":{"type":"boolean","default":false}}},"context":{"type":"object","description":"Reply threading.","properties":{"message_id":{"type":"string"}}}}},"MediaMessage":{"type":"object","required":["to","type"],"properties":{"messaging_product":{"type":"string","enum":["zenfinder","whatsapp"]},"to":{"type":"string"},"type":{"type":"string","enum":["image","document"]},"image":{"$ref":"#/components/schemas/MediaObject"},"document":{"$ref":"#/components/schemas/MediaObject"},"context":{"type":"object","properties":{"message_id":{"type":"string"}}}}},"MediaObject":{"type":"object","description":"Exactly one of `id` (from a prior media upload) or `link` (a public URL) is required.","properties":{"id":{"type":"string","description":"MEDIA_ID from a prior upload."},"link":{"type":"string","format":"uri"},"caption":{"type":"string","maxLength":1024},"filename":{"type":"string","description":"document type only."}}},"LocationMessage":{"type":"object","required":["to","type","location"],"properties":{"messaging_product":{"type":"string","enum":["zenfinder","whatsapp"]},"to":{"type":"string"},"type":{"type":"string","enum":["location"]},"location":{"type":"object","required":["latitude","longitude"],"properties":{"latitude":{"type":"number","minimum":-90,"maximum":90},"longitude":{"type":"number","minimum":-180,"maximum":180},"name":{"type":"string"},"address":{"type":"string"}}}}},"InteractiveMessage":{"type":"object","required":["to","type","interactive"],"properties":{"messaging_product":{"type":"string","enum":["zenfinder","whatsapp"]},"to":{"type":"string"},"type":{"type":"string","enum":["interactive"]},"interactive":{"type":"object","required":["type","body"],"properties":{"type":{"type":"string","enum":["button","list","cta_url","location_request","address_message","carousel"]},"header":{"type":"object","properties":{"text":{"type":"string","maxLength":60}}},"body":{"type":"object","required":["text"],"properties":{"text":{"type":"string","maxLength":1024}}},"footer":{"type":"object","properties":{"text":{"type":"string","maxLength":60}}},"action":{"type":"object","description":"Shape depends on `interactive.type`:\n- `button`: `{ buttons: [{ type:\"reply\", reply:{ id, title (<=20 chars) } }] }` (1–3 buttons)\n- `list`: `{ button: \"<=20 chars\", sections: [{ title?, rows: [{ id, title (<=24), description? (<=72) }] }] }` (1–10 sections, <=10 rows total)\n- `cta_url`: `{ parameters: { display_text: \"<=60 chars\", url: \"https://...\" } }`\n- `location_request`: no extra fields — body + a send-location button.\n- `address_message`: `{ button?: \"<=20 chars\", country?: \"2-letter ISO code\" }`\n- `carousel`: `{ cards: [{ image:{ link }, body:{ text (<=160) }, action:{ buttons:[{type:\"reply\",reply:{id,title}}] } }] }` (1–10 cards, 1–2 buttons each, same button config across all cards)\n"}}}}},"TemplateMessage":{"type":"object","required":["to","type","template"],"properties":{"to":{"type":"string"},"type":{"type":"string","enum":["template"]},"template":{"type":"object","required":["name","language"],"properties":{"name":{"type":"string"},"language":{"type":"object","required":["code"],"properties":{"code":{"type":"string"}}},"components":{"type":"array","description":"Parameter values for the approved template's BODY (and TEXT HEADER, if any). Only `type:\"text\"` parameters are supported.","items":{"type":"object","properties":{"type":{"type":"string","enum":["body","header"]},"parameters":{"type":"array","items":{"type":"object","properties":{"type":{"type":"string","enum":["text"]},"text":{"type":"string"}}}}}}}}}}},"ReadReceipt":{"type":"object","required":["status","message_id"],"properties":{"status":{"type":"string","enum":["read"]},"message_id":{"type":"string"}}},"TypingIndicator":{"type":"object","required":["typing_indicator"],"properties":{"typing_indicator":{"type":"object","required":["type"],"properties":{"type":{"type":"string","enum":["text"]}}},"message_id":{"type":"string"}}},"MessageAcceptedResponse":{"type":"object","properties":{"messaging_product":{"type":"string"},"contacts":{"type":"array","items":{"type":"object","properties":{"input":{"type":"string"},"zen_id":{"type":"string"}}}},"messages":{"type":"array","items":{"type":"object","properties":{"id":{"type":"string"},"message_status":{"type":"string","enum":["accepted"]}}}}}},"DryRunPriceResponse":{"type":"object","description":"Returned only for `type:\"template\"` sends with `?dry_run=true` — no message is sent or charged.","properties":{"messaging_product":{"type":"string"},"pricing":{"type":"object","properties":{"billable":{"type":"boolean"},"amount_minor":{"type":"integer","description":"Price in the ZBA currency's minor units (e.g. kobo, cents)."},"currency":{"type":"string"},"category":{"type":"string","enum":["MARKETING","UTILITY","AUTHENTICATION"]},"reason":{"type":"string","enum":["service","free_entry_point","utility_in_window","no_rate","charged"]}}}}},"SimpleSuccessResponse":{"type":"object","properties":{"success":{"type":"boolean"},"messaging_product":{"type":"string"}}},"Error":{"type":"object","description":"Graph/WhatsApp-Cloud-API-compatible error envelope. Numeric `code`\nvalues are reused verbatim from the WhatsApp Cloud API:\n\n| Code | HTTP | Meaning |\n|---|---|---|\n| 0 | 401 | AuthException |\n| 3 | 403 | Capability or permissions issue |\n| 10 | 403 | Permission denied |\n| 100 | 400 | Invalid parameter |\n| 190 | 401 | Access token invalid or expired |\n| 200 | 403 | Permission denied (missing scope / not authorised for this resource) |\n| 130429 | 429 | Rate limit hit |\n| 131000 | 500 | Something went wrong |\n| 131008 | 400 | Required parameter is missing |\n| 131009 | 400 | Parameter value is not valid |\n| 131016 | 503 | Service unavailable |\n| 131021 | 400 | Recipient cannot be sender |\n| 131026 | 400 | Message undeliverable |\n| 131037 | 403 | Display name not approved |\n| 131042 | 402 | Business eligibility payment issue (insufficient wallet balance) |\n| 131047 | 400 | Re-engagement message (service window closed — send a template) |\n| 131048 | 400 | Spam rate limit hit |\n| 131049 | 400 | Per-user marketing message limit reached |\n| 131050 | 400 | User stopped marketing messages (opted out) |\n| 131051 | 400 | Unsupported message type |\n| 131052 | 400 | Media download error |\n| 131053 | 400 | Media upload error |\n| 132000 | 400 | Template parameter count mismatch |\n| 132001 | 400 | Template does not exist / not approved |\n| 132005 | 400 | Template hydrated text too long |\n| 132007 | 400 | Template format character policy violated |\n| 132012 | 400 | Template parameter format mismatch |\n| 132015 | 400 | Template is paused |\n| 132016 | 400 | Template is disabled |\n| 133010 | 400 | Sender not registered |\n","properties":{"error":{"type":"object","properties":{"message":{"type":"string"},"type":{"type":"string"},"code":{"type":"integer"},"error_subcode":{"type":"integer","nullable":true},"error_data":{"type":"object","properties":{"messaging_product":{"type":"string"},"details":{"type":"string"}}},"zen_trace_id":{"type":"string"}}}}}},"responses":{"BadRequest":{"description":"Invalid, missing, or semantically rejected parameters (codes `100`, `131008`, `131009`, `131021`, `131026`, `131037`, `131047`–`131053`, `132000`–`132016`, `133010`, etc. — see `Error` schema).","content":{"application/json":{"schema":{"$ref":"#/components/schemas/Error"}}}},"Unauthorized":{"description":"Missing, malformed, revoked or expired Bearer token (codes `0`, `190`).","content":{"application/json":{"schema":{"$ref":"#/components/schemas/Error"}}}},"PaymentRequired":{"description":"Wallet balance can't cover this send (code `131042`).","content":{"application/json":{"schema":{"$ref":"#/components/schemas/Error"}}}},"Forbidden":{"description":"Token lacks the required scope, or isn't authorised for this sender/ZBA/media (codes `3`, `10`, `200`).","content":{"application/json":{"schema":{"$ref":"#/components/schemas/Error"}}}},"DefaultError":{"description":"Unexpected error (code `131000`) or upstream unavailability (`131016`).","content":{"application/json":{"schema":{"$ref":"#/components/schemas/Error"}}}}}},"paths":{"/{phoneNumberId}/messages":{"post":{"operationId":"sendMessage","tags":["Messages"],"summary":"Send a message, read receipt, or typing indicator","description":"One polymorphic endpoint (mirrors the WhatsApp Cloud API's\n`POST /{PHONE_NUMBER_ID}/messages`). The request body shape determines\nthe behavior — see the `oneOf` variants. A `200` response means the\nmessage was **accepted**, not delivered; delivery status arrives\nlater via webhook.\n\n- `text` / `image` / `document` / `location` / `interactive` are\n  **free-form** and require an open service window (`400 #131047` if\n  closed).\n- `template` may be sent **any time**, is billed per (category,\n  recipient country), and supports `?dry_run=true` to get a price\n  quote without sending or charging anything.\n- A `status:\"read\"` body sends a read receipt for an inbound message.\n- A `typing_indicator` body sends a typing indicator.\n","parameters":[{"$ref":"#/components/parameters/PhoneNumberId"},{"name":"dry_run","in":"query","description":"For `type:\"template\"` sends only — return a price quote instead of sending.","schema":{"type":"boolean","default":false}},{"$ref":"#/components/parameters/IdempotencyKey"}],"requestBody":{"required":true,"content":{"application/json":{"schema":{"oneOf":[{"$ref":"#/components/schemas/TextMessage"},{"$ref":"#/components/schemas/MediaMessage"},{"$ref":"#/components/schemas/LocationMessage"},{"$ref":"#/components/schemas/InteractiveMessage"},{"$ref":"#/components/schemas/TemplateMessage"},{"$ref":"#/components/schemas/ReadReceipt"},{"$ref":"#/components/schemas/TypingIndicator"}]}}}},"responses":{"200":{"description":"Accepted","content":{"application/json":{"schema":{"oneOf":[{"$ref":"#/components/schemas/MessageAcceptedResponse"},{"$ref":"#/components/schemas/DryRunPriceResponse"},{"$ref":"#/components/schemas/SimpleSuccessResponse"}]}}}},"400":{"$ref":"#/components/responses/BadRequest"},"401":{"$ref":"#/components/responses/Unauthorized"},"402":{"$ref":"#/components/responses/PaymentRequired"},"403":{"$ref":"#/components/responses/Forbidden"},"default":{"$ref":"#/components/responses/DefaultError"}}}}}}
```


---

# Agent Instructions
This documentation is published with GitBook. GitBook is the documentation platform designed so that both humans and AI agents can read, navigate, and reason over technical content effectively. Learn more at gitbook.com.

## Querying This Documentation
If you need additional information that is not directly available in this page, you can query the documentation dynamically by asking a question.

Perform an HTTP GET request on the current page URL with the `ask` query parameter, and the optional `goal` query parameter:

```
GET https://docs.zenfinder.ai/reference/messages.md?ask=<question>&goal=<endgoal>
```

`ask` is the immediate question: it should be specific, self-contained, and written in natural language.
`goal` is optional and describes the broader end goal you are ultimately trying to accomplish on behalf of the user. GitBook uses it to tailor the answer towards what is most useful for that goal.

The response will contain a direct answer to the question and relevant excerpts and sources from the documentation.

Use this mechanism when the answer is not explicitly present in the current page, you need clarification or additional context, or you want to retrieve related documentation sections.
