Skip to content
Developers

Build on the same API the app uses.

A JSON REST API, lead-intake webhooks for your ad platforms and website, and signed webhooks for everything that happens in a workspace.

Introduction

The base URL is https://crm.anibrosdesign.com/api/v1. Requests and responses are JSON. Successful responses wrap the result as { "data": … }; lists add a page object for the next page. Times are ISO 8601 in UTC. Everything is over HTTPS.

Base URLhttps://crm.anibrosdesign.com/api/v1

The API is the one the web app itself uses, so anything you can do in the CRM you can do from code, within the permissions of the key you use.

Authentication

Create an API key in the CRM under Settings → API keys. Send it as a bearer token on every request:

curl https://crm.anibrosdesign.com/api/v1/leads?limit=5 \
  -H "Authorization: Bearer crm_live_xxxxxxxx"
  • A key is shown once, when you create it. Store it like a password. Only a hash is kept on our side.
  • A key belongs to one workspace (one client) and can do only what its scopes allow. Two ready-made templates cover the usual cases: send leads in (create leads and contacts) and read data out (read leads, contacts, activities and reports).
  • Keys can expire, and can be revoked at any time. Failed attempts are rate-limited per address.
  • How many keys you can have depends on your plan.

Send your first lead

Create a lead with a contact. If the phone number or e-mail matches a person you already have, the lead is linked to them rather than creating a duplicate.

curl https://crm.anibrosdesign.com/api/v1/leads \
  -H "Authorization: Bearer crm_live_xxxxxxxx" \
  -H "Content-Type: application/json" \
  -H "Idempotency-Key: 6f2c1d9e-form-1042" \
  -d '{
    "contact": { "firstName": "Priya", "lastName": "Sharma", "phone": "+919800000000", "email": "priya@example.com" },
    "source": "website",
    "campaign": "diwali-offer",
    "estimatedValue": 8500000
  }'

The response is the created lead, with its id, stage, owner (assigned by your routing rules) and score.

Leads

EndpointWhat it does
GET/leadsList leads. Filters: q, stageId, ownerId, source, state (open or closed), status, createdFrom, createdTo, tag, sort, order.
POST/leadsCreate a lead (see above). source is one of website, referral, portal, direct, meta_ads, google_ads, walk_in, other.
GET/leads/{id}One lead with its contact, stage history and details.
PATCH/leads/{id}Change fields. Send If-Match: "<version>" with the version you read; a stale edit gets 412 instead of overwriting someone else's change.
POST/leads/{id}/stageMove to a stage: { "stageId": "…" }. Lost or won stages may need reason or a deal value, depending on your pipeline rules.
POST/leads/{id}/assignGive the lead to a person: { "ownerId": "…" }, or null to unassign.
POST/leads/{id}/activitiesLog a call, WhatsApp, SMS, e-mail, meeting or note: { "type": "call", "description": "…", "direction": "outbound", "durationMinutes": 6, "nextFollowupAt": "…" }.
GET/leads/{id}/activitiesThe lead's activity history.

Other resources

ResourceEndpoints
ContactsGET/POST /contacts, GET/PATCH/DELETE /contacts/{id}, GET /contacts/lookup
TasksGET/POST /tasks, GET/PATCH /tasks/{id}, POST /tasks/{id}/complete
PipelinesGET /pipelines, GET /pipelines/{id}, GET /pipelines/{id}/board
PropertiesGET/POST /properties, GET/PATCH /properties/{id}
Lead sourcesGET/POST /lead-sources, POST /lead-sources/{id}/test
WebhooksGET/POST /webhooks, POST /webhooks/{id}/test, GET /webhooks/deliveries

What a key can reach is limited by its scopes, for example leads:read, leads:create, contacts:create. GET /api-keys/scopes lists them.

This table is a summary. The API reference lists every endpoint with its parameters and request body, and the OpenAPI file can be imported into Postman or used to generate a client.

Pagination

List endpoints use cursors. Pass limit (1 to 200, default 50) and, for the next page, the cursor from the previous response's page.next. When page.next is empty you have everything.

curl "https://crm.anibrosdesign.com/api/v1/leads?limit=100&state=open" -H "Authorization: Bearer crm_live_xxxxxxxx"
# { "data": [ … ], "page": { "next": "eyJ…" } }

curl "https://crm.anibrosdesign.com/api/v1/leads?limit=100&state=open&cursor=eyJ…" -H "Authorization: Bearer crm_live_xxxxxxxx"

Errors

Errors follow RFC 9457 (application/problem+json). Every error carries a stable code and a requestId; quote the request id when you contact us.

