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.
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: Bearer bk_live_xxxxxxxxxxxxxxxxCreate 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.
| Scope | Grants |
|---|---|
| contacts:read | List and read contacts |
| contacts:write | Create and update contacts |
| companies:read | List and read companies |
| companies:write | Create companies |
| deals:read | List and read deals |
| deals:write | Create 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.
/api/v1/contactsscope contacts:readList contacts in your workspace, most recent paginated with limit / offset.
Example request
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
}/api/v1/contactsscope contacts:writeCreate 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
| Field | Type | Constraints |
|---|---|---|
| company_idrequired | string | Must reference a company in your workspace (≤ 100 chars). Contacts always belong to a company. |
| first_nameoptional | string | ≤ 200 chars. At least one of first_name, last_name, or email is required. |
| last_nameoptional | string | ≤ 200 chars. |
| titleoptional | string | ≤ 300 chars. |
| emailoptional | string | ≤ 320 chars. Must be a valid email address when present. |
| phoneoptional | string | ≤ 50 chars. |
| linkedin_urloptional | string | ≤ 2000 chars. |
| owner_user_idoptional | string | ≤ 100 chars. |
Example request
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"
}
}/api/v1/contacts/{id}scope contacts:readFetch a single contact by id. Returns 404 if no contact with that id exists in your workspace.
Example request
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"
}
}/api/v1/contacts/{id}scope contacts:writeUpdate 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
| Field | Type | Constraints |
|---|---|---|
| first_nameoptional | string | null | ≤ 200 chars. Send null (or an empty string) to clear the field; omit to leave it unchanged. |
| last_nameoptional | string | null | ≤ 200 chars. Nullable to clear. |
| titleoptional | string | null | ≤ 300 chars. Nullable to clear. |
| emailoptional | string | null | ≤ 320 chars. Must be valid when a non-null string. Nullable to clear. |
| phoneoptional | string | null | ≤ 50 chars. Nullable to clear. |
| linkedin_urloptional | string | null | ≤ 2000 chars. Nullable to clear. |
| owner_user_idoptional | string | null | ≤ 100 chars. Nullable to clear. |
Example request
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.
/api/v1/companiesscope companies:readList companies in your workspace, paginated with limit / offset.
Example request
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
}/api/v1/companiesscope companies:writeCreate a company. name is required; everything else is optional.
Request body
| Field | Type | Constraints |
|---|---|---|
| namerequired | string | ≤ 200 chars. |
| domainoptional | string | ≤ 255 chars. |
| industryoptional | string | ≤ 120 chars. |
| employee_countoptional | string | Free-text label, e.g. "1,001-5,000" (≤ 60 chars) — not a number. |
| locationoptional | string | ≤ 200 chars. |
| is_customeroptional | boolean | Must be a real boolean (true/false) when present. |
Example request
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"
}
}/api/v1/companies/{id}scope companies:readFetch a single company by id. Returns 404 if no company with that id exists in your workspace.
Example request
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.
/api/v1/dealsscope deals:readList deals in your workspace, paginated with limit / offset.
Example request
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
}/api/v1/dealsscope deals:writeCreate a deal. title is required. stage defaults to lead; a won/lost stage stamps closed_at server-side.
Request body
| Field | Type | Constraints |
|---|---|---|
| titlerequired | string | ≤ 300 chars. |
| stageoptional | string | One of: lead, qualified, proposal, negotiation, won, lost. Defaults to lead. |
| amountoptional | number | Finite number ≥ 0, in whole units of the currency. |
| currencyoptional | string | ≤ 10 chars, e.g. "USD". |
| contact_idoptional | string | ≤ 100 chars. Must reference a contact in your workspace when present. |
| company_idoptional | string | ≤ 100 chars. Must reference a company in your workspace when present. |
| owner_user_idoptional | string | ≤ 100 chars. |
| expected_closeoptional | string | ≤ 100 chars (e.g. an ISO date). |
| notesoptional | string | ≤ 300 chars. |
Example request
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
}
}/api/v1/deals/{id}scope deals:readFetch a single deal by id. Returns 404 if no deal with that id exists in your workspace.
Example request
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.