Skip to main content

BeastCaller API documentation

Push leads into your BeastCaller campaigns from your own systems and keep them up to date. Outbound webhooks and Quick Actions send data the other way. The API is small on purpose: today it covers lead intake.

Base URL

https://api.beastcaller.app/ext/v1

Endpoints

External API endpoints
EndpointWhat it does
POST /leadsCreate one or more leads in a campaign
PATCH /leads/{externalId}Update a lead by your own external ID

Requests and responses are JSON over HTTPS (request bodies up to 2 MB). Call the API from your server or an automation tool such as Zapier, Make or n8n — never from browser code, where your key would be visible.

Authentication

Send your workspace API key in the X-Api-Key header with every request:

X-Api-Key: ck_your_api_key
  • A key belongs to one workspace and only reaches that workspace’s campaigns and leads.
  • Keys start with ck_ followed by 40 hexadecimal characters. A key is shown once when it is created; BeastCaller only stores a hash of it.
  • A missing key or an unknown key returns 401.
  • Treat the key like a password: keep it on your server or in your tool’s secret store.

Getting a key: keys are created by a workspace owner or admin. There is no self-serve API key screen in the app yet — email support@beastcaller.app from your owner or admin account and we will help you set one up.

Rate limits

Each API key can make 100 requests per minute. Send leads in batches rather than one request per lead. Responses carry these headers:

Rate limit headers
HeaderMeaning
X-RateLimit-LimitRequests allowed per minute (100)
X-RateLimit-RemainingRequests left in the current window
X-RateLimit-ResetWhen the window resets (Unix time, seconds)

Over the limit, the API answers 429 with a Retry-After header (seconds):

HTTP/1.1 429 Too Many Requests
Retry-After: 42

{
  "error": "Too Many Requests",
  "message": "Rate limit exceeded. Try again in 42 seconds.",
  "retryAfter": 42
}

Create leads

POST/ext/v1/leads

Adds one or more leads to a campaign in your workspace. New leads appear in the campaign’s queue in the phase given by status (Queued by default).

Request body

Create leads — request body
FieldTypeRequiredDescription
campaignIdstringYesID of a campaign in your workspace. It is the last part of the campaign page URL (/campaigns/{id}).
leadsarrayYesOne or more lead objects (see below). An empty array is rejected.

Lead object

Lead object fields
FieldTypeRequiredDescription
phonestringYesStored as sent (whitespace trimmed). Use international E.164 format, e.g. +4520123456, so the dialer can call it.
namestringNoContact name.
companystringNoCompany name.
citystringNoCity.
nichestringNoIndustry or segment label.
externalIdstringNoYour own ID for the lead (e.g. your CRM record ID). Needed to update it later.
statusstringNoWhere the lead starts in the campaign. See the status values table. Default: Queued.

Other fields are ignored.

Status values

Status values and the campaign phase they map to
statusCampaign phase
queued, newQueued
attempting, attempting_contact, in_progress, retryAttempting
connectedConnected
follow_up, follow-up, follow_up_requiredFollow Up
not_interested, invalid, do_not_call, skippedNot Interested
closed, completedClosed
anything else, or no statusQueued

If the campaign has no matching phase, the lead goes to Queued.

Example request

curl -X POST https://api.beastcaller.app/ext/v1/leads \
  -H "X-Api-Key: ck_your_api_key" \
  -H "Content-Type: application/json" \
  -d '{
    "campaignId": "your_campaign_id",
    "leads": [
      {
        "externalId": "crm-1042",
        "phone": "+4520123456",
        "name": "Jane Doe",
        "company": "Acme ApS",
        "city": "Aarhus",
        "niche": "Solar"
      }
    ]
  }'

Response

HTTP/1.1 201 Created

{
  "inserted": 1
}
  • inserted is the number of leads in your request.
  • Leads are not de-duplicated: sending the same lead twice creates it twice.
  • If any lead in the batch has no phone string, the whole request is rejected and nothing is created.
  • Each new lead triggers a lead_created webhook (see Outbound webhooks).

Update a lead

PATCH/ext/v1/leads/{externalId}

Updates the lead(s) in your workspace with the given externalId — the ID you sent when you created the lead. Send at least one of the fields below.

Request body

Update a lead — request body
FieldTypeRequiredDescription
statusstringNoMoves the lead to the matching campaign phase (same values as for creating leads). Unrecognised values move the lead to Queued.
metaany JSONNoStored on the lead as-is. Replaces any meta value stored before.

Example request

curl -X PATCH https://api.beastcaller.app/ext/v1/leads/crm-1042 \
  -H "X-Api-Key: ck_your_api_key" \
  -H "Content-Type: application/json" \
  -d '{
    "status": "follow_up",
    "meta": { "dealStage": "proposal-sent" }
  }'

Response

HTTP/1.1 200 OK

{
  "updated": 1
}
  • updated is the number of leads changed. Every lead in your workspace with that externalId is updated.
  • A successful update triggers a lead_updated webhook.

Errors

Error status codes
StatusWhen
401The X-Api-Key header is missing, or the key is unknown.
429The key made more than 100 requests in the current minute.
500The request was rejected by the endpoint — for example a missing campaignId, an empty leads array, a lead without phone, a campaign that is not in your workspace, nothing to update, or an unknown externalId — or an unexpected server error.

Validation and lookup problems currently come back with status 500 as well, not 400/404. Read the error message in the body: it says what to fix.

401 response

HTTP/1.1 401 Unauthorized

