The largest consumer companies in the world are building on our API, and they all hit the same wall at scale: at any given point in time, which Linq number is most optimal to assign to a user?
Today we’re taking on all the work involved in answering that question through two new endpoints in our API. These endpoints take routing, health checks, failover, and number migration off your plate. You send a message to a recipient, and we pick the line, create or reuse the chat, and route around numbers that can't send.
Load balancing has been a rough edge in our API since the beginning. If you send at any real volume, you’ve experienced the pain. You spread traffic across a pool of lines, watch for numbers that get throttled or flagged, and migrate conversations off the bad ones before your delivery rates fall. You track which chat lives on which line, and you keep a table of chat IDs so replies land in the right place. None of this is your actual product. It is undifferentiated work that every high-volume sender rebuilds. So we built it for you.
Here is how the two endpoints work and when to use each.
The two endpoints
GET /v3/available_number tells you the best line to send from right now.
POST /v3/messages sends a message without asking you for a from number at all.
Most integrations only need the second endpoint.
The first is there for the cases where you need a number to render before you send anything, like a click-to-text button or a contact card on a landing page.
Getting a line to display: available_number
available_number returns the healthiest line in your pool at a point in time, in E.164 format. Health here is a function of internal line reputation plus rolling volume over the last hour and the last 24 hours.
curl https://api.linqapp.com/api/partner/v3/available_number \
-H "Authorization: Bearer $LINQ_API_V3_API_KEY"
{
"phone_number": "+12025551234",
"vcf_url": "https://s3.us-east-1.amazonaws.com/linq-attachments/vcf/9716d5c5/12025551234.vcf?X-Amz-Signature=..."
}
Two things to know about it.
It is advisory. The call does not reserve the line or change any selection state. If you want to guarantee a chat starts on the number you just got back, pass that phone_number as from when you create the chat. You can also pass a to set to the endpoint. When you do, and an existing chat with those recipients is already on a healthy line, the response reuses that line instead of picking a fresh one.
It also returns a vcf_url. That is a time-limited link to a vCard for the selected line. The card carries the line's name and photo, sets the selected number as the primary TEL, and lists your other healthy lines as backups. Share it so recipients can save the number as a contact. The backups matter later: if you have to move a recipient to a different line, the number is already in their contacts, so the transition doesn't look like a stranger texting them. The link expires, so call the endpoint again to mint a fresh one.
Sending without a from: POST /v3/messages
This is the endpoint that removes the load balancing work. You give it recipients and a message. You do not give it a from number, and you do not give it a chat ID. It resolves both.
curl -X POST https://api.linqapp.com/api/partner/v3/messages \
-H "Authorization: Bearer $LINQ_API_V3_API_KEY" \
-H "Content-Type: application/json" \
-d '{
"to": ["+12025550147"],
"message": {
"parts": [
{ "type": "text", "value": "Your table for 4 is confirmed for 7:30 tonight." }
]
},
"continuation_message": {
"text": "Confirming your table for 4 at 7:30 tonight."
}
}'
to is an order-independent set of handles, either E.164 numbers or email addresses. One handle is a direct chat. Multiple handles is a group chat. The set identifies the chat, so you never look up or store a chat ID to continue a conversation.
The response tells you exactly what happened.
{
"chat_id": "b1c0a2d4-...",
"created_new_chat": true,
"from": "+12025551234",
"from_selection": {
"reason": "new_best_number",
"reused_existing_chat": false
},
"is_group": false,
"message": {
"id": "9f3e...",
"created_at": "2026-07-08T15:04:05Z",
"delivery_status": "queued",
"parts": [ ... ]
}
}
The endpoint always responds 202 Accepted. Chat creation is incidental to the send.
How the line gets chosen
from_selection.reason reports which of three branches ran.
reused_active_chat. A chat with exactly these recipients already exists, and the line it lives on is healthy. The message goes into that chat on its existing line. This is what keeps a conversation on one number across many sends, so the whole exchange stays in a single thread for the recipient.
new_best_number. No chat exists for this recipient set. We create one on the best available line, using the same health and volume signals as available_number.
failover_flagged. A matching chat exists, but its line has been flagged and can no longer send. This is the case you used to handle by hand. We create a new chat on a fresh best line, abandon the flagged one, and set previous_chat_id on the response. If you supplied a continuation_message, its text is sent as the single message instead of message. That is the opener in the curl above. The recipient is now on a new number, so a short reintroduction usually reads better than replaying the original content. Exactly one message is sent on this branch either way. The continuation_message is ignored on the other two branches.
You do not choose the branch. You send the same request every time and read reason if you care what happened.
Scaling and migration come for free
Add a line to your pool and it enters rotation automatically. You do not register it anywhere or change your send code. As your volume grows, more of the pool absorbs it. When a number degrades, failover moves new sends to a healthy line and, with continuation_message, does it with copy you wrote for that situation. You are no longer writing migration jobs.
A few details worth knowing
The message object carries parts (text, media, or link), plus optional effect, preferred_service (iMessage, RCS, or SMS), idempotency_key, and reply_to. A text part goes up to 10,000 characters. A link part must be the only part in the message.
The first message on a chat may contain a link, including on a newly created chat. Be careful with it. Sending a link as the very first message on a freshly selected line raises that line's flagging risk. It is allowed, not recommended.
Voice memos are not supported here. To send an iMessage voice-memo bubble, use POST /v3/chats/{chatId}/voicememo with a known chat id.
Pass an Idempotency-Key header, or message.idempotency_key, to make retries safe.
When to use it
If you run more than five lines, use both endpoints. Migration and load balancing are where most of the value is, and they only matter at scale. If you run one or two lines, POST /v3/messages is still worth adopting on its own. You stop managing chat IDs and stop maintaining separate create-chat and send-message calls. One endpoint creates the chat when it needs to and appends to the existing one when it can.
The reference docs are at available_number and messages.


