Messages
Send SMS and MMS from your business number, and search every message body and call transcript.
Messages are activities inside a conversation. Sending through the API is exactly like a teammate typing in the app: the text goes out from your business number, appears in the thread, and the reply comes back to the same thread (and to your message.received webhook).
US carriers require a 10DLC registration before a local number can send texts. Until the campaign is approved, a live POST /messages returns 409 failed_precondition, and the message says exactly what's missing (not filed, in carrier review, waiting for a PIN, or rejected). Check canText with GET /messaging. Some newly filed numbers can text earlier because Ody temporarily attaches them to its own approved campaign while yours is reviewed. Test keys skip this gate, since nothing is sent.
#Send a message
/v1/api/messagesSends an SMS, or an MMS when you include mediaUrls. Address it in one of two ways:
conversationId: reply in an existing thread, from that thread's number.to: an E.164 number. Ody finds or creates the thread from your workspace number, or fromfromif you own that number.
Delivery is asynchronous. The response returns once the carrier has accepted the message; the final outcome arrives as message.delivered or message.failed.
With a test key (ody_test_…) the message is recorded with deliveryStatus: "simulated" and nothing is sent.
Scope: messages:send · supports Idempotency-Key
Body
bodystring required — The text. Longer messages are split into segments by the carrier.conversationIduuid — Reply in this thread. Use this orto.tostring — Recipient in E.164. Use this orconversationId.fromstring — One of your workspace numbers, used when starting a thread withto. Ignored forconversationId(a reply always goes out from the thread's number). Defaults to your workspace number.mediaUrlsarray of URLs — Up to 10 publicly fetchable images or files, sent as MMS.
Returns 201 with { message: Activity, conversationId, simulated? }.
Errors: 409 when the workspace has no active number or texting isn't registered yet; 404 for an unknown conversationId.
curl https://api.ody.co/v1/api/messages \
-H "Authorization: Bearer $ODY_API_KEY" \
-H "Content-Type: application/json" \
-H "Idempotency-Key: appt-7731-reminder" \
-d '{
"to": "+15125550123",
"body": "Hi Ada, reminder: your appointment is tomorrow at 10am. Reply C to confirm."
}'const { message, conversationId } = await ody.messages.send({
to: "+15125550123",
body: "Hi Ada, reminder: your appointment is tomorrow at 10am. Reply C to confirm.",
idempotencyKey: "appt-7731-reminder",
});res = client.messages.send(
to="+15125550123",
body="Hi Ada, reminder: your appointment is tomorrow at 10am. Reply C to confirm.",
idempotency_key="appt-7731-reminder",
){
"message": {
"id": "5e7f…",
"type": "message",
"direction": "outbound",
"body": "Hi Ada, reminder: your appointment is tomorrow at 10am. Reply C to confirm.",
"media": [],
"deliveryStatus": "queued",
"createdAt": "2026-10-14T18:10:00.000Z"
},
"conversationId": "c4f1a2b3-5d6e-4f70-8a9b-0c1d2e3f4a5b"
}#Search messages
/v1/api/messages/searchCase-insensitive search across message bodies and stored call and voicemail transcripts, newest first. It's the same retrieval the MCP search_messages tool uses.
Scope: conversations:read
Query parameters
qstring required — Text to find.limitinteger — 1–100, default 25.
Returns { results: [{ activityId, conversationId, type, direction, body, participants, numberE164, createdAt }] }.
curl "https://api.ody.co/v1/api/messages/search?q=refund&limit=10" \
-H "Authorization: Bearer $ODY_API_KEY"const results = await ody.messages.search("refund", { limit: 10 });results = client.messages.search("refund", limit=10)