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_ASSERTIONStart 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
Who may call and what happens on a call. Send only the fields that change; the answer is the full settings after the change.
| Field | Type | Description |
|---|---|---|
caller_policy.min_level | account | phone | identity | customers | custom | The least a caller must have proven in Authvio. phone is the default. |
caller_policy.calls_per_hour | integer, 1 to 3 | Per caller. A business can set a lower limit than Bolrach's, never a higher one. |
caller_policy.calls_per_day | integer, 1 to 10 | Per caller, over the last 24 hours. |
recording_mode | off | ask | always | The caller always hears and sees a notice before anything is recorded. |
notice_text | string or null | Custom wording for that notice, up to 300 characters: phone callers hear it read aloud. |
retention_days | integer, 30 to 365 | How long recordings and transcripts are kept. |
max_queue | integer | When 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"}'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"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
}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"]}'| Event | Sent when |
|---|---|
call.queued | someone starts a call to you |
call.offered | the call is offered to one of your agents |
call.answered | an agent picks it up |
call.transferred | an agent hands it to a colleague or another queue |
call.ended | an answered call hangs up |
call.abandoned | the caller leaves before anyone answers |
callback.requested | a caller asks to be rung when it is their turn |
callback.missed | that caller did not pick up after two rings |
recording.ready, transcript.ready, summary.ready | after 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
| Date | Change |
|---|---|
| 2026-10-03 | v1: settings, queues, calls, usage and signed webhooks. |