POST /devices/{id}/messages
POST /devices/{id}/messages
Section titled “POST /devices/{id}/messages”Sends text, an image or a file from a number you rent. Which one a multipart body carries is decided by the
file’s own content type, not by a field you set: an image is rendered inline with its caption, a document
arrives as a file the recipient opens, named.
| Scope | send and an active/past_due rental for this device |
| Body | application/json with phone + message, or multipart/form-data with file + phone + optional caption |
| Idempotency | Required — Idempotency-Key, 1–128 characters |
JSON — text
Section titled “JSON — text”| Field | Type | Required | Validation |
|---|---|---|---|
phone |
string | yes | E.164 after stripping separators |
message |
string | yes | Non-empty; trimmed length at most 4096 |
curl -X POST https://app.maukirim.com/api/v1/devices/gwd_…/messages \ -H "Authorization: Bearer mk_live_…" -H "Content-Type: application/json" \ -H "Idempotency-Key: reply-8821-1" \ -d '{"phone": "+6281234567890", "message": "Your order is on the way."}'multipart — image or file
Section titled “multipart — image or file”| Part | Type | Required | Validation |
|---|---|---|---|
file |
File | yes | JPEG/PNG inline images (at most 5 MiB), or named files (PDF, DOCX, XLSX, PPTX, CSV, TXT, ZIP, WebP, GIF, MP3, ICP, ICS; at most 16 MiB). WebP, GIF and MP3 are files, not inline images or voice notes, and require matching .webp, .gif or .mp3 filenames. Known formats are checked against their bytes; opaque .icp is accepted by extension only. Use application/octet-stream with a .icp filename; .ics uses text/calendar. |
phone |
string | yes | E.164 |
caption |
string | no | At most 1024 characters; omitted when blank. An image shows it under the picture; a document shows it above the file |
curl -X POST https://app.maukirim.com/api/v1/devices/gwd_…/messages \ -H "Authorization: Bearer mk_live_…" \ -H "Idempotency-Key: reply-8821-2" \ -F "file=@receipt.png;type=image/png" \ -F "phone=+6281234567890" -F "caption=Your receipt"The same call with a document. The filename you send is the filename the recipient reads, so send the real name rather than a placeholder:
curl -X POST https://app.maukirim.com/api/v1/devices/gwd_…/messages \ -H "Authorization: Bearer mk_live_…" \ -H "Idempotency-Key: invoice-8821-1" \ -F "file=@Invoice-8821.pdf;type=application/pdf" \ -F "phone=+6281234567890" -F "caption=Your invoice"Response
Section titled “Response”200 — a text send returns the queued batch:
{ "ok": true, "batchId": "gwb_…", "created": true, "progress": { "batchId": "gwb_…", "total": 1, "counts": { "queued": 1, "sending": 0, "accepted": 0, "failed": 0, "uncertain": 0, "cancelled": 0 }, "terminal": false, "items": [{ "id": "gwi_…", "recipient": "+6281234567890", "kind": "text", "sendState": "queued", "deliveryState": null, "attempts": 0, "messageId": null, "errorCode": null, "completedAt": null }] }}A multipart send is the same object plus the id of the upload that was stored for it:
{ "ok": true, "media": { "id": "gwm_…" }, "batchId": "gwb_…", "created": true, "progress": { "…": "…" } }| Field | Meaning |
|---|---|
media.id |
The stored upload, present only for a multipart send. media is absent for text. |
created |
false when the Idempotency-Key replayed an identical request — the earlier batch is returned |
progress |
A snapshot, not a subscription. There is no batch-status endpoint; delivery state arrives on your webhook. |
items[].sendState |
queued → sending → accepted/failed. uncertain means the attempt may or may not have arrived and was not retried. |
items[].deliveryState |
The recipient’s receipt, which arrives later and is separate from sendState |
The message body and caption are never echoed back.
Errors
Section titled “Errors”| HTTP | Code | Condition |
|---|---|---|
| 400 | invalid_input |
Missing/blank/E.164-invalid phone; missing/empty message; missing file part; attachment of an unsupported type, empty, over its size limit, or whose bytes do not match the declared type; non-string caption; Idempotency-Key missing; invalid item kind |
| 404 | rental_not_found |
Guard failure (not your device, or no active rental) |
| 404 | device_not_found |
Device unknown at enqueue |
| 404 | gateway_not_found |
The device’s gateway row is missing |
| 409 | gateway_disabled |
The gateway is switched off |
| 409 | batch_idempotency_conflict |
Same key, different normalized request |
| 410 | media_unavailable |
Media unknown, owned by another account, expired (7 days), or no bucket configured |
| 413 | batch_too_large |
message over 4096, caption over 1024, or Idempotency-Key over 128 |
| 401/403 | auth codes | Needs scope send |
| 500 | server_error |
Unexpected |