Reference

Data API reference

The public /v1 data API: pull records, change streams, and intelligence events with scoped keys, cursor pagination, a stable envelope, and transparent data-credit metering.

Base URL: https://api.ballastos.com (production) or http://localhost:8000 (development). The current schema version is 2026-07-01and is returned in every response's meta.schema_version.

Authentication

Authenticate with a scoped data-API key in the Authorization header (or X-API-Key):

First request
curl "https://api.ballastos.com/v1/entities?entity_type=project&confidence_min=0.8&limit=25" \
  -H "Authorization: Bearer blst_live_..."
  • Keys are created in the console under API & developers → Create key. The secret is shown once, stored as a hash, and revocable instantly.
  • blst_live_… keys read your live data; blst_test_… keys are sandboxed to synthetic demo records (is_demo: true) so you can integrate safely.
  • Keys carry a scope list; a request without the required scope returns 403 naming the missing scope. Optional per-key controls: expiry, IP allowlist (CIDR), monthly request and data-credit allowances.
  • Console/SDK aos_… keys are not valid on /v1, and sessions/cookies are never accepted here.
FieldTypeDescription
feeds:readscopeList and read feeds and their specifications
entities:readscopeRead records and the /v1/changes stream
events:readscopeRead intelligence events
sources:readscopeRead the source network and health
usage:readscopeRead month-to-date usage and allowances
exports:createscopeCreate JSON/CSV exports
webhooks:readscopeList webhook endpoints
webhooks:writescopeCreate and delete webhook endpoints

The response envelope

Every JSON response uses the same shape:

Envelope
{
  "data": [ ... ],
  "pagination": { "next_cursor": "MjAyNi0wNy0yMFQxODowMjoxMXw..." },
  "meta": {
    "request_id": "req_9c2f1ab4d0e3",
    "schema_version": "2026-07-01",
    "credits_consumed": 25
  }
}
  • pagination.next_cursor — pass it back as ?cursor= to fetch the next page. null means the end. limit accepts 1–100 (default 25); an unparseable cursor returns 400.
  • meta.credits_consumed — exactly what this call cost in data credits, so metering is never a surprise.
  • meta.request_id — quote it when contacting support about a specific request.

Data credits

Reads are metered in data credits against your plan's monthly allowance (defaults shown; enterprise pricing can vary):

FieldTypeDescription
basic record1 creditEach entity returned by GET /v1/entities
enriched record5 creditsGET /v1/entities/{id}
full history20 creditsGET /v1/entities/{id}?include=history
event3 creditsEach event returned (unverified/machine verified)
verified event10 creditsEvents that are multi-source verified or human reviewed
change row1 creditEach row from GET /v1/changes
export row1 creditEach row in a POST /v1/exports result

Note

Allowance exhaustion fails before data is returned: 429 when the monthly request allowance is spent, 402 when the data-credit allowance is spent (organization plan or per-key cap). Every call also meters one api_request.

Endpoints

GET/v1/feeds

List your organization's feeds (non-archived) with status, freshness, schema, and geography. Scope: feeds:read.

GET/v1/feeds/{feed_id}

One feed, including its full monitoring specification. Scope: feeds:read.

GET/v1/entities

Canonical records, newest first, with cursor pagination. Scope: entities:read. 1 credit per record.

FieldTypeDescription
feed_idstringRestrict to one feed
entity_typestringproject, permit, company, property, contract, product, listing, …
geographystringMatches city, region (state), or country
confidence_minnumberOnly records at or above this confidence (0–1)
updated_sinceISO 8601Records updated at or after this instant
created_sinceISO 8601Records first observed at or after this instant
limitintPage size, 1–100
cursorstringOpaque cursor from pagination.next_cursor
Entity record
{
  "id": "a1b2c3d4e5f6",
  "feed_id": "fd_123",
  "entity_type": "project",
  "canonical_name": "Lakeview Medical Office Expansion",
  "aliases": [],
  "attributes": {
    "project_name": "Lakeview Medical Office Expansion",
    "property_address": "4200 Lakeview Dr, Orlando, FL 32803",
    "developer": "Meridian Health Partners",
    "permit_number": "BLD-2026-041877",
    "project_value": 2400000,
    "project_stage": "permit_approved",
    "general_contractor": null,
    "contractor_status": "not_yet_identified",
    "opportunity_score": 87
  },
  "external_ids": { "permit_number": "BLD-2026-041877" },
  "geography": { "city": "Orlando", "region": "FL", "country": "US", "postal_code": "32803" },
  "status": "active",
  "verification_status": "multi_source_verified",
  "confidence": 0.95,
  "version": 3,
  "source_evidence": [
    {
      "source_id": "src_88a1",
      "source_name": "Orange County Building Permits (Demo)",
      "url": "https://demo.ballastos.com/sources/orange-county-permits/bld-2026-041877",
      "retrieved_at": "2026-07-20T18:02:11Z",
      "synthetic": true
    }
  ],
  "first_observed_at": "2026-07-14T09:12:44Z",
  "last_observed_at": "2026-07-20T18:02:11Z",
  "last_verified_at": "2026-07-20T18:02:11Z",
  "is_demo": true
}
GET/v1/entities/{id}

One record with full detail (5 credits). Add ?include=history for the complete version history (20 credits).

GET/v1/changes

