Developers · REST API v1

The Beacon API

A clean JSON REST API for your contacts, companies, and deals. Authenticate with a scoped Bearer key, get back stable, predictable shapes — and pipe Beacon into whatever you already run.

https://warmbeacon.com/api/v1Download OpenAPI spec

Getting started

Authentication

Every request is authenticated with an API key, passed as a Bearer token. Keys start with bk_. There are no sessions or cookies — the key alone identifies the caller.

Authorization header
Authorization: Bearer bk_live_xxxxxxxxxxxxxxxx

Create a key in Settings → Developers. Pick the scopes it needs; the full secret is shown once at creation, so copy it then. Your workspace (tenant) is derived entirely from the key — you never pass a workspace id, and a key can only ever read or write data in its own workspace.

Scopes

A key carries one or more scopes. Read scopes gate the GET endpoints; write scopes gate POST and PATCH. A call to an endpoint whose scope the key lacks returns 403.

ScopeGrants
contacts:readList and read contacts
contacts:writeCreate and update contacts
companies:readList and read companies
companies:writeCreate companies
deals:readList and read deals
deals:writeCreate deals

Pagination

List endpoints accept limit and offset query params.

  • limit — 1 to 100, default 50. Values above 100 are clamped to 100; non-positive or non-numeric fall back to 50.
  • offset — ≥ 0, default 0.

Every list response echoes limit, offset, and the unpaginated total so you can page to the end.

Status codes & errors

Errors return a JSON body shaped { "error": "…" }.

200OK — the request succeeded (list, single read, or update).
201Created — a POST created a new resource; the body holds it under data.
400Bad request — invalid JSON, a missing required field, an unknown field, or a value out of bounds. The body has an error message.
401Unauthorized — the Authorization header is missing, malformed, or the key is invalid/revoked.
403Forbidden — a valid key that lacks the scope this endpoint requires.
404Not found — no resource with that id exists in your workspace.

Contacts

People in your workspace. Every contact belongs to a company. Note that status and persona appear in responses but are not writable — status is engine-controlled (new contacts start queued) and persona is set by enrichment. Sending either field is rejected with a 400.

GET/api/v1/contactsscope contacts:read

List contacts in your workspace, most recent paginated with limit / offset.

Example request

curl
curl https://warmbeacon.com/api/v1/contacts?limit=25 \
  -H "Authorization: Bearer bk_live_xxxxxxxxxxxxxxxx"

Example response · 200

{
  "data": [ /* contact objects, as above */ ],
  "limit": 50,
  "offset": 0,
  "total": 128
}
POST/api/v1/contactsscope contacts:write

Create a contact. company_id is required and must resolve to a company in your workspace. At least one of first_name, last_name, or email must be present.

Request body

FieldTypeConstraints
company_idrequiredstringMust reference a company in your workspace (≤ 100 chars). Contacts always belong to a company.
first_nameoptionalstring≤ 200 chars. At least one of first_name, last_name, or email is required.
last_nameoptionalstring≤ 200 chars.
titleoptionalstring≤ 300 chars.
emailoptionalstring≤ 320 chars. Must be a valid email address when present.
phoneoptionalstring≤ 50 chars.
linkedin_urloptionalstring≤ 2000 chars.
owner_user_idoptionalstring≤ 100 chars.

Example request

curl
curl -X POST https://warmbeacon.com/api/v1/contacts \
  -H "Authorization: Bearer bk_live_xxxxxxxxxxxxxxxx" \
  -H "Content-Type: application/json" \
  -d '{
    "company_id": "co_31Ab",
    "first_name": "Dana",
    "last_name": "Reyes",
    "title": "VP of Revenue",
    "email": "dana@acme.com"
  }'

Example response · 201

{
  "data": {
    "id": "ct_8fK2",
    "first_name": "Dana",
    "last_name": "Reyes",
    "title": "VP of Revenue",
    "email": "dana@acme.com",
    "phone": null,
    "linkedin_url": "https://linkedin.com/in/danareyes",
    "status": "queued",
    "persona": null,
    "company_id": "co_31Ab",
    "company_name": "Acme, Inc.",
    "owner_user_id": null,
    "beacon_id": null,
    "created_at": "2026-06-15T14:02:11.000Z",
    "updated_at": "2026-06-15T14:02:11.000Z"
  }
}
GET/api/v1/contacts/{id}scope contacts:read

