API Limits and Constraints
The limits the LigueLead API enforces today, what happens when a request goes over one, and how to plan your integration around them.
🚦 Rate Limits
Current Limits
| Limit | Value | Scope |
|---|---|---|
| Request rate | 10,000 requests per second | The API as a whole, not per token |
| Burst | Up to 10,000 requests at once | The API as a whole, not per token |
A request over the limit is refused before it is processed, with 429 Too Many Requests. There is no separate limit per API token, per app or per endpoint.
Note: The API does not send rate-limit headers (
X-RateLimit-*or similar). Rely on the429status instead of a remaining-requests counter.
Handling Rate Limits
A 429 means the request was not accepted, so it is safe to wait and send it again:
import random
import time
import requests
def post_with_rate_limit_retry(url, headers, data, max_retries=3):
"""POST, waiting and retrying only when the API answers 429."""
for attempt in range(max_retries + 1):
response = requests.post(url, headers=headers, json=data, timeout=35)
if response.status_code != 429 or attempt == max_retries:
return response
delay = (2 ** attempt) + random.uniform(0, 0.5)
print(f"⏱️ Rate limit hit, waiting {delay:.1f}s... (attempt {attempt + 1})")
time.sleep(delay)
return response📱 Phone Number Limits
Recipients per Request
| Endpoint | Maximum numbers per request |
|---|---|
POST /v1/sms (SMS and SMS Flash) | 10,000 |
POST /v1/voice | 10,000 |
POST /v1/rcs | 10,000 |
POST /v1/voice-agent/call | 1,000 |
Every send needs at least one number. A longer list is refused with 422 — At most 10000 phone numbers are allowed per request (Maximum 1000 phone numbers allowed on AI calls). To reach more recipients, split them across several requests.
The request body is also limited to 250 KB (see Request Size below). A list of 10,000 numbers fits within it.
Phone Number Format
Accepted formats:
| Format | Example | Description |
|---|---|---|
| With country code | 5511999999999 | Country code 55 + area code + number |
| International | +5511999999999 | Same, with a leading + |
| National | 11999999999 | Area code + number; 55 is added for you |
Validation rules on POST /v1/sms, POST /v1/rcs and POST /v1/voice (each failure is a 422 whose field points to the entry, e.g. phones.3):
- Only digits and the characters
+,(,)and-are accepted. Spaces and dots are refused:(11)99999-9999is valid,(11) 99999-9999is not. - Each number must have between 10 and 16 digits.
- On
POST /v1/voice,is_international: trueskips these rules for numbers outside Brazil — see How to Send Voice Messages.
Best Practice: Send numbers with the country code (
5511999999999or+5511999999999). Without it, the length decides: 10 or 11 digits are a national number and get55in front — area code 55 included, so55991234567is sent as5555991234567.
📦 Request Size
| Limit | Value | When exceeded |
|---|---|---|
| JSON request body | 250 KB | 413 — Payload too large. Maximum allowed size is 250 KB. |
| Audio upload | 5 MB per file | 413 — File too large. Maximum allowed size is 5 MB. |
RCS template endpoints accept inline media and have their own size rules — see How to Send RCS.
📁 File Upload Limits
Audio Files (Voice Messages)
| Specification | Limit | Notes |
|---|---|---|
| File Size | 5 MB maximum | Larger files are refused with 413 |
| Duration | No specific limit | The file must contain decodable audio |
| Form fields | file and title | Sent as multipart/form-data; both are required |
Supported Audio Formats
| Format | Extension | MIME type | Recommendation |
|---|---|---|---|
| WAV | .wav | audio/wav or audio/wave | ⭐ Recommended — lossless, best audio quality |
| MP3 | .mp3 | audio/mpeg or audio/mp3 | Accepted, but lossy compression may reduce quality |
Any other extension or MIME type (AAC, M4A, OGG, ...) is refused with 415 Unsupported Media Type.
⭐ Recommended format — WAV: Prefer uploading WAV files. MP3 uses lossy compression, so an MP3 upload may carry audible quality loss (compression artifacts) that WAV avoids. Every upload is normalized to the telephony standard (8 kHz, mono, 16‑bit PCM WAV) on our side, so starting from a lossless WAV preserves the best possible quality.
Important: The file's actual contents are checked against its extension: uploading a file whose bytes clearly belong to the other format (for example a WAV renamed to
.mp3) is rejected with415 Unsupported Media Type. A file that is not decodable audio is rejected while it is being processed, with422(or415for a WAV encoding that is not supported).
File Management
GET /v1/voice/uploadslists the 100 most recent audio files of the app, without pagination. Any file, older ones included, can still be read by its ID withGET /v1/voice/uploads/{id}.- There is no endpoint for deleting uploaded audio files, and uploaded files do not expire.
Audio Conversion Tips
# Convert an unsupported format to WAV in the telephony standard (8 kHz, mono, 16-bit)
ffmpeg -i input.m4a -ar 8000 -ac 1 -sample_fmt s16 output.wav
# Check duration and size before uploading
ffprobe -v quiet -show_entries format=duration,size input.wav💬 Message Content Limits
SMS Messages
POST /v1/sms sends standard SMS, or SMS Flash with "is_flash": true. The same content rules apply to both:
| Specification | Limit | Notes |
|---|---|---|
| Maximum Length | 1,600 characters | Longer messages are refused with 422 |
| Up to 160 characters | 1 credit | A single SMS |
| Over 160 characters | 1 credit per started block of 152 characters | Counted over the whole message: ceil(length / 152) |
| Characters | Letters, digits, spaces and common punctuation | Accents are removed before sending (ç → c, ã → a), and characters outside this set — emoji included — are dropped |
Credits are counted on the text actually sent, after accents and unsupported characters are removed.
SMS Billing Examples
Message: "Hello! Welcome to LigueLead API." → 32 chars = 1 credit
Message: 160 characters exactly → 160 chars = 1 credit
Message: 161 characters → 161 chars = 2 credits (ceil(161 / 152))
Message: 304 characters → 304 chars = 2 credits (ceil(304 / 152))
Message: 305 characters → 305 chars = 3 credits (ceil(305 / 152))
Message: 1,600 characters (maximum) → 1,600 chars = 11 credits (ceil(1600 / 152))
Character Count Calculator
import math
import re
import unicodedata
# Characters kept in the text that is sent; everything else is dropped.
_NOT_ALLOWED = re.compile(r"""[^A-Za-z0-9()@`"'=:/!?$%{}~^#_*,&+<>\\\[\]\s.-]""")
def normalize_sms(message: str) -> str:
"""The text actually sent: accents removed, then characters outside the allowed set dropped."""
decomposed = unicodedata.normalize("NFD", message)
without_accents = re.sub(r"[̀-ͯ]", "", decomposed)
return _NOT_ALLOWED.sub("", without_accents)
def calculate_sms_credits(message: str) -> int:
"""Credits for an SMS, counted on the normalized text."""
text = normalize_sms(message)
length = len(text)
if length == 0 or not text.strip():
raise ValueError("Message cannot be empty")
if length > 1600:
raise ValueError("Message cannot exceed 1600 characters")
if length <= 160:
return 1
return math.ceil(length / 152)
# Examples
print(repr(normalize_sms("Promoção café 🎉"))) # Output: 'Promocao cafe '
print(calculate_sms_credits("Hello World!")) # Output: 1
print(calculate_sms_credits("A" * 160)) # Output: 1
print(calculate_sms_credits("A" * 161)) # Output: 2
print(calculate_sms_credits("A" * 305)) # Output: 3
print(calculate_sms_credits("é" * 161)) # Output: 2 (counted as "e" * 161)The API also refuses, with 422, a message longer than 1,600 characters before normalization, so keep the text you submit within that limit as well.
RCS Messages
| Specification | Limit |
|---|---|
Freeform message | 306 characters |
| Carousel | 2 to 10 cards |
The template rules (body, buttons, media) are in How to Send RCS.
Titles and Names
| Field | Rule |
|---|---|
Audio upload title | Required |
Voice send title | Required |
SMS send title | Optional |
AI call title (POST /v1/voice-agent/call) | Required, up to 200 characters |
App name | Required, 1–255 characters |
⏱️ Timeouts
| Operation | Limit | Notes |
|---|---|---|
| Any API request | 29 seconds | A request still running after that is answered with 504 Gateway Timeout |
| Webhook delivery to your endpoint | 60 seconds | The attempt is abandoned and the notification is retried, up to 8 attempts over about 45 minutes — see How to Receive Webhooks |
Send requests only queue the messages, so they normally answer well within the limit. Set your client timeout slightly above 29 seconds, so that a 504 is reported by the API instead of being cut by your own timeout.
🔄 Retry Strategies & Error Handling
Retry according to what the answer tells you about the request:
| Answer | Was the send accepted? | What to do |
|---|---|---|
200 / 202 | Yes | Keep the returned campaign_id |
429 | No | Wait and retry (see above) |
401, 413, 415, 422 | No | Fix the request; retrying it unchanged fails again |
5xx, 504 or a timeout on your side | Possibly | Do not resend blindly |
⚠️ Important: Send endpoints have no idempotency key: every
POSTthat is accepted creates a newcampaign_idand is charged. A send that ended in a5xxor a timeout may already have been accepted, in whole or in part, so repeating it can deliver — and charge — the same messages twice. Before resending, check what went out: for SMS, the webhooks andGET /v1/campaigns/{campaign_id}/recipients(see How to Check Campaign Status) report per recipient.
GET requests do not send anything and are always safe to retry.
📊 Concurrent Operations
There is no separate limit on concurrent requests per client: concurrent requests count towards the request rate above, which is shared by the whole API. Keep your own concurrency moderate, and handle 429 as described.
💾 Storage & Account Limits
Apps
| Resource | Limit | Notes |
|---|---|---|
| Active Apps per Client | No limit | Hold as many apps as your integration needs |
| App Name | 1–255 characters | Must be unique among your active apps |
The name is checked against active apps only, so a removed app frees its name for
reuse. A duplicate name is the only thing that answers 409 on creation. See
Managing Apps.
Audio File Storage
| Resource | Limit |
|---|---|
| File Size | 5 MB per file |
| File Retention | Files do not expire; there is no delete endpoint |
| Listing | GET /v1/voice/uploads returns the 100 most recent files |
🚀 Optimization Best Practices
1. Efficient Batch Operations
Good Practice: Combine multiple recipients in single requests
# ✅ Efficient: Single request with multiple recipients
payload = {
"title": "Monthly Newsletter",
"message": "Check out our latest updates!",
"phones": ["5511999999999", "5521888888888", "5531777777777"] # Up to 10,000 numbers
}Avoid: Multiple single-recipient requests
# ❌ Inefficient: Multiple requests for individual recipients
for phone in phones:
payload = {"message": "Hello", "phones": [phone]} # One request per number2. Audio File Reuse Strategy
import os
import requests
class AudioFileManager:
"""Upload each audio file once and reuse its ID across campaigns."""
MIME_TYPES = {".wav": "audio/wav", ".mp3": "audio/mpeg"}
def __init__(self):
self.uploaded_files = {} # Cache uploaded file IDs
def upload_once_use_many(self, file_path, title, api_token, app_id):
"""Upload an audio file once and reuse it across campaigns."""
# Check if already uploaded
file_key = f"{title}_{hash(file_path)}"
if file_key in self.uploaded_files:
print(f"♻️ Reusing existing upload: {self.uploaded_files[file_key]}")
return self.uploaded_files[file_key]
# The part needs its MIME type: without it the upload is refused with 415
extension = os.path.splitext(file_path)[1].lower()
mime_type = self.MIME_TYPES[extension]
with open(file_path, 'rb') as audio_file:
files = {'file': (os.path.basename(file_path), audio_file, mime_type)}
data = {'title': title}
headers = {'api-token': api_token, 'app-id': app_id}
response = requests.post(
"https://api.liguelead.com.br/v1/voice/uploads",
headers=headers,
files=files,
data=data
)
if response.status_code == 201:
voice_id = response.json()['data']['id']
self.uploaded_files[file_key] = voice_id
print(f"✅ New upload successful: {voice_id}")
return voice_id
else:
raise Exception(f"Upload failed: {response.text}")
# Usage
audio_manager = AudioFileManager()
# Upload once
voice_id = audio_manager.upload_once_use_many(
"promo.mp3",
"Black Friday Promotion",
api_token,
app_id
)
# Use in multiple campaigns
campaigns = [
{"title": "VIP Customers", "phones": vip_numbers},
{"title": "Regular Customers", "phones": regular_numbers},
{"title": "New Prospects", "phones": prospect_numbers}
]
for campaign in campaigns:
send_voice_message(voice_id, campaign["phones"], campaign["title"])🆘 Support & Escalation
When to Contact Support
- Receiving
429regularly - File upload failures
- Authentication issues
- Unexpected API behavior
- Volume that you expect to approach the request rate above
What to Include in Support Requests
-
Account Information
- API token (last 4 characters only)
- App ID
- Account email
-
Technical Details
- The
campaign_idof the affected send, when there is one - Current usage patterns
- Expected volume increase
- Specific limits being hit
- Error messages or response codes
- The
-
Business Context
- Use case description
- Timeline requirements
- Integration complexity
Contact Methods
- Client Area: areadocliente.liguelead.app.br
- Documentation: Always check latest docs first
Updated about 5 hours ago
