One request to POST /sms/send sends one text from one of your agents' phone numbers. The text lands on that agent's SMS thread with the recipient, and so does the reply, so you and the agent see the whole conversation in the portal. Authenticate with your workspace API key, as on every endpoint (see Authentication).
curl -X POST "https://api.telvana.com/sms/send" \
-H "x-api-key: $TELVANA_API_KEY" \
-H "Idempotency-Key: $IDEMPOTENCY_KEY" \
-H "Content-Type: application/json" \
-d '{
"from": "+16575550100",
"to": "+17145550123",
"message": "Hi Dana, this is Sunrise Home Care checking in after your first visit. Reply here anytime."
}'
The fields
from is the agent's phone number, as listed on the portal's Phone Numbers page. It has to be the number of one of your workspace's agents (otherwise the answer is 404), and both that agent and your workspace need SMS turned on (otherwise 400 with SMS_NOT_ENABLED).
to is the number to text. US and Canadian numbers only, in any common format. A number written with another country code is refused, even when its digits would read as a US number.
message is the text itself, 1 to 1,600 characters (counted in UTF-16 code units, so most emoji count as two), and not blank. It is sent as written: fill in names and dates on your side before you send. A NUL character, or half of an emoji left by cutting a message short, is refused with VALIDATION_ERROR.
Retrying safely
Send an Idempotency-Key header with every text ($IDEMPOTENCY_KEY in the examples): any string up to 255 characters that is unique to this text. Telvana stores only its hash, but use an opaque value, such as a UUID or a hash of your own record ids, rather than anything a person could read. Create the key once, when you create the text, and store it with the text's from, to and message: every attempt at that text sends the same key and exactly the same fields. If your request times out and you send it again that way, Telvana answers with the first send if it finished (replayed: true), with a 409 while it is unfinished or its outcome is unknown, or sends the text now if the first attempt sent nothing. A retry under a new key is a second text. Retry within hours, not weeks: Telvana keeps keys, but does not promise to keep them forever, so a late retry of a text that may have gone out could send it again.
Keys belong to your workspace. Never reuse one: the same text under an old key answers with that old send and texts nothing, and a different text under it, even one changed by a single character or sent to another number, is refused: 422 with IDEMPOTENCY_KEY_REUSED. That refusal sent nothing, and the key still belongs to its first text. A separate text, to another person or with other words, goes out under its own new key. If the new text was meant to replace the first, the first may already have gone out: do not send the replacement automatically, and flag it for someone to follow up.
A 409 with IDEMPOTENCY_KEY_IN_USE means an earlier request with that key has not finished, or its outcome is unknown. Retrying with the same key is always safe, a few seconds apart rather than in a loop. If the 409 carries error.details.providerSid, the text went out: do not send it again. If the 409 lasts more than a minute, stop retrying, as it may never change: treat the text as possibly delivered and flag it for someone to follow up, and do not resend it under a new key or by hand. A later retry under the same key, within the hours above, answers with the send if one finished, but if the first attempt was refused in the meantime, it sends the text now, so retry only while the text is still wanted.
Two answers mean the text may already be with the recipient, so do not send it again:
- 502 with SMS_SEND_OUTCOME_UNKNOWN: the carrier did not confirm the send. The text may have gone out, but it is not on the thread and has no status to read. A retry with the same key answers 409.
- 502 with SMS_SENT_NOT_RECORDED: the text went out but could not be put on the thread, so it has no status to read, and a retry with the same key answers 409 (or, if the record landed after all, 200 with replayed: true). error.details.providerSid, when present, is the carrier's id for it.
These mean this request sent nothing. Retry with the same key, a few seconds later and then backing off, or for 429 once the next minute starts:
| Status | error.code | Meaning |
|---|---|---|
| 429 | RATE_LIMITED | Your workspace's API keys made more than 60 send attempts this clock minute that passed the number, key and account checks, whether or not the carrier took them, a Telvana agent number included. Most replays and 409s, and requests refused by those checks, are not counted. |
| 502 | SMS_PROVIDER_UNREACHABLE | Telvana could not reach the carrier. |
| 503 | SMS_PROVIDER_BUSY | The carrier is throttling sends, or the number has too many texts queued. |
| 503 | SMS_SEND_UNAVAILABLE | Telvana could not process the request. |
In the rare case of 503 with IDEMPOTENCY_KEY_HELD, nothing was sent but the key could not be released: send the text again under a new key.
A timeout, a dropped connection or a 5xx with no code may have sent the text. Retry it with the same key, never a new one. A 401 or 404 sent nothing and holds no key: fix the API key or the from number, and the text can keep its key.
What the response means
A 200 means the carrier accepted the text, not that it arrived:
{
"data": {
"success": true,
"messageId": "h8k2m4p6r8t0v2x4z6b8d0f2",
"conversationId": "q1w3e5r7t9y1u3i5o7p9a1s3",
"providerSid": "SM0123456789abcdef0123456789abcdef",
"status": "queued",
"replayed": false
}
}
Keep messageId. A text to a landline is often accepted and fails a moment later, so read its delivery status once, a few minutes after sending, rather than polling. A workspace can read 120 statuses a minute.
curl "https://api.telvana.com/sms/messages/<messageId>" \
-H "x-api-key: $TELVANA_API_KEY"
If the read answers 502 with SMS_STATUS_UNAVAILABLE, the carrier could not be asked: try again later. A 429 means the workspace read more than 120 statuses this clock minute: read the rest once the next minute starts.
status is read from the carrier on each request. It moves through queued, sending and sent, and usually ends as delivered, undelivered or failed; a carrier that reports no delivery receipt leaves it at sent. When the text did not arrive, errorCode says why: 30006 is a landline or a carrier that cannot be reached, 30003 a phone that is off or out of service, 30007 a text the carrier filtered. Some codes, such as 30034, are about the sending number rather than the recipient, so check the code before you stop texting a number.
GET /sms/messages/{id} answers status: null for a text on a number Telvana carries through Telnyx rather than Twilio, and for a text the carrier no longer holds. A null never means the text was not sent. On a Telnyx number, a 200 from POST /sms/send also does not rule out an opt-out or a failed delivery: those may only show after the send.
Refusals to act on
These mean nothing was sent, and the same text will not go through on a retry. Stop texting the number, or fix it. Branch on code, not the status: a 422 can also be IDEMPOTENCY_KEY_REUSED, which is about your key, not the recipient (see Retrying safely).
| Status | error.code | Meaning |
|---|---|---|
| 422 | RECIPIENT_OPTED_OUT | The person replied STOP to this number. Texts are refused until they reply START. |
| 422 | LANDLINE_OR_UNREACHABLE | A landline, or a number that cannot receive texts. |
| 400 | INVALID_PHONE_NUMBER | Not a valid phone number. |
| 400 | UNSUPPORTED_DESTINATION | Not a US or Canadian number, or a premium-rate, service or Telvana agent number. |
| 400 | NUMBER_BLOCKED | The number is blocked for this agent. |
A 400 with VALIDATION_ERROR or INVALID_IDEMPOTENCY_KEY is a request to fix: nothing was sent and no key is held, so the corrected text can keep its key, or take a new one if the key itself was refused. A 403 with ACCOUNT_SUSPENDED means the account is suspended for an overdue invoice: nothing was sent and no key is held, so once the invoice is paid, send the text again with the same key if it is still wanted. A 500 with IDEMPOTENCY_UNAVAILABLE means sending is unavailable: nothing was sent and no key is held. Retry later with the same key rather than dropping the Idempotency-Key to get past it, and write to support@telvana.com if it persists.
502 with SMS_PROVIDER_REFUSED is a carrier refusal for any other reason: nothing was sent, and the key is free again, so a resend, if the code allows one, goes under the same key. The carrier's own code is in error.details.providerErrorCode. Some of those codes are about the sending number rather than the recipient, so check the code before you stop texting a number.
The error format is on the Errors page, and the API reference has the endpoint's full description.
Replies
The person's reply arrives on the same thread, in the portal's Conversations page under SMS. The agent's SMS settings decide what happens next: the AI answers it, or your staff answer it.