Fetch a single contact by id. Returns 404 if no contact with that id exists in your workspace.

Example request

curl
curl https://warmbeacon.com/api/v1/contacts/ct_8fK2 \
  -H "Authorization: Bearer bk_live_xxxxxxxxxxxxxxxx"

Example response · 200

{
  "data": {
    "id": "ct_8fK2",
    "first_name": "Dana",
    "last_name": "Reyes",
    "title": "VP of Revenue",
    "email": "dana@acme.com",
    "phone": null,
    "linkedin_url": "https://linkedin.com/in/danareyes",
    "status": "queued",
    "persona": null,
    "company_id": "co_31Ab",
    "company_name": "Acme, Inc.",
    "owner_user_id": null,
    "beacon_id": null,
    "created_at": "2026-06-15T14:02:11.000Z",
    "updated_at": "2026-06-15T14:02:11.000Z"
  }
}
PATCH/api/v1/contacts/{id}scope contacts:write

Update a contact. Only the fields below are writable; send null (or an empty string) to clear a field, omit a field to leave it untouched. Unknown or non-writable fields are rejected with a 400.

Request body

FieldTypeConstraints
first_nameoptionalstring | null≤ 200 chars. Send null (or an empty string) to clear the field; omit to leave it unchanged.
last_nameoptionalstring | null≤ 200 chars. Nullable to clear.
titleoptionalstring | null≤ 300 chars. Nullable to clear.
emailoptionalstring | null≤ 320 chars. Must be valid when a non-null string. Nullable to clear.
phoneoptionalstring | null≤ 50 chars. Nullable to clear.
linkedin_urloptionalstring | null≤ 2000 chars. Nullable to clear.
owner_user_idoptionalstring | null≤ 100 chars. Nullable to clear.

Example request

curl
curl -X PATCH https://warmbeacon.com/api/v1/contacts/ct_8fK2 \
  -H "Authorization: Bearer bk_live_xxxxxxxxxxxxxxxx" \
  -H "Content-Type: application/json" \
  -d '{ "title": "Chief Revenue Officer", "phone": null }'

Example response · 200

{
  "data": {
    "id": "ct_8fK2",
    "first_name": "Dana",
    "last_name": "Reyes",
    "title": "VP of Revenue",
    "email": "dana@acme.com",
    "phone": null,
    "linkedin_url": "https://linkedin.com/in/danareyes",
    "status": "queued",
    "persona": null,
    "company_id": "co_31Ab",
    "company_name": "Acme, Inc.",
    "owner_user_id": null,
    "beacon_id": null,
    "created_at": "2026-06-15T14:02:11.000Z",
    "updated_at": "2026-06-15T14:02:11.000Z"
  }
}

Companies

Accounts in your workspace. is_customer is a strict boolean — true or false only — and is returned as a boolean too. employee_count is a free-text band (e.g. "1,001-5,000"), not a number.

GET/api/v1/companiesscope companies:read

List companies in your workspace, paginated with limit / offset.

Example request

curl
curl https://warmbeacon.com/api/v1/companies?limit=50&offset=0 \
  -H "Authorization: Bearer bk_live_xxxxxxxxxxxxxxxx"

Example response · 200

{
  "data": [ /* company objects */ ],
  "limit": 50,
  "offset": 0,
  "total": 42
}
POST/api/v1/companiesscope companies:write

Create a company. name is required; everything else is optional.

Request body

FieldTypeConstraints
namerequiredstring≤ 200 chars.
domainoptionalstring≤ 255 chars.
industryoptionalstring≤ 120 chars.
employee_countoptionalstringFree-text label, e.g. "1,001-5,000" (≤ 60 chars) — not a number.
locationoptionalstring≤ 200 chars.
is_customeroptionalbooleanMust be a real boolean (true/false) when present.

Example request

curl
curl -X POST https://warmbeacon.com/api/v1/companies \
  -H "Authorization: Bearer bk_live_xxxxxxxxxxxxxxxx" \
  -H "Content-Type: application/json" \
  -d '{
    "name": "Acme, Inc.",
    "domain": "acme.com",
    "industry": "Logistics",
    "employee_count": "1,001-5,000",
    "location": "Austin, TX",
    "is_customer": false
  }'

Example response · 201

