Skip to content
RelationsAI
Guides

Going live

Available

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: check capabilities.webhooks on 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:

curl -X GET https://voice-api.relationsai.com/v1/workspace \ -H "Authorization: Bearer <token>"

Response 200 (a live key):

JSONJSON
{ "id": "ws_7Hk2Lm9QxR4tB8vN3cZp6W", "object": "workspace", "livemode": true, "name": "Acme Lending", "timezone": "Asia/Kolkata", "currency": "INR", "api_key": { "id": "key_9Bv2Cx7Zl4Kj8Hg3Fd6Sa5", "name": "CRM sync", "environment": "live", "scopes": [ "workspace:read", "calls:read", "calls:write" ], "created_at": "2026-10-21T06:00:00.000Z", "expires_at": "2027-01-19T06:00:00.000Z" }, "capabilities": { "campaign_pause": true, "campaign_resume": true, "call_ringing_status": false, "live_transcript": false, "sentiment_analysis": false, "end_call": false, "agent_editing": false, "inbound_calls": false, "webhooks": false }, "limits": { "rate_limits": { "read": { "per_key_per_minute": 600, "per_workspace_per_minute": 1200 }, "write": { "per_key_per_minute": 120, "per_workspace_per_minute": 240 }, "dial": { "per_key_per_minute": null, "per_workspace_per_minute": 30 } }, "daily_call_limit": 500, "campaign_max_concurrency": 10 } }

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:

curl -X GET https://voice-api.relationsai.com/v1/agents/agent_1ZltiWzxrXmMprGD6q63/readiness \ -H "Authorization: Bearer <token>"
curl -X GET "https://voice-api.relationsai.com/v1/webhook-endpoints?limit=100" \ -H "Authorization: Bearer <token>"
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.

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 :write scope. 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 on error.code and error.retry, never on error.message.
  • No blind retries of writes: your own id in metadata, and after a timeout or 5xx a state check before resending; optionally an Idempotency-Key per 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 retry retry: "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.
  • PATCH uses If-Match and handles 412 version_conflict by 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 v1 values (Verifying webhook signatures).
  • Deduplication by event id in durable storage, before answering.
  • Answer 2xx within 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 succeeded in 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_limit for calls placed through the API per UTC day, and limits.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 write and dial limits and read RateLimit-Remaining.
  • The workspace balance covers your expected volume, and someone is responsible for topping it up. Calls are refused with insufficient_balance when 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.paused with data.reason) and resume once the cause is fixed.

Monitoring

  • Log request_id (the X-Request-Id header, 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 4xx and 5xx per code, 429s, webhook deliveries that are failed or retrying (from the delivery log or your own receiver), your inbox of unprocessed events, and calls without call.settled after an hour.
  • Track business outcomes as well: refusals by code, no_answer and failed rates, and spend per day.
  • Dashboards distinguish test from live (livemode on every object and event).

Launch

  1. Switch configuration to the live key and the live webhook secret. No code changes should be needed.
  2. Start with a small set of real records, ideally people who expect the call.
  3. Check the first results in both RelationsAI and your system: outcomes, analysis written to the right records, refusals recorded, charges as expected.
  4. 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 an Origin header). 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.

Next steps