> ## Documentation Index
> Fetch the complete documentation index at: https://docs.politicalcomms.com/llms.txt
> Use this file to discover all available pages before exploring further.

# Best Practices

> API key security, caching recommendations, rate limit strategies, carrier throughput limits, and date range optimization.

## 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-Remaining` on every response.
* Implement **exponential backoff** for `429` responses.
* 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 messages 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. At the cap, recipients on known other carriers keep sending; T-Mobile recipients and recipients with an unknown carrier wait. When only those remain, and everything already queued has been sent, the project auto-pauses with `pause_reason` `brand_daily_cap`. Run list analysis first (`POST /contact-lists/{id}/analyze`) and only T-Mobile recipients wait, because no carrier is unknown. Do not resume at midnight: that is inside quiet hours for much of the country. Call `POST /projects/{id}/schedule` for the next day during sending hours (the project's sending window: 8 AM to 10 PM in the time zone of most recipients, or 8 AM Eastern to 10 PM Pacific when no zone holds a majority) with a morning `scheduled_at`, or resume now with `daily_cap_bypass: true` (T-Mobile messages over the cap may fail, and failed messages are still billed). `POST /projects/{id}/schedule` accepts paused projects, so it is the resume call.
* **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 is optional, but a list cannot be used while it is running: start the analysis, wait for `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](/help/sending-speed).

**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.will_pause`, `t_mobile.estimated_send_days`, and `att.estimated_minutes`. A `null` means the number is not known right now (for example, today's usage is temporarily unavailable, in which case `will_pause` is `false`); it is never zero. A project with `daily_cap_bypass` on reports `will_pause: false` and `estimated_send_days: 1`. 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` is waiting for the next Pacific day, not stuck; call `POST /projects/{id}/schedule` with a morning `scheduled_at` inside the next day's sending hours to resume it, or send `daily_cap_bypass: true` to resume now. A poller that waits only for `completed` will time out on a large send.

Known `pause_reason` values:

| Value | Meaning |
| - | - |
| `brand_daily_cap` | The brand reached its T-Mobile daily cap. Schedule it for the next day's sending hours with `POST /projects/{id}/schedule` (morning `scheduled_at`), or resume now with `daily_cap_bypass: true` (over-cap T-Mobile may fail, still billed). |
| `quiet_hours` | Paused when the project's sending window closes: 10 PM in the time zone of most recipients, or 10 PM Pacific when no time zone holds a majority. Restart manually once the window opens (8 AM Eastern when no time zone holds a majority). |
| `carrier_block_rate` | Carriers were blocking too many messages. |
| `unregistered_campaign` | The sending campaign is not registered. |
| `provider_error` | The messaging provider reported a critical error. |
| `insufficient_funds_auto_recharge_failed` | The wallet ran out and auto-recharge failed. |
| `insufficient_funds_ancestor` | A parent organization's wallet ran out. |
| `shared_phone_revoked` | A shared sending number was revoked. |
| `phone_released` | A sending number was released. |
| `organization_deleted` | The organization was deleted. |
| `manual` or free text | A user paused the project. Free text is a short reason of up to 100 characters. |

Other values may appear. Every automatic pause needs a manual restart via `POST /projects/{id}/schedule`.

**Running through the cap.** Send `daily_cap_bypass: true` on `POST /projects/{id}/schedule` to run the whole project in one pass. Messages to T-Mobile recipients over the cap may fail, and failed attempts are still billed. 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](/api-reference/webhooks/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 `200` within **10 seconds** (process asynchronously if needed).
* Always validate the **HMAC signature** before processing.
* Use `event_id` to **deduplicate** (rare but possible).
* Log failures and monitor your endpoint health.
* Use **HTTPS** endpoints (required in production).
* **Rotate secrets** periodically (every 90 days recommended).


This documentation is built and hosted on [Mintlify](https://mintlify.com), a developer documentation platform.