{
  "ok": false,
  "error": {
    "code": "auth.unauthenticated",
    "message": "Invalid API key",
    "correlationId": "…"
  },
  "status": "fail",
  "message": "Invalid API key"
}

Endpoint error response

HTTP/1.1 500 Internal Server Error

{
  "error": "campaignId is required"
}

Outbound webhooks

Get a POST request in your own system when leads are created or updated through the API. A workspace owner or admin adds an HTTPS endpoint under Workspace Settings → Integrations → Custom Webhook and picks the events to send. All selected events go to the same URL.

Events

Webhook events
EventSent when
lead_createdOnce per lead created through POST /ext/v1/leads
lead_updatedAfter a lead is updated through PATCH /ext/v1/leads/{externalId}

The webhook settings also list Call Ended and Campaign Finished. Those events are not triggered by normal use of the app yet (calls in the call console, pausing or resuming campaigns), so do not build on them. For automation during a call, use Quick Actions.

Delivery

  • JSON POST with the headers X-BeastCaller-Event (the event name) and X-BeastCaller-Signature: the hex HMAC-SHA256 of the raw body, keyed with your workspace’s webhook secret. The secret is not shown in the app yet — email support@beastcaller.app if you want to verify signatures.
  • Answer with any 2xx status. Redirects are not followed.
  • Failed deliveries are retried automatically. After repeated failures a delivery is marked failed; you can see every delivery, and replay it, in the Webhook Deliveries log of the integration settings.
  • timestamp is when the event was sent. Lead fields you did not provide are null.

lead_created

POST https://your-endpoint.example.com/beastcaller
Content-Type: application/json
X-BeastCaller-Event: lead_created
X-BeastCaller-Signature: <hex HMAC-SHA256 of the body>

{
  "event": "lead_created",
  "workspaceId": "…",
  "timestamp": "2026-09-29T10:15:00.000Z",
  "lead": {
    "phone": "+4520123456",
    "name": "Jane Doe",
    "company": "Acme ApS",
    "city": "Aarhus",
    "niche": "Solar",
    "externalId": "crm-1042",
    "externalSource": "api"
  }
}

lead_updated

changes contains what the update wrote: phaseId (the internal ID of the new phase) and/or meta.

{
  "event": "lead_updated",
  "workspaceId": "…",
  "timestamp": "2026-09-29T10:20:00.000Z",
  "lead": {
    "externalId": "crm-1042",
    "changes": {
      "phaseId": "…",
      "meta": { "dealStage": "proposal-sent" }
    }
  }
}

Quick Actions

Quick Actions are one-click buttons in the call console. When a caller clicks one, BeastCaller POSTs the current lead to your HTTPS endpoint — for example to send a contract, book a meeting, update your CRM or start a Zapier or Make scenario.

  • Owners and admins set them up per campaign under Campaign settings → Quick Actions: a label, your HTTPS URL and a signing secret you choose (at least 16 characters). Up to 10 per workspace.
  • Reply with a 2xx within 5 seconds. If your endpoint times out or fails, the run is queued and retried in the background for up to 24 hours. Retried requests carry "retry": true and include only the lead’s id, so make your endpoint safe to call more than once.
  • The same action runs at most 5 times per lead per hour. A repeat click shortly after a successful run does not send a second request.
  • The Test button sends quick_action.test with "test": true and no lead.

Request headers

Quick Action request headers
HeaderValue
X-Beastcaller-Eventquick_action.execute (or quick_action.test)
X-Beastcaller-Signaturesha256= followed by the hex HMAC-SHA256 of the raw body, keyed with your signing secret
X-Beastcaller-TimestampSend time in milliseconds since the Unix epoch
X-Beastcaller-Execution-IdID of this run (quick_action.execute only)
User-AgentBeastcaller-Webhook/1.0

Example request

email and fieldOverrides are only included when the caller filled in fields in the action’s form.

POST https://your-endpoint.example.com/send-contract
Content-Type: application/json
User-Agent: Beastcaller-Webhook/1.0
X-Beastcaller-Event: quick_action.execute
X-Beastcaller-Execution-Id: …
X-Beastcaller-Timestamp: 1790676900000
X-Beastcaller-Signature: sha256=<hex HMAC-SHA256 of the body>

{
  "event": "quick_action.execute",
  "action": { "id": "…", "label": "Send contract" },
  "lead": {
    "id": "…",
    "externalId": "crm-1042",
    "name": "Jane Doe",
    "phone": "+4520123456",
    "company": "Acme ApS",
    "email": "jane@acme.example"
  },
  "fieldOverrides": { "email": "jane@acme.example" },
  "executionId": "…",
  "workspaceId": "…",
  "timestamp": 1790676900000
}

Verifying signatures

Compute the HMAC over the exact raw body you received (before parsing JSON) and compare in constant time.

import crypto from "node:crypto";

// rawBody: the exact request body as received (string or Buffer).
// Quick Actions send "sha256=<hex>"; workspace webhooks send the bare hex.
export function isValidSignature(rawBody, signatureHeader, secret) {
  const digest = crypto.createHmac("sha256", secret).update(rawBody).digest("hex");
  const received = String(signatureHeader || "").replace(/^sha256=/, "");
  const a = Buffer.from(digest);
  const b = Buffer.from(received);
  return a.length === b.length && crypto.timingSafeEqual(a, b);
}

Questions about the API?

Email support@beastcaller.app. New to BeastCaller? Start with a 14-day free trial, no credit card required.

Start free trial