Idempotency and rate limits
Two things separate an integration that works in a demo from one that works at 3 am under load: it retries safely, and it stays under limits it knows about. Both are simple once the rules are explicit.
Retrying safely
Most Talkif endpoints are naturally safe to retry: a GET is harmless, a PUT sets the same state twice, a DELETE of something already gone succeeds. The one that isn’t is POST /calls — retried blindly after a timeout, it can dial the same person twice.
Talkif doesn’t currently accept a client-supplied idempotency key on call creation. Use the request’s own identity instead:
Attach the contact
Pass contactId on POST /calls whenever you have one. It makes the call findable by contact, and it means a duplicate would be a second call to the same contact record — easy to spot.
On timeout, look before you retry
A timeout means you don’t know whether the call was created. Before re-issuing, list recent calls — GET /calls filtered by contactId (or phoneNumber) with startDate a few minutes back. A call created since your request → done. Nothing → retry.
Keep the SDK's retries for the rest
The official SDKs retry 408, 429 and 5xx twice with backoff. That’s right for every endpoint except call creation; for POST /calls, pass maxRetries: 0 and do the check above.
Campaigns are idempotent by construction: adding a contact who’s already in the campaign doesn’t add them twice, and a campaign never redials a contact whose call was answered.
What’s rate-limited
Defaults are per account and can be raised — contact support with your expected peak concurrency. The account’s current values are shown in the dashboard’s account settings, and Limits explains how the queue prioritises when you’re at the ceiling.
A limited request gets a 429 (or a 4xx with the error code above) and a JSON error body:
Every API error has this shape — code is the field to branch on, requestId is what to quote to support. The full catalogue is on Error codes.
Back off exponentially from the first 429, jitter the delay, and never retry in a tight loop — the limiter counts attempts, so a hot retry loop keeps you limited longer.
Designing for the limits
- Batch through campaigns, not
POST /callsloops. A campaign paces itself to your concurrency and windows, retries no-answers, and skips DNC — a loop ofPOST /callshas to reimplement all of that and will hit the per-minute limit at 60. - Treat
202as success. A queued call will be placed; readqueueIdandpositionand move on. Only4xx/5xxneed handling. - Use the event stream, not polling. Real-time events removes almost all read traffic from an integration.
- One WebSocket per service instance, re-subscribing on reconnect — not one per call. The per-account connection cap is 20.
- Set concurrency to what your own systems can absorb. If every call creates a ticket in your helpdesk, 50 concurrent calls create tickets at a rate your helpdesk’s API may not like.