{
  "type": "https://crm.anibrosdesign.com/developers#errors",
  "title": "Validation failed",
  "status": 400,
  "detail": "One or more fields are invalid",
  "code": "VALIDATION_FAILED",
  "requestId": "01a15d23-…",
  "errors": [ { "path": "body.contact.phone", "message": "Enter a valid phone number" } ]
}
StatusMeaning
400The request is invalid. errors says which fields.
401Missing, wrong, expired or revoked key.
402The plan blocks this: a limit is reached (PLAN_LIMIT) or the subscription has ended and the workspace is read-only (SUBSCRIPTION_REQUIRED). Reading still works.
403The key's scopes do not allow this.
404Not found, or not in this key's workspace.
409 / 412Conflict, or the record changed since you read it (If-Match).
429Too many requests. Wait for the seconds in Retry-After.
5xxSomething went wrong on our side. Safe to retry with the same Idempotency-Key.

Limits and idempotency

Rate limits. Requests are limited per key and per address. When you exceed a limit you get 429 and a Retry-After header; RateLimit-Limit and RateLimit-Remaining are sent along. Back off and retry.

Idempotency. Send an Idempotency-Key header (any unique string up to 128 characters) on POST requests that create things (leads, contacts, tasks, messages, webhooks). Repeating a request with the same key and the same body returns the original response instead of creating a second record, so retrying after a timeout is always safe. The same key with a different body is rejected.

Plan limits. Your plan caps client workspaces, team members, leads and API keys. Hitting a cap returns 402 with the limit, what you use and the plan.

Lead intake

Each lead source you create in Settings → Lead sources gets its own address, https://crm.anibrosdesign.com/hooks/{source-id}. Use it for ad platforms, your website and your own systems. It is separate from the REST API and does not use API keys or cookies.

Source typeHow it is authenticated
Meta lead adsMeta's own X-Hub-Signature-256, checked with your app secret. Each Facebook Page has its own access token (see below).
WhatsAppThe same Meta signature. Every message a customer sends to your WhatsApp Business number lands on their lead.
Google adsThe key you set on the lead form asset (Google Ads → Assets → Lead form → Webhook integration).
WebsiteNo secret (it runs in a browser); only the origins you list are accepted. Use the capture script below.
Generic (your own system)Authorization: Bearer <key> or X-Anibros-Key, or an HMAC signature: X-Anibros-Signature: t=<unix>,v1=<hex> where v1 is HMAC-SHA256 of "<t>.<raw body>" with the source secret.
# a lead from your own system, authenticated with the source's key
curl https://crm.anibrosdesign.com/hooks/YOUR-SOURCE-ID \
  -H "Authorization: Bearer YOUR-SOURCE-KEY" \
  -H "Content-Type: application/json" \
  -d '{ "name": "Priya Sharma", "phone": "+919800000000", "email": "priya@example.com", "utm_campaign": "diwali-offer" }'

Field names are matched generously (name, full_name, phone, mobile, email and common variations). The source and campaign are recorded, a repeat enquiry is linked to the existing person, and every delivery is kept so a failed one can be retried from the CRM. The same delivery sent twice creates one lead.

Meta and Google in detail

Meta lead ads: one address, one token per Facebook Page

A Meta app has one webhook address for every Facebook Page it serves, but a lead can only be read with the access token of the Page it came from. So a Meta lead source holds a list of Pages, each with its own token. In the CRM: Settings → Lead sources → your Meta source → Pages. From code:

# add a Page (the token is checked with Meta, kept encrypted and never returned)
curl -X POST https://crm.anibrosdesign.com/api/v1/lead-sources/SOURCE-ID/pages \
  -H "Authorization: Bearer crm_live_xxxxxxxx" -H "Content-Type: application/json" \
  -d '{ "token": "EAAB…page-access-token", "subscribe": true }'

# how each Page is doing: token state, notifications, last lead, leads waiting
curl https://crm.anibrosdesign.com/api/v1/lead-sources/SOURCE-ID/pages -H "Authorization: Bearer crm_live_xxxxxxxx"

# a new token for a Page whose token stopped (waiting leads are read at once)
curl -X PATCH https://crm.anibrosdesign.com/api/v1/lead-sources/SOURCE-ID/pages/188174958051218 \
  -H "Authorization: Bearer crm_live_xxxxxxxx" -H "Content-Type: application/json" -d '{ "token": "EAAB…" }'
  • Set up once: in Meta for Developers open your app → Webhooks → Page, paste the source's address as Callback URL and its verify token, subscribe the leadgen field. The app must be Published: Meta delivers nothing to an app in development mode. Leads need the permissions leads_retrieval, pages_show_list, pages_read_engagement, pages_manage_metadata and ads_management.
  • Per Page: add the Page with its token. We ask Meta which Page it is for, refuse a personal token, check the token works, and (unless you say otherwise) switch on lead notifications for the Page. A Page belongs to one lead source on the whole platform.
  • Reading a lead: we use that Page's own token, and fill in the campaign, ad set and ad names from the ad when Meta's lead record does not carry them.
  • Nothing is lost: a lead of a Page without a working token waits in the delivery log, saying which Page, and is read automatically the moment you save a token. Every few hours each token is checked with Meta; the owners are notified when one stops, or ends within a week (give the source your Meta App ID under Settings so Meta can tell us when it ends).
  • Tip: a Page token made from a long-lived user token does not expire. Make it in the Graph API Explorer (me/accounts) after extending the login in the Access Token Debugger.

