Attribution Data API

A read-only HTTP API over the curated attribution warehouse. Authenticate with a scoped bearer key and pull JSON — no database connection string required. Every dataset is a curated, allowlisted view; customer/staff PII lives only behind dedicated *_pii scopes.

Curated datasets

Cleanly-shaped read-only views — leads, events, activities, attribution, LTV, staff and reference. Filter by any column, paginate, done.

Scoped & PII-gated

Each key is granted only the datasets it needs. Team-performance data is non-PII; names / emails / phones require a separate *_pii scope.

Using the API

All routes are read-only GETs returning JSON over HTTPS. Base URL /v1.

Authentication

Send a bearer key on every data route (this catalog and /v1/health need none). Keys are scoped per dataset — the scope name equals the dataset name (e.g. attr_activity, attr_leads).

Authorization: Bearer <your-api-key>
SituationStatusMeaning
valid key, in scope200Data returned
missing / bad key401No or invalid Authorization header
wrong scope403Key not scoped for that dataset
unknown id / dataset404No matching row, or dataset not in the allowlist
bad filter column400Filter column not on that dataset

Scopes & PII

Scoping is the only access mechanism, and PII-gating is just what it achieves for sensitive fields:

  • Scoped — every dataset is its own scope. A key reads only the datasets it was granted; anything else is a 403.
  • PII-gated — customer/staff PII (names, emails, phones, raw notes) lives only in the separate *_pii datasets. A key without a *_pii scope never sees PII — and the non-PII view doesn't even contain those columns.

So an app that doesn't need PII is granted only the plain scopes and cannot retrieve it. Example — a sales-performance dashboard:

AppGranted scopesCan read
sales dashboard (no PII)attr_activity attr_leads attr_events attr_staffbookings, advisors, funnel, revenue — no names/emails/phones
app that needs contactsabove + crm_leads_piiadds contact/identity fields, for that key only

Pagination & filtering

ParamTypeDescription
limitintegerRows to return. Default 100, max 1000
offsetintegerRows to skip. Default 0
{column}={value}anyAny real column becomes an equality filter (else 400)

List responses are enveloped: { items, limit, offset, total }. Single-record routes (/v1/{dataset}/{id}) return the row object directly.

Health & docs

curl -s /v1/health     → {"status":"ok"}
curl -s /v1/health/db  → {"status":"ok","db":"ok"}

Interactive OpenAPI docs: /docs · /redoc · /openapi.json.