{
  "data": {
    "id": "co_31Ab",
    "name": "Acme, Inc.",
    "domain": "acme.com",
    "industry": "Logistics",
    "employee_count": "1,001-5,000",
    "location": "Austin, TX",
    "is_customer": false,
    "created_at": "2026-06-15T13:58:40.000Z"
  }
}
GET/api/v1/companies/{id}scope companies:read

Fetch a single company by id. Returns 404 if no company with that id exists in your workspace.

Example request

curl
curl https://warmbeacon.com/api/v1/companies/co_31Ab \
  -H "Authorization: Bearer bk_live_xxxxxxxxxxxxxxxx"

Example response · 200

{
  "data": {
    "id": "co_31Ab",
    "name": "Acme, Inc.",
    "domain": "acme.com",
    "industry": "Logistics",
    "employee_count": "1,001-5,000",
    "location": "Austin, TX",
    "is_customer": false,
    "created_at": "2026-06-15T13:58:40.000Z"
  }
}

Deals

Pipeline opportunities. stage must be one of the six pipeline stages; amount is a whole number ≥ 0. Optional contact_id / company_id links must resolve within your workspace.

GET/api/v1/dealsscope deals:read

List deals in your workspace, paginated with limit / offset.

Example request

curl
curl https://warmbeacon.com/api/v1/deals?limit=25 \
  -H "Authorization: Bearer bk_live_xxxxxxxxxxxxxxxx"

Example response · 200

{
  "data": [ /* deal objects */ ],
  "limit": 25,
  "offset": 0,
  "total": 17
}
POST/api/v1/dealsscope deals:write

Create a deal. title is required. stage defaults to lead; a won/lost stage stamps closed_at server-side.

Request body

FieldTypeConstraints
titlerequiredstring≤ 300 chars.
stageoptionalstringOne of: lead, qualified, proposal, negotiation, won, lost. Defaults to lead.
amountoptionalnumberFinite number ≥ 0, in whole units of the currency.
currencyoptionalstring≤ 10 chars, e.g. "USD".
contact_idoptionalstring≤ 100 chars. Must reference a contact in your workspace when present.
company_idoptionalstring≤ 100 chars. Must reference a company in your workspace when present.
owner_user_idoptionalstring≤ 100 chars.
expected_closeoptionalstring≤ 100 chars (e.g. an ISO date).
notesoptionalstring≤ 300 chars.

Example request

curl
curl -X POST https://warmbeacon.com/api/v1/deals \
  -H "Authorization: Bearer bk_live_xxxxxxxxxxxxxxxx" \
  -H "Content-Type: application/json" \
  -d '{
    "title": "Acme — Platform expansion",
    "stage": "qualified",
    "amount": 48000,
    "currency": "USD",
    "company_id": "co_31Ab",
    "contact_id": "ct_8fK2",
    "expected_close": "2026-09-30"
  }'

Example response · 201

{
  "data": {
    "id": "dl_77Qz",
    "title": "Acme — Platform expansion",
    "stage": "qualified",
    "amount": 48000,
    "currency": "USD",
    "contact_id": "ct_8fK2",
    "company_id": "co_31Ab",
    "owner_user_id": null,
    "expected_close": "2026-09-30",
    "created_at": "2026-06-15T15:10:02.000Z",
    "updated_at": "2026-06-15T15:10:02.000Z",
    "closed_at": null
  }
}
GET/api/v1/deals/{id}scope deals:read

Fetch a single deal by id. Returns 404 if no deal with that id exists in your workspace.

Example request

curl
curl https://warmbeacon.com/api/v1/deals/dl_77Qz \
  -H "Authorization: Bearer bk_live_xxxxxxxxxxxxxxxx"

Example response · 200

{
  "data": {
    "id": "dl_77Qz",
    "title": "Acme — Platform expansion",
    "stage": "qualified",
    "amount": 48000,
    "currency": "USD",
    "contact_id": "ct_8fK2",
    "company_id": "co_31Ab",
    "owner_user_id": null,
    "expected_close": "2026-09-30",
    "created_at": "2026-06-15T15:10:02.000Z",
    "updated_at": "2026-06-15T15:10:02.000Z",
    "closed_at": null
  }
}

Mint a key and start building.

Create a scoped API key in Settings → Developers, then point your integration at https://warmbeacon.com/api/v1.

Read-only by default — grant write scopes only when you need them.