Organization API, version 1
Read and add Business Retention & Expansion records from your own systems, and receive signed events when things change. Available to every BRE and Complete organization.
API version 2026-09-17 (sent in every response as BF-API-Version). Breaking changes will be published as /api/v2; version 1 will not change behavior silently.
Authentication
An organization owner or admin creates a key in the BRE console under Setup › Integrations › Organization API, choosing a name, the permissions (scopes), an expiry and a per-minute limit. The key is shown once; BRE Pulse stores only a hash of it. Revoked or expired keys stop working immediately.
curl https://brepulse.com/api/v1/businesses \ -H "Authorization: Bearer bfk_…"
A key can only reach its own organization. It sees organization-internal and public records only: restricted and confidential cases, projects, interactions and businesses — and case fields marked private — are left out entirely.
Resources
| Request | Scope | Notes |
|---|---|---|
GET /api/v1 | any | The key's organization, name and scopes. |
GET /api/v1/businesses | businesses:read | updated_since (YYYY-MM-DD HH:MM:SS) filter. |
GET /api/v1/businesses/{id} | businesses:read | |
POST /api/v1/businesses | businesses:write | name required; also dba, address, city, state, zip, phone, website, industry, naics, employees_est. An existing match is returned with matched_existing: true (200) instead of a duplicate (201). |
PATCH /api/v1/businesses/{id} | businesses:write | website, phone, industry, naics, dba, priority_tier (strategic, major, standard), employees_est, tags. |
GET /api/v1/businesses/{id}/contacts | contacts:read | Includes each contact's consent to be contacted. |
GET /api/v1/interactions | interactions:read | business_id filter. |
POST /api/v1/interactions | interactions:write | business_id, kind (call, email, meeting, note, visit, referral), summary, occurred_at. |
GET /api/v1/cases, /cases/{id} | cases:read | status filter. Jobs and investment are separate stages (proposed, committed, completed); amounts are in cents. |
GET /api/v1/projects | projects:read | |
GET /api/v1/actions | actions:read | |
POST /api/v1/actions | actions:write | title required; business_id, detail, owner_email, due_date (YYYY-MM-DD), priority (high, normal, low). |
GET /api/v1/commitments | commitments:read | Each carries overdue. |
GET /api/v1/scores | scores:read | Program figures for the last 12 months, impact by stage, data health. |
GET /api/v1/reports, /reports/{id} | reports:read | Reports readable at organization-internal level (board and partner reports; internal reports that drew on confidential records are not returned). |
GET /api/v1/locations | locations:read | Map positions (latitude, longitude, precision, source) for placed businesses. |
Pagination
Lists return {"data": [...], "next_cursor": "…"}. Pass cursor to get the next page and limit (1–200, default 50). next_cursor is null on the last page.
Writes and idempotency
Every POST and PATCH must include an Idempotency-Key header (8–100 letters, digits, . : _ -). Repeating a request with the same key within 24 hours returns the original answer with Idempotent-Replayed: true and changes nothing. Reusing a key for a different request is refused with 422 idempotency_key_reused.
curl -X POST https://brepulse.com/api/v1/interactions \
-H "Authorization: Bearer bfk_…" \
-H "Idempotency-Key: crm-sync-2026-09-17-4411" \
-H "Content-Type: application/json" \
-d '{"business_id":"…","kind":"call","summary":"Called about the expansion timeline"}'
Errors and limits
Errors look like {"error": {"code": "insufficient_scope", "message": "…"}}. Codes include unauthenticated, invalid_key, expired_key, not_licensed, insufficient_scope, not_found, invalid, invalid_json, idempotency_key_required, idempotency_key_reused and rate_limited. Each key has a per-minute limit (60 by default); over it, the API answers 429 with Retry-After.
Webhooks
Admins add endpoints under Setup › Integrations › Webhooks and choose events: business.created, business.updated, flag.confirmed, case.created, case.updated, project.updated, commitment.overdue, checkin.received, report.generated. Events about restricted or confidential records are never sent.
Each delivery is a JSON POST:
{"id":"evt_…","type":"case.created","created_at":"2026-09-17T14:03:00Z",
"api_version":"2026-09-17","organization_id":"…","data":{"case":{…}}}
Headers: BF-Delivery-Id (the same on every retry — use it to ignore duplicates), BF-Event-Id, BF-Event-Type, BF-Timestamp and BF-Signature: t=<unix seconds>,v1=<hex>, where the hex is HMAC-SHA256 of <t>.<raw body> with your endpoint's signing secret (whsec_…, shown once). Reject requests whose timestamp is more than five minutes old.
// Node.js
const [t, v1] = header.split(',').map(p => p.split('=')[1]);
const expected = crypto.createHmac('sha256', secret).update(`${t}.${rawBody}`).digest('hex');
const ok = crypto.timingSafeEqual(Buffer.from(expected), Buffer.from(v1)) && Math.abs(Date.now() / 1000 - Number(t)) < 300;
Answer with any 2xx within 10 seconds. Anything else is retried after 1, 5, 30, 120, 360, 720 and 1440 minutes (8 attempts in all); the delivery log in the console shows every attempt and can send an event again. An endpoint is turned off automatically after 15 failed attempts in a row spanning at least 24 hours; turning it back on re-sends failed events from the last 7 days.
