Calling API

Live

People call a business over the internet from its verified page, with no phone number and no airtime. This API is the business's side of those calls. It sets who may call and the queues they wait in, lists every call afterwards, and sends a signed webhook the moment a call queues, connects or ends.

Base URL https://calling.bolrach.comScopes calls:read, calls:admin, webhooks:manageSpec openapi/calling.json

Authentication

There are no API keys. A developer's server signs in to Authvio as an OAuth client of its verified organisation and asks for a token for the audience https://calling.bolrach.com. Bolrach registers that client with Calling for the organisation and names the scopes it may hold. A token carries only the scopes both sides allow and acts for that organisation alone; a request about any other organisation answers 404.

curl -X POST "https://id.authv.io/oauth2/token" \
  -d grant_type=client_credentials \
  -d audience=https://calling.bolrach.com \
  -d "scope=calls:read webhooks:manage" \
  -d client_assertion_type=urn:ietf:params:oauth:client-assertion-type:jwt-bearer \
  -d client_assertion=$SIGNED_ASSERTION

Start with GET /v1/whoami, which returns the client id, organisation, scopes and rate tier.

Callers you see

A caller signs in through Authvio, so every one of them is a real, verified person. Their identity never leaves Calling. Each caller appears to a client as a reference such as cr_9b2f…, the same every time that person calls the business. Another developer sees the same person under a different reference, so nobody can join their records with anyone else's.

Endpoints

GET PUT /v1/orgs/{org}/settings

Who may call and what happens on a call. Send only the fields that change; the answer is the full settings after the change.

FieldTypeDescription
caller_policy.min_levelaccount | phone | identity | customers | customThe least a caller must have proven in Authvio. phone is the default.
caller_policy.calls_per_hourinteger, 1 to 3Per caller. A business can set a lower limit than Bolrach's, never a higher one.
caller_policy.calls_per_dayinteger, 1 to 10Per caller, over the last 24 hours.
recording_modeoff | ask | alwaysThe caller always hears and sees a notice before anything is recorded.
notice_textstring or nullCustom wording for that notice, up to 300 characters: phone callers hear it read aloud.
retention_daysinteger, 30 to 365How long recordings and transcripts are kept.
max_queueintegerWhen this many people are waiting, new callers are asked to try later.
curl -X PUT "https://calling.bolrach.com/v1/orgs/3f6c1d2e-8a4b-4c7d-9e1f-2a3b4c5d6e7f/settings" \
  -H "Authorization: Bearer $AUTHVIO_ACCESS_TOKEN" \
  -H "Content-Type: application/json" \
  -d '{"caller_policy": {"min_level": "identity", "calls_per_hour": 2, "calls_per_day": 5}, "recording_mode": "ask"}'
GET /v1/orgs/{org}/queues · PUT /v1/orgs/{org}/queues/{name}

Each queue shows how many people answer it and how many are waiting. Internet callers and phone callers wait in the same queue, and the next caller goes to whoever has been free longest. Bolrach appoints the people who answer, from the organisation's Help desk and staff list.

curl "https://calling.bolrach.com/v1/orgs/3f6c1d2e-8a4b-4c7d-9e1f-2a3b4c5d6e7f/queues" \
  -H "Authorization: Bearer $AUTHVIO_ACCESS_TOKEN"
GET /v1/orgs/{org}/calls · /v1/orgs/{org}/calls/{id}

Newest first, up to 31 days at a time. Filter by state, page with the next_before value from the previous answer.

curl "https://calling.bolrach.com/v1/orgs/3f6c1d2e-8a4b-4c7d-9e1f-2a3b4c5d6e7f/calls?state=ended&limit=50" \
  -H "Authorization: Bearer $AUTHVIO_ACCESS_TOKEN"

Response

{
  "calls": [
    {
      "call_id": "7d0e2b5c-3a51-4f0f-8e57-6a3c3c2d9f11",
      "state": "ended",
      "mode": "callback",
      "channel": "internet",
      "created_at": "2026-10-03T12:00:00.000Z",
      "connected_at": "2026-10-03T12:00:42.000Z",
      "ended_at": "2026-10-03T12:05:54.000Z",
      "end_reason": "caller_hung_up",
      "caller": "cr_9b2f4c1d8e7a6b5c4d3e2f1a0b9c8d7e"
    }
  ],
  "next_before": null
}
GET /v1/orgs/{org}/usage?from=2026-10-01&to=2026-10-31

Calls, talk seconds, recording and transcript seconds, summaries and ring-out seconds, by day.

Webhooks

Add an https endpoint and choose its events. The answer shows the signing secret once. Store it then: Calling keeps it sealed and cannot show it again. Private addresses are refused, and the address is checked again before every delivery.

curl -X POST "https://calling.bolrach.com/v1/webhook-endpoints" \
  -H "Authorization: Bearer $AUTHVIO_ACCESS_TOKEN" \
  -H "Content-Type: application/json" \
  -d '{"url": "https://example.com/hooks/calling", "events": ["call.answered", "call.ended", "call.abandoned"]}'
EventSent when
call.queuedsomeone starts a call to you
call.offeredthe call is offered to one of your agents
call.answeredan agent picks it up
call.transferredan agent hands it to a colleague or another queue
call.endedan answered call hangs up
call.abandonedthe caller leaves before anyone answers
callback.requesteda caller asks to be rung when it is their turn
callback.missedthat caller did not pick up after two rings
recording.ready, transcript.ready, summary.readyafter a recorded call

Every delivery carries calling-signature, x-calling-event and x-calling-delivery. Check the signature against the raw body before trusting it. After POST /v1/webhook-endpoints/{id}/rotate both secrets sign for 24 hours, so a receiver can switch without missing anything. A delivery that gets no 2xx is retried for about a day. Keep the event id, because the same event can arrive twice.

import { createHmac, timingSafeEqual } from "node:crypto";

// header: the calling-signature value, e.g. "t=1790998274,v1=5f1c…"
export function verifyCalling(header, rawBody, secret, toleranceSeconds = 300) {
  const t = Number(/t=(\d+)/.exec(header)?.[1]);
  if (!t || Math.abs(Date.now() / 1000 - t) > toleranceSeconds) return false;
  const want = createHmac("sha256", secret).update(`${t}.${rawBody}`).digest();
  return [...header.matchAll(/v1=([0-9a-f]{64})/g)].some((m) => timingSafeEqual(Buffer.from(m[1], "hex"), want));
}

Limits and errors

A standard client may make 120 requests a minute. Past that the answer is 429 with details.retry_after_seconds. Every error has the same shape: error, a stable code to branch on, message, statusCode and request_id, which is the thing to quote to Bolrach support.

Changelog

DateChange
2026-10-03v1: settings, queues, calls, usage and signed webhooks.