Texting Blue enforces rate limits to ensure fair usage and platform stability. Limits vary by plan and are applied per account.
| Scope | Free | Starter | Pro | Enterprise |
|---|---|---|---|---|
| API requests/min | 60 | 300 | 1,000 | 5,000 |
| Messages/min | 5 | 20 | 60 | 200 |
| Monthly messages | 100 | 1,000 | 10,000 | Custom |
| Phone numbers | 1 | 2 | 5 | Unlimited |
| Team members | 1 | 3 | 10 | Unlimited |
Every API response includes a header indicating your remaining request budget for the current window:
| Header | Description |
|---|---|
x-ratelimit-remaining |
Number of requests remaining in the current minute window. |
When you exceed the rate limit, the API returns a 429 Too Many Requests response with an additional header:
| Header | Description |
|---|---|
Retry-After |
Number of seconds to wait before making another request. |
HTTP/1.1 429 Too Many Requests
Retry-After: 12
x-ratelimit-remaining: 0
Content-Type: application/json
{
"error": {
"code": "rate_limited",
"message": "Too many requests. Please retry after 12 seconds."
}
}When you receive a 429 response, wait the number of seconds indicated by Retry-After before retrying. For repeated failures, use exponential backoff to progressively increase the delay.
async function sendWithRetry(payload, maxRetries = 3) {
for (let attempt = 0; attempt <= maxRetries; attempt++) {
const response = await fetch(
"https://api.texting.blue/v1/messages/send",
{
method: "POST",
headers: {
"x-api-key": process.env.TEXTINGBLUE_API_KEY,
"Content-Type": "application/json",
},
body: JSON.stringify(payload),
}
);
if (response.status !== 429) {
return response.json();
}
const retryAfter = parseInt(response.headers.get("Retry-After") || "1", 10);
const delay = retryAfter * 1000 * Math.pow(2, attempt);
console.log(`Rate limited. Retrying in ${delay / 1000}s...`);
await new Promise((resolve) => setTimeout(resolve, delay));
}
throw new Error("Max retries exceeded");
}import os
import time
import requests
def send_with_retry(payload: dict, max_retries: int = 3) -> dict:
for attempt in range(max_retries + 1):
response = requests.post(
"https://api.texting.blue/v1/messages/send",
headers={
"x-api-key": os.environ["TEXTINGBLUE_API_KEY"],
"Content-Type": "application/json",
},
json=payload,
)
if response.status_code != 429:
return response.json()
retry_after = int(response.headers.get("Retry-After", "1"))
delay = retry_after * (2**attempt)
print(f"Rate limited. Retrying in {delay}s...")
time.sleep(delay)
raise Exception("Max retries exceeded")function sendWithRetry(array $payload, int $maxRetries = 3): array
{
for ($attempt = 0; $attempt <= $maxRetries; $attempt++) {
$ch = curl_init("https://api.texting.blue/v1/messages/send");
curl_setopt($ch, CURLOPT_POST, true);
curl_setopt($ch, CURLOPT_HTTPHEADER, [
"x-api-key: " . getenv("TEXTINGBLUE_API_KEY"),
"Content-Type: application/json",
]);
curl_setopt($ch, CURLOPT_POSTFIELDS, json_encode($payload));
curl_setopt($ch, CURLOPT_RETURNTRANSFER, true);
curl_setopt($ch, CURLOPT_HEADER, true);
$response = curl_exec($ch);
$httpCode = curl_getinfo($ch, CURLINFO_HTTP_CODE);
$headerSize = curl_getinfo($ch, CURLINFO_HEADER_SIZE);
$body = substr($response, $headerSize);
$headers = substr($response, 0, $headerSize);
curl_close($ch);
if ($httpCode !== 429) {
return json_decode($body, true);
}
preg_match('/Retry-After:\s*(\d+)/i', $headers, $matches);
$retryAfter = isset($matches[1]) ? (int) $matches[1] : 1;
$delay = $retryAfter * pow(2, $attempt);
echo "Rate limited. Retrying in {$delay}s...\n";
sleep($delay);
}
throw new \RuntimeException("Max retries exceeded");
}If your application sends messages in bulk, queue them locally and send at a steady rate below your plan's limit. This avoids hitting rate limits entirely.
class MessageQueue {
constructor(apiKey, messagesPerMinute) {
this.apiKey = apiKey;
this.interval = (60 / messagesPerMinute) * 1000; // ms between sends
this.queue = [];
this.processing = false;
}
enqueue(payload) {
this.queue.push(payload);
if (!this.processing) {
this.process();
}
}
async process() {
this.processing = true;
while (this.queue.length > 0) {
const payload = this.queue.shift();
await fetch("https://api.texting.blue/v1/messages/send", {
method: "POST",
headers: {
"x-api-key": this.apiKey,
"Content-Type": "application/json",
},
body: JSON.stringify(payload),
});
// Wait to stay under rate limit
await new Promise((resolve) => setTimeout(resolve, this.interval));
}
this.processing = false;
}
}
// Starter plan: 20 messages/min
const queue = new MessageQueue("tb_live_...", 20);
queue.enqueue({ to: "+14155551234", from: "+14155559876", content: "Message 1" });
queue.enqueue({ to: "+14155551235", from: "+14155559876", content: "Message 2" });
queue.enqueue({ to: "+14155551236", from: "+14155559876", content: "Message 3" });Check the x-ratelimit-remaining header on every response to proactively slow down before hitting the limit:
const response = await fetch("https://api.texting.blue/v1/messages/send", {
method: "POST",
headers: {
"x-api-key": process.env.TEXTINGBLUE_API_KEY,
"Content-Type": "application/json",
},
body: JSON.stringify(payload),
});
const remaining = parseInt(
response.headers.get("x-ratelimit-remaining") || "0",
10
);
if (remaining < 10) {
console.warn(`Only ${remaining} requests remaining in this window`);
// Slow down or pause sending
}