Authentication
All TextBubbles API requests require a Bearer token in the Authorization header. Each API key is associated with a specific customer account, providing full data segregation between customers.
Bearer Token
Authorization: Bearer tb_xxxxxxxxxxxxxExample request:
curl https://api.textbubbles.com/v1/messages \
-H "Authorization: Bearer YOUR_API_KEY"Customer-Scoped API Keys
Every API key is tied to a customer account. When you authenticate, the API automatically scopes all operations to your customer:
- Messages — you can only see and send messages belonging to your customer
- Webhooks — each customer has their own webhook configuration
- Addresses — you can only send from phone numbers authorized for your customer
If an API key has no customer association, all endpoints return 403 Forbidden.
Address Authorization
When sending messages, the from parameter must be a phone number authorized for your customer account. If omitted, the default authorized address is used. Sending from an unauthorized address returns a 403:
{
"success": false,
"error": {
"code": "ADDRESS_NOT_AUTHORIZED",
"message": "The 'from' address is not authorized for this customer"
},
"requestId": "req_xyz"
}API Key Security
API keys are hashed with bcrypt before storage. The plaintext key is returned only once at creation time and cannot be retrieved later. If you lose your key, contact your account administrator to generate a new one.
API keys use the prefix tb_ followed by a unique identifier. Store your API key securely and never expose it in client-side code.
Error Responses
Requests without a valid token receive a 401 Unauthorized response:
{
"success": false,
"error": {
"code": "UNAUTHORIZED",
"message": "Missing or invalid Authorization header"
},
"requestId": "req_xyz"
}Requests with a valid key but no customer association receive 403 Forbidden:
{
"success": false,
"error": {
"code": "FORBIDDEN",
"message": "API key has no associated customer"
},
"requestId": "req_xyz"
}Rate Limiting
Each API key has its own rate limits. When exceeded, you’ll receive a 429 Too Many Requests response with a Retry-After header:
{
"success": false,
"error": {
"code": "RATE_LIMIT_EXCEEDED",
"message": "Too many requests. Please retry after 60 seconds."
},
"requestId": "req_xyz"
}Rate Limit Headers
Every API response includes rate limit information:
| Header | Description |
|---|---|
X-RateLimit-Limit | Maximum requests per window (100) |
X-RateLimit-Remaining | Requests remaining in current window |
X-RateLimit-Reset | Unix timestamp when the window resets |
Retry-After | Seconds to wait (only on 429 responses) |
Implement exponential backoff when handling rate limits:
async function sendWithRetry(payload, maxRetries = 3) {
for (let attempt = 0; attempt <= maxRetries; attempt++) {
const response = await fetch('https://api.textbubbles.com/v1/messages', {
method: 'POST',
headers: {
'Authorization': `Bearer ${API_KEY}`,
'Content-Type': 'application/json',
},
body: JSON.stringify(payload),
});
if (response.status !== 429) {
return response.json();
}
const retryAfter = response.headers.get('Retry-After') || Math.pow(2, attempt) * 10;
await new Promise(resolve => setTimeout(resolve, retryAfter * 1000));
}
throw new Error('Max retries exceeded');
}Request IDs
Every API response includes a requestId field. You can also pass your own via the X-Request-Id header. Log these for debugging and include them when contacting support.
Best Practices
- Store API keys in environment variables, never in source code
- Save your API key immediately upon creation — it cannot be retrieved later
- Use different API keys for development and production
- Contact your account administrator to rotate keys periodically