Developers · MAX plan

API and webhooks

Read your recaps, commitments, tasks and contacts from your own tools, and get a signed notification every time a recap is ready. Read-only. Available on the MAX plan.

Read-only JSON over HTTPS Signed webhooks
Get your key Webhooks
On this page

Overview

The CallRecap API lets your own systems read what CallRecap already produced from your calls. Every endpoint is a GET that returns JSON, and a webhook tells you when a new recap exists.

Base URL
URL
https://api.callrecap.app/v1

Read-only

All endpoints are GET.

Plan

Available on the MAX plan.

Auth

Bearer token starting with crk_.

Webhook

recap.ready, signed with HMAC-SHA256.

Quick start

  1. Create a token

    In the app: Settings → API & webhooks → Create token. It starts with crk_ and is shown once, so copy it right away.

  2. Make your first request

    /me must answer 200 with your plan. 401 means a wrong token, 403 means the account is not on MAX.

    curl
    BASE="https://api.callrecap.app/v1"
    curl -H "Authorization: Bearer crk_..." "$BASE/me"
  3. Read your latest recaps

    List three recaps, then open one with /recaps/{id}. Compare the summary and the transcript with the app: it is the same data from the same tables, nothing is regenerated on the way.

    curl
    curl -H "Authorization: Bearer crk_..." \
      "$BASE/recaps?limit=3"

Authentication

In the app: Settings → API & webhooks → Create token. The token starts with crk_ and is shown once. Store it like a password. You can keep up to 5 tokens and revoke any of them at any time.

Every request carries it as a bearer token:

HTTP header
Authorization: Bearer crk_...

Keep tokens safe. Store them in a secrets manager or a password vault, never in shared documents, chats or client-side code. Use one token per system so you can revoke one without touching the others.

Endpoints

All endpoints are GET and return JSON. since and until are ISO 8601 timestamps; limit is 1 to 50 (default 20).

GET/me

Your user id, plan, timezone and limits.

Example request
curl
curl -H "Authorization: Bearer crk_..." \
  "$BASE/me"
Example response
JSON
{
  "user_id": "…",
  "plan": "max",
  "timezone": "Europe/Madrid",
  "member_since": "2026-05-02T10:14:00Z",
  "limits": { "requests_per_minute": 60, "max_page_size": 50 },
  "version": "v1"
}
GET/recaps

Calls that have a recap, newest first: call metadata, linked contact and the recap (summary, topics, decisions, open questions, next call, coaching, sentiment, participants).

Query parameters
NameTypeDescription
sinceISO 8601Only recaps from this time on.
untilISO 8601Only recaps up to this time.
contact_idUUIDOnly calls linked to this contact.
cursorstringThe next_cursor from the previous page.
limit1 to 50Page size, default 20.
Example request
curl
curl -H "Authorization: Bearer crk_..." \
  "$BASE/recaps?since=2026-09-01T00:00:00Z&limit=20"
Response shape
JSON
{ "data": [ … ], "next_cursor": "…" }
GET/recaps/{recording_id}

One call with everything above plus the transcript (text and segments with speaker names), the tasks and the commitments extracted from it.

Path parameter
NameTypeDescription
recording_idUUIDThe call to read, as returned by /recaps or the webhook.
Example request
curl
curl -H "Authorization: Bearer crk_..." \
  "$BASE/recaps/{recording_id}"
GET/commitments

What you owe (owner=user) and what others owe you (owner=contact).

Query parameters
NameValuesDescription
statusopen | doneFilter by status.
owneruser | contactWho owes it.
cursorstringThe next_cursor from the previous page.
limit1 to 50Page size, default 20.
Example request
curl
curl -H "Authorization: Bearer crk_..." \
  "$BASE/commitments?status=open&owner=contact"
GET/tasks

Tasks extracted from your calls.

Query parameters
NameValuesDescription
statuspending | doneFilter by status.
cursorstringThe next_cursor from the previous page.
limit1 to 50Page size, default 20.
Example request
curl
curl -H "Authorization: Bearer crk_..." \
  "$BASE/tasks?status=pending"
GET/contacts

Your contacts.

Query parameters
NameValuesDescription
cursorstringThe next_cursor from the previous page.
limit1 to 50Page size, default 20.
GET/contacts/{id}

One contact with relationship summary, portrait, briefing and known identities.

Example request
curl
curl -H "Authorization: Bearer crk_..." \
  "$BASE/contacts/{id}"

Pagination

List responses look like this. Pass cursor=<next_cursor> to get the next page. next_cursor is null on the last page.

JSON
{ "data": [...], "next_cursor": "..." }

Errors

Errors come with an HTTP status and this body:

