Skip to main content
The sandbox HTTP API is a complete subset of the production API, cookie-scoped per visitor. All routes run on Cloudflare Pages’ edge runtime at https://sandbox.api.enfinitos.com and respect a per-IP rate limit of 10 req/10s (burst) + 200 req/hour (sustained).

Auth

Every route after POST /api/sandbox/tenant requires the enf_sandbox_tenant cookie set by that initial call. The cookie value is <tenantId>.<hmac>; tampering breaks the HMAC and the cookie is rejected.

Tenant lifecycle

POST /api/sandbox/tenant

Provision a fresh tenant with the standard seed scenario (3 bases, 4 rights across DOOH, CTV, mobile, messaging). Idempotent — if a valid cookie is present and the tenant exists, returns the existing tenant. Response:

GET /api/sandbox/tenant

Read-only tenant summary. 404 if no tenant; 410 if tenant evicted (Worker isolate eviction).

POST /api/sandbox/reset

Drop the tenant + clear the cookie. Idempotent.

Rights bounded context

POST /api/sandbox/rights/issue

Body: { basisId, substrate, scope, effectiveFrom, effectiveUntil?, parentRightId? } Issue a new right under an existing basis.

POST /api/sandbox/rights/suspend

Body: { rightId, reason?, action?: "suspend" | "resume" } Suspend a right (default) or resume a suspended one.

POST /api/sandbox/rights/revoke

Body: { rightId, reason? } Terminal — cannot be undone.

Offers

POST /api/sandbox/offers/propose

Body: { rightId, toOrgId, scope, termsRef?, expiresAt? } Propose an offer to share / cede / sublet a right. Default expiry is 7 days from now.

POST /api/sandbox/offers/accept

Body: { offerId } Issues a derived right whose parentRightId points at the original right and contentHash references the accepted offer.

POST /api/sandbox/offers/counter

Body: { offerId, scope, termsRef?, expiresAt? } Counter-offer with revised terms. Original transitions PROPOSED → COUNTERED; a new offer goes the opposite direction with termsRef: "counter_of:<originalOfferId>".

POST /api/sandbox/offers/withdraw

Body: { offerId, action?: "withdraw" | "reject", reason? } Withdraw (proposer) or reject (recipient). Both move PROPOSED to WITHDRAWN/REJECTED respectively.

Challenges

POST /api/sandbox/challenges/open

Body: { rightId, challengerOrgId?, reason } Open a challenge against an active right. Default challengerOrgId is org_thirdparty_sandbox.

POST /api/sandbox/challenges/resolve

Body: { challengeId, upheld?, resolution?, action?: "resolve" | "withdraw" } Resolve as upheld (revokes the right) or overturned (right stays ACTIVE). action: "withdraw" resolves as WITHDRAWN — the challenger backed down.

Delivery + proof

POST /api/sandbox/run-demo

Body: { eventCount?: number (1..100, default 12), seed?: number } Fire N synthetic delivery events with substrate-realistic dwell times. Seals a fresh SignedProofPack containing every receipt plus the projected metering and reconciled settlement.

POST /api/sandbox/constraint-demo

Body: { scenario: "messaging-optout" | "automotive-speed" | "drone-remote-id-missing" } Deliberately configure a delivery that fails the pre-render constraint gate. Returns structured violations; no receipt is signed. Demonstrates real compliance enforcement.

GET /api/sandbox/proof-packs

List sealed packs (summaries only). Newest last.

GET /api/sandbox/proof-packs/[packId]

Full pack JSON plus the sandbox VerificationKey. The verification key is embedded so an offline auditor can verify without a second round-trip.

Provenance + inspector

GET /api/sandbox/provenance?rightId=…

Returns the lineage chain — basis → root right → derived rights → target right.

GET /api/sandbox/inspect

Full read-only tenant snapshot — every basis, right, offer, challenge, event, pack header. Consumed by the Tenant Inspector UI.

Verification keys

GET /api/sandbox/runtime-keys

Public, unauthenticated. Returns the sandbox’s verification-key directory. The auditor SDK consumes this to verify sandbox-issued packs.

Error responses

Every error returns the same envelope:
Stable codes: