burstdocs
DEVELOPERS / Reference

Data commerce: first implementation

Updated 2026-10-29. This is an integrated development slice of the roadmap, not the completed commercial platform. The fifteen component Go modules now include BurstData, BurstPay and BurstStream. They run as libraries inside burst; no additional daemon is needed.

What works

  • Private, authenticated dataset catalog with immutable publication, operator rights attestation, exact license-version acceptance and purpose review.
  • Free approved access, paid orders, explicit revocation and expiry. Each object is bound to at most one dataset, so another offer cannot bypass its commercial access rules.
  • Short-lived object grants provisioned in the same transaction as activation. The existing object-read API checks commercial access as well as current grants and policy before plaintext release. Even a broad space-level object grant cannot bypass a dataset restriction. Owners still require ordinary core read authority.
  • Immutable fiat offers, frozen order pricing, test-only hosted Stripe checkout and raw-body signature verification. Buyer identity comes from the authenticated session; callback URLs cannot activate orders.
  • Platform-account test payment events, durable event deduplication, gross receipt/seller-liability ledger entries and cumulative partial/full refund reversals. Every ledger entry has equal debit/credit amount in one currency. Any confirmed refund revokes this first release's license; later partial-license semantics require a versioned policy. Initiate test refunds in Stripe's dashboard; no refund-initiation API exists yet.
  • Fixture-only tick.v1 feeds, monotonic per-feed sequences, source-event deduplication, bounded authenticated replay and durable unique-payload preparation counters. Every replay checks current membership, entitlement and active signed read policy, including retries of old idempotency keys.

All domain mutations, authority changes, command receipts and outbox records share one SQLite immediate transaction or PostgreSQL advisory-locked transaction. These implement the existing single-authority model. Outbox records are durable integration/audit facts; no external consumer dispatcher is implemented. Failed grant provisioning rolls back local event intake and returns a retryable error; Stripe must retry. Operator reconciliation of permanently failed intake is still required before live payments.

Persistence and startup

SQLite upgrades runtime schema versions 1–4 to version 5 on open. PostgreSQL requires explicit migration-owner invocation of the existing -migrate-only flow; normal startup rejects an older schema. Schema v5 adds tenant-scoped commerce records, command outcomes and events. Back up the existing database before upgrading. PostgreSQL runtime roles need the appropriate DML permissions on the new tables; production role provisioning remains operator-managed.

Start the existing control service with its ordinary OIDC, TLS, signing policy worker, node and key-custody configuration. Commerce routes use the same session cookie, origin/CSRF checks and request limits. Core read policy and working encrypted object storage are required for dataset downloads. A space must grant a buyer space.read before the private catalog is visible. Operators or a holder of current grant.manage manage commerce; publication additionally checks source object write authority or feed ownership.

Stripe is optional. Without its configuration, catalog/free access and fixture feeds work, while checkout/webhook ingress report unavailable. Configure both private 0600 files:

sh
# Add to the existing burst command; do not put secrets on the command line.
-stripe-test-key-file /private/path/stripe-test-key \
-stripe-webhook-secret-file /private/path/stripe-webhook-secret

Only sk_test_ credentials, platform-account test events and HTTPS browser origins are accepted. Hosted Checkout currently uses the application's configured HTTPS origin for return navigation. A separate pay.goburst.app origin is a future deployment option, not a domain provisioned by these changes. Register the webhook URL /api/pay/v1/stripe/webhook and select checkout.session.completed, checkout.session.async_payment_succeeded and charge.refunded. Unhandled signed event types are acknowledged without domain effects. Refunds arriving before the corresponding successful payment return a conflict for retry rather than creating negative seller proceeds.

Orders freeze the price/license and expire after one hour. Create Checkout during the first 30 minutes, respecting Stripe's minimum session lifetime; the provider session expires with the order. Order ID supplies the provider idempotency key so a retry after local failure addresses the same session. Real provider reconciliation and abandoned/expired order recovery remain a subsequent gate.

API sequence