The attribute-level change stream: every versioned change across your records, newest first. Scope: entities:read. 1 credit per row.

FieldTypeDescription
feed_idstringRestrict to one feed
updated_sinceISO 8601Changes recorded at or after this instant
limitintPage size, 1–100
cursorstringOpaque cursor
GET/v1/events

Intelligence events, newest first. Scope: events:read. 3 credits per event, 10 per verified event. Returned events are marked delivered.

FieldTypeDescription
feed_idstringRestrict to one feed
event_typestringpermit_filed, permit_approved, bid_opened, contract_awarded, contractor_identified, price_reduced, …
confidence_minnumberMinimum confidence (0–1)
created_sinceISO 8601Detected at or after this instant
limitintPage size, 1–100
cursorstringOpaque cursor
Intelligence event
{
  "id": "ev_71c9d2",
  "feed_id": "fd_123",
  "event_type": "permit_approved",
  "occurred_at": "2026-07-20T18:02:12Z",
  "detected_at": "2026-07-20T18:02:12Z",
  "primary_entity_id": "a1b2c3d4e5f6",
  "summary": "Permit approved: Lakeview Medical Office Expansion in Orlando, FL ($2,400,000)",
  "relevance_score": 0.95,
  "opportunity_score": 87.5,
  "confidence": 0.95,
  "verification_status": "multi_source_verified",
  "match_reason": "Matched because signal 'approved' is in your watch list; value $2,400,000 meets your $500,000 threshold; located in Orlando, FL; contractor not yet identified.",
  "source_evidence": [ { "source_id": "src_88a1", "...": "..." } ],
  "is_demo": true
}
GET/v1/events/{id}

One event with evidence and match reason. Scope: events:read.

GET/v1/sources

Your source network: type, jurisdiction, cadence, permission and license status, reliability score, and health. Scope: sources:read. No credits.

GET/v1/usage

Month-to-date usage (requests, records, credits, webhook deliveries, source checks) against plan allowances, plus this key's own usage. Scope: usage:read.

POST/v1/exports

Synchronous export of up to 10,000 rows as JSON or CSV (text/csv attachment). Scope: exports:create. 1 credit per row; fires export.completed to subscribed webhooks.

Request body
{
  "kind": "entities",     // or "events"
  "format": "json",       // or "csv"
  "feed_id": "fd_123"     // optional
}
GET/v1/webhooks

List webhook endpoints. Scope: webhooks:read.

POST/v1/webhooks

Create an endpoint: {url, events: [...]}. Returns the whsec_ signing secret exactly once. Scope: webhooks:write.

DELETE/v1/webhooks/{id}

Delete an endpoint and its pending deliveries. Scope: webhooks:write.

Webhooks

Ballast pushes intelligence to your systems as it lands. Event types: entity.created, entity.updated, intelligence_event.created, intelligence_event.verified, feed.status_changed, source.failed, source.recovered, usage.threshold_reached, export.completed — plus ping for tests.

Delivery payload
{
  "id": "evt_ab12cd34ef56",
  "type": "intelligence_event.created",
  "created_at": "2026-07-20T18:02:12+00:00",
  "data": {
    "feed_id": "fd_123",
    "intelligence_event_id": "ev_71c9d2",
    "event_type": "permit_approved",
    "summary": "Permit approved: Lakeview Medical Office Expansion in Orlando, FL ($2,400,000)",
    "opportunity_score": 87.5,
    "confidence": 0.95
  }
}

Every delivery is signed. The X-Ballast-Signature header has the form t=<unix>,v1=<hex> where v1 is the HMAC-SHA256 of {t}.{raw_body} using your endpoint's whsec_… secret:

Verifying signatures (Python)
import hashlib, hmac, time

def verify(secret: str, header: str, body: bytes, tolerance: int = 300) -> bool:
    parts = dict(p.split("=", 1) for p in header.split(","))
    ts = int(parts["t"])
    if abs(time.time() - ts) > tolerance:
        return False  # replay window exceeded
    expected = hmac.new(secret.encode(), f"{ts}.".encode() + body, hashlib.sha256).hexdigest()
    return hmac.compare_digest(expected, parts.get("v1", ""))
  • Delivery is at-least-once per endpoint per event id; use the X-Ballast-Delivery header (stable across retries) for idempotency.
  • Non-2xx responses and timeouts retry with exponential backoff (30s doubling, capped at 1 hour) up to 8 attempts, then dead-letter. The console shows delivery history with manual retry, and secrets are rotatable.
  • Five consecutive failures mark the endpoint failing; it recovers automatically on the next success.

Errors

FieldTypeDescription
400statusInvalid cursor or parameters
401statusMissing, invalid, revoked, or expired key (or a non-blst_ credential)
402statusMonthly data-credit allowance exhausted
403statusMissing scope, or request IP not in the key's allowlist
404statusNot found — including ids that belong to another organization
422statusInvalid body (exports, webhooks)
429statusMonthly request allowance exhausted, or platform rate limit

Error bodies are {"detail": "human-readable reason"}.

The operations API is separate

The console and orchestration surface (/api/… — feeds administration, monitoring pipelines, approvals, audit) uses session or role-key auth and is not part of this public contract. In development the backend serves its interactive OpenAPI docs at http://localhost:8000/docs.

Note

SDKs for the data API are planned. The existing Python/TypeScript SDKs target the operations API (pipelines and runs), not /v1.