JSON
{ "error": { "code": "...", "message": "..." } }
StatusCodeMeaning
400bad_requestA parameter is not valid, for example an id that is not a UUID or an unknown status.
401unauthorizedMissing, wrong or revoked token.
403plan_requiredThe API is part of the MAX plan.
404not_foundNot found.
405method_not_allowedOnly GET is supported.
429rate_limited, daily_limit_reachedRate limited. See Limits.
500server_errorSomething failed on our side. Try again later.

Limits

LimitValue
Requests per minute60 per token
Requests per day5,000 per token (the daily counter resets at 00:00 UTC)
Page sizelimit 1 to 50, default 20
TokensUp to 5 per account

A 429 answer includes Retry-After and the error code rate_limited or daily_limit_reached.

For most integrations that is 50 to 500 times the normal usage: poll every few minutes, not every few seconds, and rely on the webhook to know when a new recap exists.

Webhook: recap.ready

Set it up

In the app: Settings → API & webhooks → Webhook. Enter an https:// URL. You get a secret (whsec_...) once.

Payload

Each time a recap is ready we POST a small JSON to your URL. The body carries ids and dates only; fetch the content with api_url and your token.

JSON
{
  "event": "recap.ready",
  "recording_id": "…",
  "analysis_id": "…",
  "call_started_at": "2026-09-11T16:20:25Z",
  "duration_seconds": 114,
  "call_type": "outgoing",
  "contact_name": "…",
  "language": "es",
  "recap_ready_at": "2026-09-11T16:22:40Z",
  "api_url": "https://…/api-v1/recaps/{recording_id}",
  "delivery_id": "…"
}

Headers on every delivery

HeaderValue
X-CallRecap-Eventrecap.ready or ping (the test button).
X-CallRecap-DeliveryUnique id of this delivery. Use it to ignore duplicates.
X-CallRecap-Signaturet=<unix seconds>,v1=<hex HMAC-SHA256>

Verify the signature

  1. Read the header

    Take t and v1 from X-CallRecap-Signature.

  2. Compute the HMAC

    The signed message is t + "." + raw body and the key is your secret. Hex HMAC-SHA256.

  3. Compare and check the time

    Compare in constant time with v1 and reject timestamps more than 300 seconds away from now.

Node.js
const crypto = require('crypto');
function verify(rawBody, header, secret) {
  const t = /t=(\d+)/.exec(header)?.[1];
  const v1 = /v1=([0-9a-f]+)/.exec(header)?.[1];
  if (!t || !v1) return false;
  const expected = crypto.createHmac('sha256', secret).update(`${t}.${rawBody}`).digest('hex');
  return crypto.timingSafeEqual(Buffer.from(expected), Buffer.from(v1))
    && Math.abs(Date.now() / 1000 - Number(t)) < 300;
}

Responses and retries

Answer with any 2xx within 10 seconds. Otherwise we retry after 5 and 30 minutes, then 2 and 12 hours, for five attempts in total.

After 20 consecutive failures the webhook is paused; enable it again from the app once your endpoint is fixed. The app shows the last 20 deliveries with their status.

Your data, in your hands

The API hands your recaps, transcripts and contacts to your own tools. From that moment they are under your care: store them safely, respect the people who appear in your calls and follow the law that applies to you. Treat the token like a password. The first time you create a token or a webhook, the app asks you to accept the API terms; this is the short version.

Good to know

  • If your plan stops being MAX, tokens and webhooks stop working (they are not deleted). They work again when you return.
  • Revoking a token or deleting the webhook is immediate.
  • The token is shown only once when you create it. Treat it like a password: store it in a secrets manager or a password vault, never in shared documents, chats or client-side code. Use one token per system so you can revoke one without touching the others.
  • Tokens do not expire on their own. Revoke the ones you stop using.
  • Data you read through the API leaves CallRecap and lands in your own systems: from then on, keeping it safe is your responsibility.
  • We never show the token or the secret again after creation. Rotate the webhook secret from the app if needed.

Test it in five minutes

  1. Token

    Settings → API & webhooks → Create token. Copy it once.

  2. First call

    curl -H "Authorization: Bearer crk_..." "<base>/me" must answer 200 with your plan. 401 = wrong token, 403 = not MAX.

  3. Your latest recaps

    /recaps?limit=3, then one /recaps/{id}. Compare the summary and the transcript with the app: it is the same data from the same tables, nothing is regenerated on the way.

  4. Webhook

    Create a free inbox at webhook.site, paste its URL in Settings → API & webhooks → Webhook, keep the secret, press "Send test". A ping arrives within a minute with the three X-CallRecap-* headers. Verify the signature with the snippet above.

  5. Real event

    Record a call. When the recap is ready a recap.ready arrives; fetch api_url with your token. The app lists the last 20 deliveries with status and attempts.

What works with it

Anything that can call an HTTPS API with a bearer token or receive a webhook:

ZapierMaken8nPipedreamGoogle Sheets via Apps ScriptA CRM with a custom importerYour own backend

The webhook carries ids only; the content always comes through the API, so nothing sensitive travels to an intermediary that only relays events.