allodic.

HTTP API reference

Everything the storefront does, you can do — same endpoints.

Public (no auth)

Route Returns
GET / Storefront index (HTML) or {registry, catalog, stats} (JSON)
GET /catalog All listings, machine-readable
GET /s/:slug Listing page (HTML for browsers, JSON otherwise)
GET /api/s/:slug Listing JSON, explicit
GET /api/stats {skills, licenses, transactions, volumeCents} aggregates
GET /buy/:slug, /thanks Checkout and post-purchase pages

Purchase & activation

Route Body → Returns
POST /api/checkout/:slug {email} → Stripe URL (free skills instant-grant; paid skills need Stripe, or the insecure dev flag)
POST /api/webhook/stripe Stripe events: checkout.session.completed / …async_payment_succeeded (validated against the immutable checkout-time terms, never the current listing), charge.refunded and refund.created (refunds accumulate; the license is revoked only when the captured amount is fully refunded). Processing is one transaction — on a storage failure the server returns 500 so Stripe redelivers
POST /api/activate/start {email} → one-time code via SMTP
POST /api/activate/finish {email, code}{token} (bearer, per-server)

Licensed (Bearer token)

Route Notes
GET /api/bundle/:slug Per-buyer signed bundle; 402 + buy link without a paid, non-revoked order for this slug
GET /api/updates/:slug?version= {updateAvailable} for entitled buyers
POST /api/events/:slug Delivery events (announced client-side, opt-out)
POST /api/reports/:slug Buyer's explicit usage report

Admin (x-admin-key)

Route Notes
POST /api/skills Publish (gated: compliance 422, scan criticals blocked)
POST /api/orders/:id/revoke Manual revoke
POST /api/trace Server-side trace of a suspected leak
GET /api/insights/:slug Aggregated delivery/report insights

External scanner webhook (you implement)

Set EXTERNAL_SCAN_URL; publish POSTs {name, version, files} (base64). Respond HTTP 200 with content-type: application/json and {findings:[{severity:'critical'|'warn', rule, path?, why}]}. Criticals block publish; errors and timeouts never do — the hook is advisory. Your response is treated as untrusted input: the timeout (EXTERNAL_SCAN_TIMEOUT_MS, default 10 s) covers the whole exchange including the body, the body is capped (EXTERNAL_SCAN_MAX_BYTES, default 1 MB), non-2xx and non-JSON responses are rejected, and at most 100 findings are merged (length-capped fields, truncation marked). The outcome lands in the capability as scan.external ("ok" or the error).

Rendered from docs/api.md in the allodic repository — that file is the source of truth.