> For clean Markdown of any page, append .md to the page URL. > For a complete documentation index, see https://docs.getdial.ai/api-reference/rest-api/messages/reply-to-message/llms.txt. > For AI client integration (Claude Code, Cursor, etc.), connect to the MCP server at https://docs.getdial.ai/_mcp/server. # Reply to a message POST https://api.getdial.ai/api/v1/messages/{messageId}/reply Content-Type: application/json Sends a threaded reply or a reaction targeted at an existing message. **The sender is derived from the target message.** The reply goes out from the Dial number the target message belongs to, to the other party of that message — there is no `to` or `fromNumberId`. A reply always stays in the conversation the target message is part of. The request carries **exactly one** of: - `body` — a text reply. On iMessage numbers it is delivered as a native threaded reply (quoting the target message) when the recipient supports threads, and as a regular message otherwise. On WhatsApp numbers it is delivered as a native quoted reply (the target message is quoted above the reply). On standard numbers it is delivered as a regular SMS. - `reaction` — a reaction to the target message: one of the six reaction names (`love`, `like`, `dislike`, `laugh`, `emphasize`, `question`) or a **single emoji** (one visible symbol — skin-tone and multi-person emoji count as one; anything else is rejected with `400`). On iMessage numbers the reaction is delivered natively (a Tapback). When the recipient can only receive SMS — including everything sent from standard numbers — an **emoji** reaction is delivered as a regular message whose body is the emoji, and a reaction **name** is rejected with `400` (names have no SMS rendering; send an emoji instead). On **WhatsApp** numbers the reaction is delivered natively too, and a reaction **name** is mapped to its emoji — 👍 `like`, ❤️ `love`, 👎 `dislike`, 😂 `laugh`, ‼️ `emphasize`, ❓ `question` — so both a name and a single emoji react natively. **Targets.** Any message on the account can be targeted, with one current restriction: on iMessage numbers the target must be an **inbound** message — replying to your own sent messages isn't supported on those numbers yet. Standard and WhatsApp numbers accept targets in both directions. On **WhatsApp** numbers the target may be a **group** message — the reply or reaction is then delivered to the group; on other channels a group message cannot be the target and is rejected with `400`. A **reaction cannot itself be the target** — replying or reacting to a reaction is rejected with `400`. **A `body` reply on an iMessage number also needs a threadable target.** A threaded reply attaches to the target by the message id its channel carries, and only messages delivered over iMessage or RCS carry one. A target that arrived over **SMS** — the fallback used when the other party has neither — has nothing to attach to, so a `body` reply to it is rejected with `400`. Send a new message instead (`POST /api/v1/messages`): the same conversation takes it over SMS, it just isn't quoted on the recipient's device. A `reaction` on such a target is unaffected and follows the reaction rules above (an emoji is delivered as a regular message; a reaction name is rejected with `400`). **On WhatsApp numbers both forms are native**, one-to-one or in a group: a `reaction` lands on the target message itself, and a `body` is a quoted reply. A reply carries text only — to send media, send a new message in the same conversation (`POST /api/v1/messages`). The created message is recorded like any outbound message and billed the same as Send a message. Its `replyToId` points at the target message; `reaction` carries the reaction string when one was sent (a reaction delivered natively has an empty `body`; one delivered as a regular message carries the emoji in `body` too). Not idempotent — retrying a failed request can send a duplicate reply. On an ambiguous failure, confirm via List messages before re-sending. Reference: https://docs.getdial.ai/api-reference/rest-api/messages/reply-to-message ## Authentication - `Authorization` header (bearer token, required) — Your Dial API key, sent as `Authorization: Bearer sk_live_...` ## Request ### Path parameters - `messageId` (string, required) — ID of the message to reply or react to (see List messages). ### Body (application/json) This endpoint expects an object. - `body` (string, optional) — Reply text. Exactly one of `body` and `reaction` is required. On a standard number the same content-size limit as Send a message applies (1530 characters, or 670 if the text uses characters outside the basic SMS alphabet); a longer reply is rejected with `400`. - `reaction` (string, optional) — Reaction to send: `love`, `like`, `dislike`, `laugh`, `emphasize`, `question`, or a single emoji. Exactly one of `body` and `reaction` is required. ## Response ### 201 Reply queued. The created message, with `replyToId` set to the target message's ID. - `message` (Message, optional) ## Errors ### 400 Bad Request Error The request body failed validation. - `error` (ErrorError, optional) — An error message, or a validation-error object for 400 responses. ### 401 Unauthorized Error Missing or invalid API key. - `error` (ErrorError, optional) — An error message, or a validation-error object for 400 responses. ### 402 Payment Required Error Insufficient Dial credit. The number is pay-as-you-go and the account's credit balance is at or below zero. Add credit or start a subscription. (error code `insufficient_credit`.) - `error` (ErrorError, optional) — An error message, or a validation-error object for 400 responses. ### 403 Forbidden Error WhatsApp has limited this number's access, so it can't start a new conversation with this recipient yet. Nothing was sent. Replies to people who have messaged the number still go through, and retrying right away fails the same way. (error code `whatsapp_limited`.) - `error` (ErrorError, optional) — An error message, or a validation-error object for 400 responses. ### 404 Not Found Error The requested resource was not found on this account. - `error` (ErrorError, optional) — An error message, or a validation-error object for 400 responses. ### 429 Too Many Requests Error The recipient is rate-limited. New contacts on iMessage numbers can only receive a limited number of messages until they reply; once they respond, the limit lifts. (error code `rate_limited`.) - `error` (ErrorError, optional) — An error message, or a validation-error object for 400 responses. ## Types ### Message - `id` (string, optional) - `phoneNumberId` (string, optional) - `from` (string, optional) — Sender in E.164 format. On an inbound group message this is the participant who sent it — not the group, and not your own line. - `to` (string, optional, nullable) — Recipient in E.164 format, and **null exactly when `groupId` is set** — in both directions. A group message is addressed to the group, and a group is not a phone number. Which of your numbers the conversation is on is `phoneNumberId`, which is set on every message. Code that reads `to` as "my number" must read `phoneNumberId` instead. - `groupId` (string, optional, nullable) — The group conversation this message belongs to, or null for a one-to-one conversation. A Dial ID (see List groups) — never the channel's own group identifier. - `body` (string, optional) - `direction` (enum, optional) - Allowed values: `inbound`, `outbound` - `channel` (enum, optional) — The channel the message was delivered on. `sms` for SMS/call numbers. For iMessage numbers, inbound messages report the channel actually used — `imessage`, `rcs`, or `sms`. Outbound iMessage sends report `unknown`, because the iMessage channel does not confirm which channel was ultimately used. `whatsapp` is a channel in its own right — a WhatsApp registration on an iMessage line — and is reported in both directions. Every group message is `whatsapp` today. - Allowed values: `sms`, `imessage`, `rcs`, `whatsapp`, `unknown` - `service` (enum, optional) — For iMessage numbers, the confirmed delivery channel. Null for SMS/call numbers (where `channel` already says `sms`). Deprecated — prefer `channel`, which is authoritative. - Allowed values: `imessage`, `rcs`, `sms` - `status` (enum, optional) — Delivery status. Outbound SMS/call messages start at `sent` and reach a terminal `delivered`, `undelivered`, or `failed`. Outbound iMessage messages are `unknown` (the iMessage channel does not report delivery). Inbound messages are `received`. - Allowed values: `sent`, `delivered`, `undelivered`, `failed`, `unknown`, `received` - `statusError` (string, optional, nullable) — A human-readable reason, present only when `status` is `undelivered` or `failed` (for example, when a carrier rejects the message). Null otherwise. - `deliveryState` (enum, optional) — The delivery axis — the same information as `status`, in the vocabulary that says what to do about it. `pending` means wait for a disposition (`status: "sent"`); `unconfirmed` means never expect one (`status: "unknown"`, an iMessage send, or a WhatsApp send before its first receipt). Inbound messages are `delivered`. - Allowed values: `pending`, `delivered`, `undelivered`, `failed`, `unconfirmed` - `readState` (enum, optional) — The read axis, independent of delivery — a message can be both delivered and read, which `status` alone could never express. `unsupported` on channels that report no reads (SMS) and on every inbound message, since Dial does not track whether you read one. iMessage and WhatsApp report reads, so an outbound message there is `unread` until it is `read`. In a group, `read` means every recipient has read it — never just somebody. - Allowed values: `unread`, `read`, `unsupported` - `readAt` (datetime, optional, nullable) — When the message was read, or null. Always null on inbound messages and on channels that report no reads. - `deliveryError` (string, optional, nullable) — The current name for `statusError`; identical value. A vendor-neutral reason, present only on an `undelivered` or `failed` message. - `media` (list of MessageMedia, optional) — Media attachments on the message, in send order. Empty for plain text messages. - `replyToId` (string, optional, nullable) — ID of the message this one replies or reacts to. Set on messages created via Reply to a message, and on inbound threaded replies and reactions received on iMessage numbers. Null for ordinary messages, or when the target of an inbound reply isn't a message on your account. - `reaction` (string, optional, nullable) — The reaction this message carries — a reaction name (`love`, `like`, `dislike`, `laugh`, `emphasize`, `question`) or an emoji — when the message is a reaction, sent or received. Null otherwise. A reaction delivered natively has an empty `body`; a reaction delivered as a regular message over SMS carries the emoji in `body` too. - `createdAt` (datetime, optional) ### ErrorError An error message, or a validation-error object for 400 responses. ### MessageMedia A media attachment on a message, hosted by Dial. - `id` (string, optional) — Public media ID — an unguessable 32-character token. - `url` (string, optional) — Stable public URL serving the media (see Get public media). Safe to use directly as an image source; requires no authentication. - `contentType` (string, optional) — MIME type of the media. - `originalUrl` (string, optional, nullable) — The caller-supplied source URL on outbound messages. Null on inbound messages and when the media was uploaded directly as bytes — inbound media is always served from `url`. ## Examples **Request** ```json {} ``` **Response** ```json { "message": { "id": "string", "phoneNumberId": "string", "from": "+14155550123", "to": "+14155559876", "groupId": null, "body": "string", "direction": "inbound", "channel": "sms", "service": "imessage", "status": "delivered", "statusError": null, "deliveryState": "delivered", "readState": "unread", "readAt": null, "deliveryError": null, "media": [ { "id": "a3f9c2d41e8b4f0a9c6d2e7b5a1f8c30", "url": "https://getdial.ai/public-media/a3f9c2d41e8b4f0a9c6d2e7b5a1f8c30.jpg", "contentType": "image/jpeg", "originalUrl": "https://your-cdn.example.com/summary.png" } ], "replyToId": null, "reaction": null, "createdAt": "2024-01-15T09:30:00Z" } } ``` **SDK Code** ```python import requests url = "https://api.getdial.ai/api/v1/messages/messageId/reply" payload = {} headers = { "Authorization": "Bearer ", "Content-Type": "application/json" } response = requests.post(url, json=payload, headers=headers) print(response.json()) ``` ```javascript const url = 'https://api.getdial.ai/api/v1/messages/messageId/reply'; const options = { method: 'POST', headers: {Authorization: 'Bearer ', 'Content-Type': 'application/json'}, body: '{}' }; try { const response = await fetch(url, options); const data = await response.json(); console.log(data); } catch (error) { console.error(error); } ``` ```go package main import ( "fmt" "strings" "net/http" "io" ) func main() { url := "https://api.getdial.ai/api/v1/messages/messageId/reply" payload := strings.NewReader("{}") req, _ := http.NewRequest("POST", url, payload) req.Header.Add("Authorization", "Bearer ") req.Header.Add("Content-Type", "application/json") res, _ := http.DefaultClient.Do(req) defer res.Body.Close() body, _ := io.ReadAll(res.Body) fmt.Println(res) fmt.Println(string(body)) } ``` ```ruby require 'uri' require 'net/http' url = URI("https://api.getdial.ai/api/v1/messages/messageId/reply") http = Net::HTTP.new(url.host, url.port) http.use_ssl = true request = Net::HTTP::Post.new(url) request["Authorization"] = 'Bearer ' request["Content-Type"] = 'application/json' request.body = "{}" response = http.request(request) puts response.read_body ``` ```java import com.mashape.unirest.http.HttpResponse; import com.mashape.unirest.http.Unirest; HttpResponse response = Unirest.post("https://api.getdial.ai/api/v1/messages/messageId/reply") .header("Authorization", "Bearer ") .header("Content-Type", "application/json") .body("{}") .asString(); ``` ```php request('POST', 'https://api.getdial.ai/api/v1/messages/messageId/reply', [ 'body' => '{}', 'headers' => [ 'Authorization' => 'Bearer ', 'Content-Type' => 'application/json', ], ]); echo $response->getBody(); ``` ```csharp using RestSharp; var client = new RestClient("https://api.getdial.ai/api/v1/messages/messageId/reply"); var request = new RestRequest(Method.POST); request.AddHeader("Authorization", "Bearer "); request.AddHeader("Content-Type", "application/json"); request.AddParameter("application/json", "{}", ParameterType.RequestBody); IRestResponse response = client.Execute(request); ``` ```swift import Foundation let headers = [ "Authorization": "Bearer ", "Content-Type": "application/json" ] let parameters = [] as [String : Any] let postData = JSONSerialization.data(withJSONObject: parameters, options: []) let request = NSMutableURLRequest(url: NSURL(string: "https://api.getdial.ai/api/v1/messages/messageId/reply")! as URL, cachePolicy: .useProtocolCachePolicy, timeoutInterval: 10.0) request.httpMethod = "POST" request.allHTTPHeaderFields = headers request.httpBody = postData as Data let session = URLSession.shared let dataTask = session.dataTask(with: request as URLRequest, completionHandler: { (data, response, error) -> Void in if (error != nil) { print(error as Any) } else { let httpResponse = response as? HTTPURLResponse print(httpResponse) } }) dataTask.resume() ```