Conversations
A conversation is a text thread between one of your numbers and one other party. It is how the message center groups messages, and it has a resolved state your team works through — the portal's "Unresolved" filter. You can read the messages in one, and change the thread's state around them.
:::info A conversation has no id It is identified by both numbers: your line, and the other party. That is why these endpoints take two path segments instead of an id.
If you are reacting to an AccountSMS webhook
you already have both numbers, so you can act immediately with no lookup first.
:::
Formatting in the path is fine — +1 (212) 555-0188 works — and the response
echoes the normalized digits, so you can see what was actually addressed.
Resolving
curl -X POST \
https://api.account.telebroad.com/api/public/v1/sms/conversations/12125550188/13475550123/resolve \
-H "Authorization: Bearer $TB_KEY"
No body required — a bare POST is a complete request.
Two behaviours to rely on:
- It works on threads nobody has touched. Most conversations have no stored
state until someone actions them. Resolving one creates that state rather
than returning
404, so you can close a fresh inbound thread your integration handled elsewhere. An endpoint that 404'd here would fail on the common case. - It is idempotent. Resolving an already-resolved conversation returns
200with the stored state and does not re-stamp who closed it or when. Retrying after a timeout cannot rewrite history.
Who gets credited
| Credential | resolvedBy |
|---|---|
| OAuth token | Always its own owner. You cannot name someone else. |
| API key | Absent, unless you pass resolvedBy explicitly. |
resolvedBy is absent — not 0 — when an integration resolved a thread with
no person named. A key has no user, and a 0 there would read as a real id.
A resolvedBy that is not a user on your account is a 400.
Reading a conversation
curl 'https://api.account.telebroad.com/api/public/v1/sms/conversations/12125550188/13475550123' -H "Authorization: Bearer $TB_KEY"
Needs messages:read — reading the contents of a conversation is a separate
consent from the configuration scope, the same way call recordings are.
:::caution Reading does not mark anything read
markRead defaults to false, which differs from the portal, where opening a
thread clears it.
An integration polling for new messages would otherwise silently empty the unread
badges your staff are working from, and nothing would explain why the inbox kept
emptying itself. Pass markRead=true only when a person has actually seen the
messages.
:::
Read state is per user, not per account: a thread your colleague has read is
still unread for you. That is why read on each message and unread on each
conversation reflect your credential, and readBy lists everyone else.
The inbox
curl 'https://api.account.telebroad.com/api/public/v1/sms/conversations?unresolved=true&assignedToMe=true' -H "Authorization: Bearer $TB_KEY"
One entry per thread with its queue state already attached, so a queue view is one
call rather than one per conversation. Filters combine as AND —
unresolved + assignedToMe is "my open threads", the message center's default
view.
Omit lines and every texting number on the account is listed, so a fresh
integration does not need GET /sms/lines first and keeps working when the
customer buys a number.
:::info The queue filters only see threads that have queue state
unresolved, assignedToMe and unassigned are evaluated against a thread's
stored state — so a thread that has never had any (never resolved, never
assigned) is not returned by them, including by unassigned. It is the same set
the portal's Unresolved queue shows, for the same reason.
List without those filters to see every thread. :::
Opening at a specific message
If you hold a message id — from an AccountSMS webhook, say — you do not know its
position in the thread, so limit/offset cannot get you there.
GET /sms/conversations/{line}/{number}/messages/{id} returns the surrounding
page directly.
Assigning
curl -X POST https://api.account.telebroad.com/api/public/v1/sms/conversations/12125550188/13475550123/assign -H "Authorization: Bearer $TB_KEY" -H 'Content-Type: application/json' -d '{"userId": 481920}'
A null or absent userId unassigns, returning the thread to the unassigned
queue — which is why there is no separate unassign endpoint.
Assigning is not resolving. They are independent axes: a resolved thread can
stay assigned to whoever closed it, and an open thread can sit unassigned. That is
what makes unresolved and unassigned separately filterable.
Reopening
POST /sms/conversations/{line}/{number}/reopen moves a resolved thread back into
the open queue — the missing half of resolve, for when a customer replies to
something you already closed. Idempotent, and a thread nobody ever resolved is
already open, so it succeeds there too.
Deleting
DELETE on a conversation, or on one message, is permanent and has no
recovery through this API. It needs messages:write.
:::danger A second gate that scopes do not override
If the account has restrict deleting SMS switched on, delete answers 403
even with messages:write. That setting exists precisely so an account owner can
stop their own staff and integrations from destroying message history.
:::
What is missing today
:::note No resolve webhook
There is no notification when a colleague resolves or assigns a thread in the
portal. The AccountSMS webhook fires on messages, not on conversation state,
so a client mirroring queue state has to poll the inbox.
If you need it, say so — it moves it up the list. :::
Errors
| Status | type | Cause |
|---|---|---|
400 | invalid_request | A path segment is not a phone number, or an unknown body field |
400 | invalid_request | resolvedBy / userId is not a user on your account |
400 | invalid_request | Marking a line read with an API key — read state needs a user, so authorize with OAuth |
403 | insufficient_scope | The credential lacks the scope the endpoint needs |
403 | permission_denied | line is not a number your credential is authorized for, or the account restricts deleting messages |
The tenant boundary is the line, not the conversation — the other party's
number is just an external phone number. It is the same check that governs
POST /sms/messages, so a line you can send from is a line you can resolve on.