Developers · BRE Pulse (Business Retention & Expansion)

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

RequestScopeNotes
GET /api/v1anyThe key's organization, name and scopes.
GET /api/v1/businessesbusinesses:readupdated_since (YYYY-MM-DD HH:MM:SS) filter.
GET /api/v1/businesses/{id}businesses:read
POST /api/v1/businessesbusinesses:writename 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:writewebsite, phone, industry, naics, dba, priority_tier (strategic, major, standard), employees_est, tags.
GET /api/v1/businesses/{id}/contactscontacts:readIncludes each contact's consent to be contacted.
GET /api/v1/interactionsinteractions:readbusiness_id filter.
POST /api/v1/interactionsinteractions:writebusiness_id, kind (call, email, meeting, note, visit, referral), summary, occurred_at.
GET /api/v1/cases, /cases/{id}cases:readstatus filter. Jobs and investment are separate stages (proposed, committed, completed); amounts are in cents.
GET /api/v1/projectsprojects:read
GET /api/v1/actionsactions:read
POST /api/v1/actionsactions:writetitle required; business_id, detail, owner_email, due_date (YYYY-MM-DD), priority (high, normal, low).
GET /api/v1/commitmentscommitments:readEach carries overdue.
GET /api/v1/scoresscores:readProgram figures for the last 12 months, impact by stage, data health.
GET /api/v1/reports, /reports/{id}reports:readReports readable at organization-internal level (board and partner reports; internal reports that drew on confidential records are not returned).
GET /api/v1/locationslocations:readMap 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.