Google ads: a key per lead form

Google keeps the webhook address and key inside each lead form asset, so one source can serve any number of forms and campaigns. Every lead carries its campaign number, form number and a click id; Google does not send the campaign name, so name the campaign once in Campaigns. Google sends the same test person every time you press “Send test data”, so repeated tests join the first lead instead of creating new ones.

Agencies: whose lead is it

The ad platform says which campaign a lead came from, never whose it is. A source in an agency's own workspace can be switched to sort leads between clients. Each lead then goes to a client by the first rule that proves it: a person's choice for that lead, the campaign's own rule, a test lead (stays with the agency), a word you gave the client that appears in the campaign, ad set, ad or form name, then a default for the form, Page, ad account or source. If nothing proves it, the lead waits in the Waiting inbox, masked, with the reason and a suggestion, and is never guessed. Deciding once for a campaign files every waiting lead of it and every future one.

EndpointWhat it does
GET/ad-routing/overviewWhat waits, which campaigns are owned, defaults.
GET/ad-routing/unroutedWaiting leads, grouped by campaign.
POST/ad-routing/assignDecide which client a campaign (or form, Page, ad account) belongs to.
GET/campaignsEvery campaign leads came from, with counts and owner.
GET/leads?channel=metaLeads by where they came from. GET /leads/channels gives the counts per channel for the same filters.

Webhooks

Add an endpoint under Settings → Send data out (it must be a public HTTPS address). Choose the events you want:

lead.created · lead.assigned · lead.stage_changed · lead.updated · lead.deleted · activity.created · task.created · task.completed · contact.created

POST /your/endpoint HTTP/1.1
Content-Type: application/json
X-Anibros-Event: lead.created
X-Anibros-Delivery: 01a15d23-…
X-Anibros-Signature: t=1766300000,v1=9f2b…

{
  "id": "01a15d23-…",
  "event": "lead.created",
  "occurredAt": "2026-10-21T10:15:00.000Z",
  "workspace": { "id": "01a15c…" },
  "change": { … },
  "data": { … }
}

Every delivery is signed with your endpoint's secret (shown once, rotatable). Verify the signature before you trust the payload, using the raw body:

import crypto from 'node:crypto';

// raw = the request body exactly as received (a Buffer or string), never re-serialised JSON
export function verify(raw, header, secret, toleranceSeconds = 300) {
  const parts = Object.fromEntries(header.split(',').map((p) => p.split('=')));
  const age = Math.abs(Date.now() / 1000 - Number(parts.t));
  if (!parts.t || !parts.v1 || age > toleranceSeconds) return false;
  const expected = crypto.createHmac('sha256', secret).update(parts.t + '.' + raw).digest('hex');
  return expected.length === parts.v1.length && crypto.timingSafeEqual(Buffer.from(expected), Buffer.from(parts.v1));
}
  • Reply with any 2xx within 8 seconds. Anything else is retried automatically with growing delays, up to 8 attempts. Deliveries and their results are listed in the CRM, and a failed one can be retried by hand.
  • An event can be delivered more than once. Use X-Anibros-Delivery or the event id to ignore repeats.
  • Endpoints that point at private or internal addresses are refused.
  • Outgoing webhooks are part of the Growth and Scale plans.

API reference

Every endpoint, with its parameters, request body, the permission it needs and an example call, generated from the running API so it is always current: open the reference. The machine-readable version is openapi.json (OpenAPI 3.1).

What changed

  • October 2026. Downloads can be aimed: GET /leads/export and GET /agency/leads/export take the campaign, ad set, ad, form and channel filters and a period as days or exact timestamps, and GET /leads/export/count and GET /agency/leads/export/count say how many leads a download would hold before it is built. POST /messaging/email/preview returns the lead e-mail exactly as it would be sent. The agency routes accept the agency’s own workspace as clientId.
  • October 2026. A Meta lead source holds one access token per Facebook Page (/lead-sources/{id}/pages), checked with Meta and renewed without losing a lead. Campaign, ad set and ad names are filled in from the ad. The API reference and the OpenAPI file are generated from the live API.
  • October 2026. Leads know where they came from: channel filter and GET /leads/channels. Agencies decide whose campaign is whose (/ad-routing, /campaigns); leads that cannot be placed wait instead of being guessed. Tagged links (UTM and click ids), Click-to-WhatsApp ads and Instagram placements are recognised.
  • October 2026. Personal details are removed from finished lead deliveries after 45 days; the lead itself is untouched.

Something missing or unclear? Tell us. The API grows with what people build on it.

Chat on WhatsApp