Production identity
Pin the hex pubkey. Verify signed discovery before sending secrets or trusting quotes.
Developer reference
Your storefront owns its catalog and checkout. BitUnlock provides a store-neutral, signed API for Lightning payment verification and buyer-private delivery.
Pin the hex pubkey. Verify signed discovery before sending secrets or trusting quotes.
Broad NWC links are accepted when they support invoice creation and status lookup, including links that advertise pay_invoice. BitUnlock never invokes extra methods. Use small amounts and a dedicated low-balance seller wallet; restricted permissions remain safest.
No package-registry guesswork and no placeholder endpoints.
import {
createBitUnlockClient,
buildBitUnlockProductEventTemplate
} from "/assets/bitunlock-sdk.js";Publish a seller-signed kind 30078 event to a relay listed in verified discovery. BitUnlock reads only the exact service-facing tags; event content remains storefront-owned public metadata.
| Tag | Value | Meaning |
|---|---|---|
| d | unique identifier | Completes the product coordinate. |
| price | positive sats, SAT | Signed seller price used for the quote. |
| status | active | Only active products can quote. |
| br:delivery | seller-unlock-service-v1 | Selects this protocol. |
| br:unlock-service | https://api.bitunlock.network | Binds the endpoint. |
| br:unlock-service-pubkey | pinned service hex key | Binds the service identity. |
| br:unlock-policy | signed policy coordinate | Binds fee and service policy. |
| br:claim-epoch | positive integer | Binds the registered payload revision. |
Store-neutral boundary: BitUnlock does not require or interpret BitRoad soft-gate fields. BitRoad's soft gate is a separate store feature for listings that do not use BitUnlock.
Two independent axes, both set at product registration. Neither changes the quote/refresh/unlock contract a buyer follows.
direct-split (default): two legs, seller and BitUnlock, per the fee policy (100bps, 10-sat minimum). single-recipient: one leg, the full price, straight to the seller's own wallet — gated to tenants an operator has explicitly granted this for. Micro-ticket tenants on accrual billing keep the single leg while the platform fee accrues to a ledger, settled as one batched invoice via GET /v1/fee-ledger; overdue settlements suspend quoting only, never a buyer's paid order — and the service DMs the tenant's configured npubs when a settlement opens, three days before its deadline, and the day quoting pauses. Never hard-code a leg count; render whatever the quote returns.
A product doesn't have to be a static payload. deliveryMode: "execution" sells a bounded, server-run call to a third-party provider instead — pay, then get a real result computed on demand, billed per completed purchase. A Provider Connection can use Routstr conventions or an OpenAI-compatible API such as OpenRouter; its key is validated live, encrypted at rest, and never returned. Execution products are operator-gated.
An execution-mode quote takes an extra executionInput: {schema, ciphertextSha256, serviceEnvelope}, a NIP-44 payload (buyer → service pubkey) whose plaintext must match the product's own inputSchema. BitUnlock decrypts it, enforces the product's limits, and — for a blob-bearing input — actually fetches the referenced blob before minting an invoice. An unreachable host, a bad content type, a hash mismatch or an oversized input is refused with the specific cause — free, with no order to recover. A text-only input carries no blob, so that fetch and its failure modes do not apply.
inputSchema and outputSchema name a validator BitUnlock implements. They are not free-form identifiers: the platform parses and bounds every execution input and output itself, and a schema it does not know is refused at quote time with input_schema_unknown — so a product registered against an unrecognised name can never take a quote. That is what makes size and shape enforceable before an invoice exists, which is what keeps a bad input a free 422 instead of a paid order that fails. Adding a schema is a small platform change; ask for one before building against it.
What is left after payment is what could not be known before it: a provider error, a timeout, or a host that died between quote and unlock. Those surface as a generic execution_failed_permanently, execution_retryable_failure, or execution_retry_window_exhausted (the last two 503 with a retry-after, and neither terminal — a spent attempt budget refills). The specific internal cause is never exposed to the buyer.
Execution products are not limited to text results, single results, or hard-coded configuration.
A product whose outputSchema has a registered output transform is an image product: the provider is called with modalities: ["image","text"], the returned image is decoded by its actual magic bytes (PNG and JPEG both accepted — declared subtypes are not trusted), and the transform reduces it server-side to a compact JSON payload, e.g. a run-length-encoded 48×48 pixel-art object. The NIP-44 fulfillment envelope caps plaintext at 64KB; the transform is what makes an image deliverable at all. Buyer input for these products is text-only.
A product can bundle a second, image-producing execution into the same order — one quote, one invoice at the primary price, one envelope. After the primary output validates, a short text input is derived from it and the companion product runs with its own model, encrypted prompt, and provider connection; the result rides along as an extra payload field. Best-effort by contract: a companion failure never fails the paid order.
A separate write-only configuration vault for future tenant features: PUT /v1/tenant-secrets/{name} creates or rotates, GET lists names and timestamps only, DELETE removes. Values are AEAD-encrypted with the tenant id and secret name bound into the encryption context, capped at 8KB each and 64 per tenant, and no endpoint ever returns one. No production runtime feature consumes these values yet; execution API keys belong in Provider Connections.
Every completed purchase also produces an entitlement — a service-signed receipt that a buyer pubkey owns a specific purchase. It verifies offline against the pinned service key, so a relying party (the seller's own backend, or an unrelated service like a chat room, game server, or API gateway) never has to call BitUnlock just to check a perpetual entitlement.
Registering a product is perpetual access by default — one purchase, access forever. Set accessTermSeconds to sell a rental or manually renewable term: access expires after that many seconds, and another completed purchase renews it. BitUnlock does not automatically or recurringly charge the buyer.
Authenticated buyers call GET /v1/access-entitlements to recover every entitlement they own — the signed receipt, an accessPolicy (perpetual or a term with its expiry), and an effective status: active, expired, or revoked.
Sellers can revoke a specific buyer's entitlement with POST /v1/entitlements/:id/revoke. Revocation is never embedded in the signed receipt itself — changing signed content would invalidate its own signature — so a relying party that must honor revocation checks current status from the API rather than trusting a cached receipt indefinitely.
Validate a parsed entitlement document against the schema before trusting its shape.
Protected calls use NIP-98 kind 27235 authorization. Mutation retries reuse their idempotency key with a fresh signature.
| Method | Path | Actor | Purpose |
|---|---|---|---|
| GET | /health | public | Liveness + signed-policy status. |
| GET | /.well-known/bitunlock.json | public | Signed service policy for discovery. |
| GET | /openapi.json | public | OpenAPI 3.1 contract. |
| GET | /entitlements/:fulfillmentId/status | public | Opaque-ID revocation status for a relying party. |
| POST | /v1/tenants | seller | Create or update the store account; accept policy + fee cap. |
| GET | /v1/store | seller | Read the signed-in seller's onboarding and receiving-wallet status. |
| POST | /v1/wallet-connections | seller | Register a compatible NWC connection (encrypted at rest). |
| POST | /v1/products | seller | Register an encrypted delivery payload against a signed product. |
| POST | /v1/products/:coord/revoke | seller | Disable or rotate a delivery payload. |
| GET | /v1/tenant/overview | seller | Read exact tenant-scoped financial metrics and operational alerts. |
| GET | /v1/tenant/orders | seller | List sanitized tenant orders with stable cursor pagination. |
| POST | /v1/provider-connections | seller | Register an execution-provider API key (validated live, encrypted at rest). |
| GET | /v1/provider-connections | seller | List a tenant's provider connections. |
| POST | /v1/provider-connections/:id/rotate | seller | Replace and re-validate a provider connection's API key. |
| POST | /v1/provider-connections/:id/disable | seller | Disable a provider connection. |
| GET | /v1/provider-connections/:id/health | seller | Provider health, low-balance, and credential-staleness summary from recorded executions. |
| GET | /v1/tenant-secrets | seller | List stored secret names and timestamps — never values. |
| PUT | /v1/tenant-secrets/:name | seller | Create or rotate a write-only tenant secret (encrypted at rest, never returned). |
| DELETE | /v1/tenant-secrets/:name | seller | Remove a tenant secret. |
| GET | /v1/fee-ledger | seller | Platform-fee balance, entries and settlements; reading it also maintains them. |
| GET | /v1/notification-settings | seller | Who is DM'd when a fee settlement opens. |
| PUT | /v1/notification-settings | seller | Replace the additional settlement-DM recipients (up to 5 npubs). |
| POST | /v1/quotes | buyer | Create an immutable payment plan — two legs (seller, BitUnlock) by default, one for a single-recipient product. |
| GET | /v1/orders/:id | buyer-or-seller | Authorized order + settlement status. |
| POST | /v1/orders/:id/refresh | buyer | Replace only expired, unpaid legs. |
| POST | /v1/orders/:id/unlock | buyer | After all legs settle, return the idempotent NIP-44 envelope. |
| POST | /v1/orders/:id/execution-input | buyer | Submit a corrected execution input after a buyer-input-attributable permanent failure. |
| POST | /v1/orders/:id/execution-retry | seller | Retry a provider-side permanent execution failure (e.g. after topping up a balance). |
| POST | /v1/orders/:id/execution-reset | buyer | Reset a permanently-failed execution once, without waiting for the seller to act. |
| GET | /v1/access-entitlements | buyer | Recover every signed entitlement receipt the buyer owns, with current status. |
| POST | /v1/entitlements/:id/revoke | seller | Revoke a specific buyer's entitlement for a product you own. |
| GET | /v1/admin/overview | super-admin | Read sanitized service-wide commercial and operational totals. |
| GET | /v1/admin/tenants | super-admin | List sanitized tenant summaries with bounded pagination. |
| GET | /v1/admin/tenants/:tenantId | super-admin | Inspect one tenant's sanitized reporting and connection metadata. |
| GET | /v1/admin/orders | super-admin | List sanitized cross-tenant orders for support and incident response. |
| GET | /v1/admin/seller-allowlist | super-admin | List Nostr identities approved to create a seller tenant. |
| POST | /v1/admin/seller-allowlist | super-admin | Approve a Nostr identity for seller onboarding. |
| DELETE | /v1/admin/seller-allowlist/:pubkey | super-admin | Remove a Nostr identity from the seller onboarding allowlist. |