Each component's root is /api/{data|pay|stream}/v1/networks/{network}/spaces/{space}. All mutations require a persisted Idempotency-Key (8–128 characters), JSON content type, application Origin, session cookie and X-CSRF-Token. Unknown JSON fields are rejected. Resource IDs come from previous responses, not caller-selected ownership fields.

  1. Upload an encrypted object through the existing core API, or POST {stream}/feeds with {"name":"Fixture ticker","schema":"tick.v1","fixture":true,"retention_seconds":3600}.
  2. POST {pay}/offers with resource_id, license_version, integer amount, lowercase three-letter currency and duration_seconds (1–86400). Amount zero represents approved free access. This first increment supports operator-owned offers only. Buyers can inspect prices through GET {pay}/offers/{id} before requesting access; commerce managers can inspect gross postings through GET {pay}/ledger?after={id}.
  3. POST {data}/datasets with title, exactly one of object_id/feed_id, license_version, rights_attestation and offer_id. Publication verifies the immutable offer matches the resource/license and current publisher. GET {data}/datasets?after={id} lists up to 100 private catalog entries; object references and rights evidence are omitted from this projection. No public catalog endpoint exists yet.
  4. Buyer: POST {data}/requests with dataset_id, exact license_version and purpose. This records acceptance. One request per buyer/dataset is supported in this first increment. Renewal/reapplication after expiry or rejection is future work.
  5. Reviewer: POST {data}/requests/{id}/review with {"decision":"approve"}, reject or revoke. Approval activates a free entitlement immediately; a priced offer waits for payment. Buyer/reviewer can inspect the request through GET {data}/requests/{id}.
  6. Buyer: POST {data}/requests/{id}/orders with {}, then POST {pay}/orders/{id}/checkout with {}. Use the returned checkout_url. GET {pay}/orders/{id} is buyer-scoped. A verified signed webhook atomically applies payment, gross accounting, entitlement and any object grant. Late payment cannot revive a revoked request.
  7. Download through the existing /v1/networks/{network}/spaces/{space}/objects/{object} endpoint. A feed producer can POST {stream}/feeds/{id}/events with {"source_id":"fixture-1","tick":{"symbol":"FIXTURE-USD","price":12345,"scale":2,"quantity":5,"event_time":1800000000000}}; supply an actual event-time millisecond value, not that illustrative timestamp.
  8. Consumer: POST {stream}/feeds/{id}/replay with {"after_sequence":0,"limit":100}. The response includes ordered events, next_sequence, latest sequence and preparation usage. Resume with the last returned sequence and a new idempotency key. Repeating an event for the same principal does not increment counters. Feed metadata is available through GET {stream}/feeds/{id}.

Feed retention is currently a replay eligibility bound, not physical deletion. An expired resume range returns conflict and requires a fresh cursor choice; replay does not silently skip gaps. IDs/sequences are feed- and tenant-scoped; the request cannot replace authorization. Every API page is bounded, and the host's request deadline applies. No persistent subscription sessions, WebSocket transport or global ordering are provided yet. Usage records measure unique payloads prepared for a principal; they do not prove receipt and are not invoices.

Verification and remaining gates

sh
sh scripts/test-commerce.sh
# Includes the entire control suite on an isolated disposable PostgreSQL cluster:
sh scripts/test-runtime-postgres.sh

The commerce tests use real signed OIDC login fixtures, session/CSRF checks, an isolated on-disk DB, the policy worker, encrypted three-node storage and an injected Stripe HTTP transport with signed webhook fixtures. They cover commercial restrictions on broad core grants, altered licenses, tenant crossover, buyer/reviewer separation, refund increments and duplicate events, restart recovery, replay deduplication, concurrency and transactional rollback. They do not contact a real Stripe account. Existing CI's PostgreSQL matrix runs the same commerce integration suite on the runtime adapter.

Next work: live Stripe test-account acceptance and reconciliation; external outbox delivery; seller onboarding/Connect and fees; disputes; subscriptions and metered invoices; wallet proof/finality indexer and Ethereum escrow compatibility; commercial UI; licensed upstream feeds, candles/corrections, bounded WebSockets, quotas and retention purge; backup/restore and measured production capacity. The replay store currently holds fixture ticks as private control metadata, not encrypted object replicas. Real commercial data streaming needs its storage/residency and delivery-obligation implementation before enablement.

Provider behavior follows Stripe Checkout creation, webhook handling and event types.

Built from plans/data-commerce-development.md in the Burst Kit workspace. Check implementation status before deployment.

Search the documentation