Going live
Before switching to a live key, check that your integration is secure, handles errors and retries correctly, and verifies webhooks. This checklist covers what to confirm before you place real, billed calls.
In live mode calls reach real people and are charged to your workspace balance. Everything below can be tested in test mode first; nothing on this list should be discovered in production.
Before you begin
- Your integration works end to end in test mode: calls placed, webhooks received and verified, results written to your system, refusals handled.
- API access for live use. The API is in private preview and must be enabled for your workspace by RelationsAI. If any request answers
api_access_not_enabled, write to support@relationsai.com with your workspace name. Webhook delivery is a separate preview: checkcapabilities.webhookson GET/workspace. - Your agent is published and ready in live mode, with a live outbound number, and the workspace has calling set up and a balance (Creating and preparing agents).
Run the preflight check with the exact key your server will use. It reports the key's mode, scopes and expiry, the limits that apply, the agent's readiness and your webhook endpoints, and exits with status 1 if a check fails:
Ask the API what the key can do:
Response 200 (a live key):
JSON
Check api_key.environment, api_key.scopes (only what this component needs), api_key.expires_at, limits and capabilities.webhooks. Then check the agent and your endpoints:
Complete runnable example
Pre-launch checks for a key: mode, scopes, expiry, limits, agent readiness and webhook endpoints.
The same steps as a complete script, with error handling, tested against test mode on every docs build. Each script reads your test key from an environment variable named in its first lines.
- cURL script (bash)
preflight.sh - JavaScript (Node.js 20+)
preflight.mjs - Python 3.9+ (requests)
preflight.py
Keys and scopes
- A live key per component, created by a workspace admin in the platform (Settings → API Keys), with only the scopes that component needs. A component that only reads results has no
:writescope. Remember a key never does more than its creator's current role allows: create it from an account that will keep its role. - An expiry and an owner. Live keys expire (30, 90, 180 or 365 days). Put the expiry date in your calendar and rotate before it: create a new key, deploy it, and let the old one expire after a short grace period.
- Server-side only. Store the key in a secret manager or your platform's secret configuration, never in source code, a mobile app, browser code, a URL, a log or a ticket. Live keys are refused from browsers with
live_key_not_allowed_in_browser. - The IP allowlist, if your workspace uses one, includes your servers' egress addresses (otherwise
ip_not_allowed). - A leak plan. If a key leaks, an admin revokes it in the console immediately; requests with it then fail with
api_key_revoked.
See Security practices.
Error handling and retries
- Every response is checked for
res.ok; errors branch onerror.codeanderror.retry, never onerror.message. - No blind retries of writes: your own id in
metadata, and after a timeout or5xxa state check before resending; optionally anIdempotency-Keyper action, saved before the first attempt and reused on retries (Retry and reliability practices). - Retries use exponential backoff with jitter and a cap, honour
Retry-After, and never retryretry: "no". - "May have started" errors (
500 internal_error, timeouts,campaign_outcome_unknown,idempotency_outcome_unknown) lead to a state check, not to a fresh request. - Refusals (
dnc_listed,consent_required,outside_calling_hours,agent_not_ready,insufficient_balance, ...) are recorded as outcomes and surfaced to the people who can fix them, not retried. - Open enums: unknown statuses, outcomes, event types and error codes are handled as "other", never as success.
-
PATCHusesIf-Matchand handles412 version_conflictby re-reading.
Webhooks
- A live webhook endpoint (its own secret, separate from the test one) on a public HTTPS URL, port 443, with a valid certificate.
- Signature verification on the raw body, constant-time compare, 5-minute tolerance, accepting any of several
v1values (Verifying webhook signatures). - Deduplication by event id in durable storage, before answering.
- Answer
2xxwithin 10 seconds, process in the background, and sweep for acknowledged but unprocessed events. - Out-of-order events cannot overwrite newer state.
- A reconciliation job lists recent calls (and campaigns) to catch anything missed (Common integration patterns).
- A test event to the live endpoint
succeededin the delivery log.
Compliance
- An admin has reviewed the workspace's compliance settings in the console: consent requirement, calling window and per-number daily limit, for the countries you call.
- Opt-outs from every channel reach the do-not-call list immediately, and do-not-call status is synchronised in both directions with your systems (Consent and DNC).
- Consent is recorded through the API only from real records of consent.
- Nobody can route around a refusal: no retries with another number format, no automatic do-not-call removals, no consent set to unblock a call.
- Transcripts, recordings and analysis are stored and shared according to your data-protection obligations; recording links are never stored or logged (Retrieving recordings and transcripts).
Limits and spending
- You know the limits that apply (from GET
/workspace): rate limits per class,limits.daily_call_limitfor calls placed through the API per UTC day, andlimits.campaign_max_concurrency. If your volume needs a higher daily limit, ask support before launch, not on the day. - Batch jobs pace themselves under the
writeanddiallimits and readRateLimit-Remaining. - The workspace balance covers your expected volume, and someone is responsible for topping it up. Calls are refused with
insufficient_balancewhen it runs low, and running campaigns pause. - Campaigns have a
budget_cap(it pauses the campaign; the check runs about every 15 minutes, so allow for overshoot), and an admin has set a monthly spend limit if you want one (spend_limit_reached). - Campaign sizes fit the daily limit: a live start counts every recipient and is refused whole when they do not all fit.
- You handle automatic campaign pauses (
campaign.pausedwithdata.reason) and resume once the cause is fixed.
Monitoring
- Log
request_id(theX-Request-Idheader, also in every error body) with every failed request, together with the operation and the time. Support needs it to find your request. - Alert on: the rate of
4xxand5xxper code,429s, webhook deliveries that arefailedorretrying(from the delivery log or your own receiver), your inbox of unprocessed events, and calls withoutcall.settledafter an hour. - Track business outcomes as well: refusals by code,
no_answerandfailedrates, and spend per day. - Dashboards distinguish test from live (
livemodeon every object and event).
Launch
- Switch configuration to the live key and the live webhook secret. No code changes should be needed.
- Start with a small set of real records, ideally people who expect the call.
- Check the first results in both RelationsAI and your system: outcomes, analysis written to the right records, refusals recorded, charges as expected.
- Increase volume gradually, watching the alerts above.
What can go wrong
api_access_not_enabled(403): The workspace is not enabled for the API. Fix: Write to support@relationsai.com.live_key_not_allowed_in_browser(403): A live key used from browser code (a request with anOriginheader). Fix: Move the call to your server.resource_not_found(404) for ids that worked yesterday: Test-mode ids used with the live key. Fix: Use live ids; nothing is shared between modes except agents and numbers.agent_not_ready(422): The live agent is not published, or has no outbound number. Fix: Fix it in the console and check readiness.insufficient_balance(402): No balance. Fix: An admin tops up.daily_call_limit_reached(429): Volume above the daily limit, or a retry storm. Fix: Check for loops; ask support about the limit.- No live webhooks arrive: The endpoint was created with the test key. Fix: Create a live endpoint with the live key.