API key security
- Store API keys in environment variables or secret management systems.
- Never commit API keys to version control or expose them in client-side code.
- Rotate API keys regularly and immediately revoke compromised keys.
- Use separate API keys for different applications and environments.
Caching recommendations
- Cache hierarchy data for 5–10 minutes (changes infrequently).
- Cache historical project/message stats for completed date ranges indefinitely.
- Use shorter TTLs (1–2 minutes) for current-day statistics.
- Implement cache invalidation when you detect data changes.
Rate limiting strategies
- Monitor
X-RateLimit-Remainingon every response. - Implement exponential backoff for
429responses. - Use caching to reduce the number of API calls needed.
- Batch operations when possible (e.g., query larger date ranges).
Carrier throughput limits
Carriers limit how fast a brand can send. Two matter for API projects:- T-Mobile daily cap. T-Mobile caps the message parts (160-character SMS segments; a three-part text spends three) a brand can send per day. This applies to brands that have a T-Mobile daily limit on file (political brands verified through Campaign Verify do not have one today), and the cap resets at midnight Pacific. The cap is a speed limit, not a stop. Past it, T-Mobile recipients and recipients with an unknown carrier keep sending at a reduced rate (about 300 message parts per minute per campaign) while the platform watches delivery receipts. Once about 300 over-cap receipts for the campaign have come back with under 5% failures, the rate rises to about 3,000 parts per minute. Recipients on other carriers are never slowed. Only the carrier pauses a project: if T-Mobile rejects messages over the cap, the project auto-pauses with
pause_reasonbrand_daily_cap(see “Past the cap” below). Run list analysis first (POST /contact-lists/{id}/analyze) and only T-Mobile recipients are affected, because no carrier is unknown. - AT&T per-minute rate. AT&T limits message parts per minute per campaign, with separate SMS and MMS rates. AT&T counts each part of a text: a text that fits in one part counts once, and a longer text that splits into 2 or 3 parts counts 2 or 3 times. A picture message (MMS) counts once against the MMS rate. Every 10DLC campaign with an AT&T rate on file has its AT&T traffic delivered at that rate, whatever the brand type. The platform sends AT&T recipients right away and the carrier queue delivers them at that rate, usually within about an hour; only in the last 90 minutes of the recipients’ sending window does the platform hold back AT&T recipients that could not be delivered before the window closes. A campaign with no rate on file has no AT&T rate applied. Recipients on other carriers are never slowed by it, and it never pauses a project.
analysis.status complete or the contact_list.analyzed webhook, then create or schedule the project (until then the API returns 409 LIST_ANALYSIS_IN_PROGRESS). A list that was never analyzed sends to every number on it; an analyzed list sends to wireless numbers only.
The two lanes are independent: a campaign can have one without the other. A political (Campaign Verify) brand has an AT&T rate but no T-Mobile daily limit today, and a brand can also lack a T-Mobile limit because it has not synced yet. For how this looks in the dashboard and how to finish a large send sooner, see Sending speed and carrier limits.
Check before you schedule. GET /campaigns/{id}/throughput returns the brand’s daily cap, how much is used today, and the AT&T rates. t_mobile is null until a T-Mobile daily cap has synced for the campaign, and att is null only when the campaign has no AT&T rate on file (inside att, sms_tpm and mms_tpm can each be null, one per message type). t_mobile is also null when the brand has no T-Mobile daily limit on file. carrier_metered is true if either lane applies; a campaign with neither returns false with both null. A political brand with an AT&T rate returns carrier_metered: true, t_mobile: null and a populated att. GET /projects/{id}/throughput estimates how a specific project will fare: t_mobile.estimated_recipients, att.estimated_minutes, and the daily cap figures. A null means the number is not known right now (for example, today’s usage is temporarily unavailable); it is never zero. t_mobile.will_pause is always false and t_mobile.estimated_send_days is always 1: the fields remain for compatibility, because a project no longer pauses on the cap arithmetic. When carrier_coverage is low, the estimates use the platform-wide carrier split rather than your recipients’ actual carriers. Project estimates are cached for up to 60 seconds, so numbers can lag by that much. If the estimate query times out, the endpoint returns 503 with code CARRIER_ESTIMATE_TIMEOUT; retry later.
Poll for paused, not just completed. GET /projects/{id} returns status, pause_reason, and auto_paused. A project with status: paused and pause_reason: brand_daily_cap was paused because T-Mobile rejected messages over the cap, not because it is stuck. Call POST /projects/{id}/schedule with a morning scheduled_at inside the next day’s sending hours to wait for the reset at midnight Pacific, or call it without one to try T-Mobile again now. A poller that waits only for completed will time out on a large send.
Known pause_reason values:
Other values may appear. Every automatic pause needs a manual restart via
POST /projects/{id}/schedule.
Past the cap. Past the daily cap, T-Mobile recipients send at the reduced rate described above, and the platform watches the receipts. T-Mobile rejecting messages over the cap (error code 016 on the receipt, or any failure code at a high rate: 20 or more failures that are at least half of the newest three minutes of T-Mobile receipts for the campaign) closes that campaign’s T-Mobile lane for the rest of the Pacific day. The campaign’s projects then auto-pause with pause_reason brand_daily_cap once only T-Mobile and unknown-carrier recipients remain, and auto_paused is true. Rejected messages are still billed.
Resume with POST /projects/{id}/schedule. With a morning scheduled_at it waits for the reset at midnight Pacific, which is the right call when you want to wait. Without one it reopens the campaign’s T-Mobile lane and tries T-Mobile again at the reduced rate; the project pauses again if T-Mobile is still rejecting.
Send daily_cap_bypass: true on POST /projects/{id}/schedule to skip the reduced rate and send T-Mobile recipients at full speed past the cap. A carrier rejection still pauses a bypassed project. The setting holds until the next schedule or resume call rewrites it, so send it on every schedule or resume call you want it to apply to. GET /projects/{id} returns the current value as daily_cap_bypass.
Failed messages over the cap report error code 016 (see error codes).
Date range optimization
- Use the maximum 31-day range when fetching historical data.
- Request only the specific date ranges you need.
- Use hierarchy filters (
organization_id,brand_id,campaign_id) to reduce data volume. - Cache aggregated results for reporting dashboards.
Webhook reliability
- Return
200within 10 seconds (process asynchronously if needed). - Always validate the HMAC signature before processing.
- Use
event_idto deduplicate (rare but possible). - Log failures and monitor your endpoint health.
- Use HTTPS endpoints (required in production).
- Rotate secrets periodically (every 90 days recommended).
