Skip to content

Ringfully is now Inbound CX What’s new

Type a word or two. Press Escape to close.

API reference · v1

Every endpoint in version 1, on one page

Calls, contacts and text messages behind an API key. Version 1 has answered at /api/v1 since August 2026, and within it fields are added, never removed or renamed.

11 endpoints

Browse by resource

Your key

Calls

Contacts

Text messages

RequestGET

GET /api/v1/calls

curl "https://api.inboundcx.com/api/v1/calls?direction=inbound&limit=1" \
  -H "Authorization: Bearer $API_KEY"
Response200 OK
{
  "calls": [
    {
      "id": "7c9e6679-7425-40de-944b-e07fc1f90ae7",
      "direction": "inbound",
      "fromNumber": "+15145550142",
      "toNumber": "+14385550199",
      "status": "completed",
      "startedAt": "2026-09-11T15:04:12.000Z",
      "answeredAt": "2026-09-11T15:04:19.000Z",
      "endedAt": "2026-09-11T15:07:31.000Z",
      "durationSec": 192,
      "isInternal": false,
      "agentId": "a3f1c2d4-5b6e-4f70-8a9b-0c1d2e3f4a5b",
      "contactId": "e2b4c6d8-1a3f-4b5d-9e7c-8f0a2b4c6d8e"
    }
  ],
  "nextCursor": "7c9e6679-7425-40de-944b-e07fc1f90ae7"
}

Keys and scopes

Every request carries an API key as a bearer token. Keys are created in the portal under Settings, API Keys, on the Pro and Business plans. The full key is shown once and stored as a hash, so a lost key is replaced rather than recovered.

Authorization: Bearer rk_live_…

A key belongs to one organization, and the organization is resolved from the key itself. There is no account id to send. If the organization moves to a plan without API access, its keys are refused with 403 until it moves back.

https://api.inboundcx.com/api/v1

Open API Keys in the portal

Scopes

What is enforced on each request is the intersection of the key’s own scopes and these five, so a key can never hold something absent from them. A key created in the portal holds all five. Ask GET /api/v1/me for the scopes a key actually has, which is how you tell not allowed from not implemented.

ScopeAccessWhat it allows
calls.view.teamReadRead call history.
contacts.viewReadRead the directory.
contacts.manageWriteCreate, change and delete contacts.
sms.viewReadRead text conversations.
sms.sendWriteSend text messages, which bills the organization.

The machine-readable description is an OpenAPI 3.1 document, generated from the same schemas the handlers validate against and served without a key.

Open the OpenAPI document

Three events, each one signed

Each delivery is an HTTPS POST signed with HMAC-SHA256 over the timestamp and the raw body. A failed one is retried with exponential backoff: 8 attempts in all, over a little more than two hours.

  • call.completedA call ended: direction, both numbers, status, who answered, the duration and the times.
  • voicemail.receivedA caller left a message and the recording is ready. The transcript comes with it when it has already arrived.
  • sms.receivedA text message arrived on one of the organization’s numbers, with its body.
  • 8 attemptsRetried with exponential backoff from 1 minute, capped at 60, over a little more than two hours.
  • At least onceEvery retry carries the same X-InboundCX-Delivery value. Use it to ignore a repeat.
  • Delivery historyEach endpoint in the portal lists its recent deliveries: status, attempts, the next retry and the last error.

The headers

HeaderWhat it carries
X-InboundCX-Event
X-Ringfully-Event Deprecated
The event name, for example call.completed.
X-InboundCX-Timestamp
X-Ringfully-Timestamp Deprecated
Milliseconds since the Unix epoch. Use the header value as an opaque string; do not parse and reformat it.
X-InboundCX-Signature
X-Ringfully-Signature Deprecated
Lowercase hex HMAC-SHA256 over the signed payload below.
X-InboundCX-Delivery
X-Ringfully-Delivery Deprecated
Stable across every retry of one delivery. Use it as the idempotency key: delivery is at least once, never exactly once.

Every delivery also carries the same four headers under their deprecated names, from X-Ringfully-Event to X-Ringfully-Delivery, with identical values. Those names will be removed 12 months after the rename: read the X-InboundCX ones. Nothing else changes, because the signature covers neither name.

An attempt that does not answer with a 2xx within 10 seconds counts as failed, and a redirect is not followed. Delivery is queued, so an event can arrive up to a minute after it happened. The address must be https, on a host your organization has approved.

Verifying a delivery

  1. Read X-InboundCX-Timestamp as a string, exactly as sent.
  2. Take the raw request body, before any JSON parsing. Parsing and re-serialising does not round-trip byte for byte and will not match.
  3. Join them: signedPayload = timestamp + "." + rawBody.
  4. Compute HMAC-SHA256 of that with your endpoint secret, as lowercase hex.
  5. Compare it to X-InboundCX-Signature with a constant-time comparison.
  6. Reject a timestamp more than five minutes from your own clock. Step five proves we sent it; only this step proves we sent it now.
import crypto from "node:crypto";

// Deprecated names with the same values: x-ringfully-timestamp, x-ringfully-signature.
const timestamp = req.get("x-inboundcx-timestamp") ?? "";
const signature = req.get("x-inboundcx-signature") ?? "";
const expected = crypto
  .createHmac("sha256", process.env.INBOUNDCX_WEBHOOK_SECRET)
  .update(`${timestamp}.${rawBody}`)
  .digest("hex");
const valid =
  expected.length === signature.length &&
  crypto.timingSafeEqual(Buffer.from(expected), Buffer.from(signature)) &&
  Math.abs(Date.now() - Number(timestamp)) < 5 * 60 * 1000;

The sample reads the secret from INBOUNDCX_WEBHOOK_SECRET. An integration that keeps it in RINGFULLY_WEBHOOK_SECRET holds the same value, so renaming the variable is optional.

Limits and versioning

Rate limits

120 requests a minute per key, and 600 a minute per network before a key is checked. The limit follows the key rather than the organization, so a batch job can have a key of its own without slowing the live integration.

Responses carry RateLimit-Limit, RateLimit-Remaining and RateLimit-Reset. A refused request gets 429 with Retry-After.

Versioning

Version 1, at /api/v1, is the only version, served since August 2026. There is no version header to send.

Within v1, fields are added and never removed or renamed. New optional parameters, new response fields and new endpoints can appear at any time, so ignore fields you do not recognise. Anything that would remove or rename a field ships as v2, at a new path.

Open the OpenAPI document

What version 1 does not cover

Three things are missing on purpose, and each is a decision rather than a queue position.

  • Live call control (hold, transfer, conference, record). It only makes sense while somebody is on the call, and there is no realtime channel here yet, so an integration could not know a call was live.
  • Recordings and voicemail audio. It is the most sensitive material we hold, and handing it to a credential pasted into a third-party automation tool is not a default.
  • Users, roles, billing, phone numbers, call flows and emergency settings. They change the account, and a leaked key must not be able to invite a user, move a number, or edit where a company’s calls go.

Ready to answer every call?

See how Inbound CX picks up and routes your calls, in a 15-minute walkthrough.

Book a demo