The CallingScout object model + how the pieces fit together.
One tenant per account. Every resource (agent, call, campaign, number, webhook, API key) lives under exactly one tenant. Tenants are created lazily on first Clerk sign-in (dev) or explicitly by the webhook on Clerk org creation (production).
Authenticate every REST call. Prefixed sk_live_ (production) or sk_test_ (sandbox). Bcrypt-hashed on the server — we show the plaintext once on creation, never again. Pass as Authorization: Bearer <key>.
See the API-key section of the quickstart for creation flow + sandbox semantics.
An agent is a configured AI voice persona: pipeline type (cascaded | realtime), provider choices (STT / LLM / TTS or S2S), system prompt, first message, tool list, kill switch (is_active). Agents are cheap to create + edit; changes take effect on the next call.
The built-in library ships 17 templates across HR, sales, healthcare, insurance, collections, support — GET /api/v1/agent-templates. Clone a template into your own agent, tweak, ship.
A call is one dial attempt. Created by POST /api/v1/calls (outbound) or the provider webhook (inbound). Walks through:
QUEUED → RINGING → IN_PROGRESS → COMPLETED
├─→ FAILED
├─→ BUSY
├─→ NO_ANSWER
└─→ VOICEMAIL
Every call is gated by the compliance layer before dispatch. On success you get the full post-call bundle: transcript, recording, per-service cost breakdown, outcome classification.
A campaign dispatches an agent against a list of contacts at a configured rate. Uploaded via CSV, paced via a Redis token bucket (max_concurrent + calls_per_minute), scoped to the agent's calling hours. Stale-contact recovery restarts dialing on worker restart.
Lifecycle webhooks: campaign.started, campaign.contact.completed, campaign.completed.
Numbers provisioned through CallingScout are bought from Twilio or Telnyx on your behalf and are automatically caller-ID verified + STIR/SHAKEN level A attested. You dial from them the moment POST /api/v1/phones/provision returns.
If you want to use a number you already own as the from_number, go through the OTP verification flow first. Outbound dispatch refuses unverified from_numbers and any number attested at STIR/SHAKEN level B or C.
Every outbound call passes through check_compliance() before dispatch — DNC registry, TCPA calling hours, two-party consent state detection, capacity, AI first-utterance disclosure. Results are persisted to compliance_checks; a Postgres trigger refuses to INSERT a call that references a denied check. Not bypassable from the application layer even if a future bug tried to.
See /security for the full posture, and compliance/gate.py for the code.
Outbound event delivery to your systems. 15 event types across call lifecycle, post-call artifacts, campaigns, agents, usage. Per-subscription signing secret, at-least-once delivery, 30-day replay history, test-fire for CI. See docs/webhooks.md.
A mode toggle built into the API key itself. sk_test_ keys produce sandbox requests:
is_test_call = true on the calls row.sandbox: true. Production subs never receive sandbox events, and vice versa.Use for CI, load tests against fixtures, and local dev without burning real minutes.
Every mutating endpoint (POST/PATCH/PUT/DELETE) accepts an Idempotency-Key header. On a retry within 24 hours with the same key, we return the cached response with X-Idempotent-Replay: true — your handler doesn't re-run. Pattern matches Stripe. The SDKs auto-generate a UUID per request unless you override.
Per-tenant, per-endpoint-class. Current classes:
| Class | Default limit | Covered endpoints |
|---|---|---|
default | 60 req/min | GET, list, detail endpoints |
dial | 10 req/min | POST /calls, POST /phones/provision |
campaign-dispatch | 120 req/min | campaign engine's internal dispatch path |
Every response carries X-RateLimit-Limit, X-RateLimit-Remaining, X-RateLimit-Reset. 429 includes Retry-After (seconds).
List endpoints support cursor-based pagination. Pass ?cursor=<opaque> to continue. Response includes has_more and next_cursor. Cursor is O(1) and stable under writes. Don't parse the cursor; it's opaque.
Path-versioned at /v1/. Breaking changes bump to /v2/; /v1/ is supported for at least 12 months after /v2/ ships. SDKs track API major via their own semver — callingscout@1.x.y speaks /v1/.
By design:
/api/v1/webhooks/twilio, /api/v1/webhooks/telnyx, /api/v1/webhooks/stripe. Signed by the provider, intended only for provider → us./api/v1/clerk/webhook.These routes exist and work, but they're excluded from the public OpenAPI. The SDKs don't expose them.