License keys, edge validation and usage metering for MCP servers. Everything here is test mode: keys start with sk_mcp_test_, revenue is a simulated ledger, and payouts aren't live.
Contents: Quickstart · Verification · Validating from an MCP server · Revocation · Quotas · Pricing · API reference
POST /v1/products). You get a product_id. Unverified accounts can do this.POST /v1/keys). The plaintext key is shown once; we store only its SHA-256 hash.Want to see it work first? The demo lets you issue, validate and revoke a sandbox key without an account.
We can't send verification emails yet, so verification is manual. After signing up you'll see a Request early access verification step in the dashboard. Submitting it records a request (optionally with a note about what you're building). Marc Stevens reviews requests by hand and marks accounts verified. Nothing is emailed; refresh the dashboard to see your status. Until you're verified you can create products, but you can't issue license keys or creator API secrets (403 email_unverified).
The SDK (@marcstevens/stevens-keys) isn't on npm yet. Until it is, use raw fetch below.
import { StevensKeys, withLicense } from "@marcstevens/stevens-keys";
const keys = new StevensKeys({ baseUrl: "https://stevens-keys.pages.dev", productId: "prod_..." });
// Every request must carry Authorization: Bearer sk_mcp_test_...
export default { fetch: withLicense(keys, (req, license) => handleMcp(req)) };
// Or check by hand inside a tool:
const r = await keys.validate(userKey, { units: 1 });
if (!r.allow) throw new Error(`license ${r.status}`);
Options: failMode: "closed" (default, deny if the API is unreachable) or "open" (allow a recently valid key for failOpenForMs). meter: false checks entitlement without counting usage. The SDK never caches metered calls.
const res = await fetch("https://stevens-keys.pages.dev/v1/validate", {
method: "POST",
headers: { authorization: `Bearer ${key}`, "content-type": "application/json" },
body: JSON.stringify({ product_id: "prod_...", units: 1 }),
});
const r = await res.json(); // 200 allow · 403 deny · 401 missing key
if (!r.allow) return deny(r.status);
Each key has one Durable Object that every validation goes through. Revoking or suspending a key updates that object before the API responds, so the next validation anywhere is denied (revoked / suspended, cache_ttl: 0). Revocation is terminal; suspend can be resumed. Archiving a product denies all its keys with product_archived.
Allowed responses carry a cache_ttl hint (default 30s, per-product, max 300s). If you cache allows for that long, a revoke takes effect within that window. If you don't cache (or use the SDK with metering on), it's immediate.
403 over_quota.units (1–1000) lets one call count as more than one unit.You keep 85% of sales processed through Stevens Keys; the platform fee is 15%. No monthly fee, no compute deduction. Free to sign up with the limits above. Payouts via Stripe Connect are not live yet; until they are, revenue in the dashboard is a simulated CAD ledger and no money moves.
Base URL https://stevens-keys.pages.dev. Creator endpoints take the session cookie (dashboard) or Authorization: Bearer sk_creator_test_.... JSON in and out.
| Method | Path | Auth | Notes |
|---|---|---|---|
| POST | /v1/validate | license key | Bearer or {key}; body {product_id?, units=1, meter=true}. CORS open. |
| GET | /v1/config, /healthz | none | Rev-share, free tier, test_mode. |
| POST | /v1/auth/signup · /login · /logout | none | Rate limited. |
| POST | /v1/auth/password-reset/request · /confirm | none | Reset emails aren't delivered in preview. |
| GET/POST | /v1/verification-request | creator | Early-access manual verification. POST {note?}; 3/hour per creator. |
| GET | /v1/me | creator | Includes email_verified. |
| GET/POST | /v1/products; GET/PATCH/DELETE /v1/products/:id | creator | DELETE archives. |
| GET/POST | /v1/keys (?product_id=); GET /v1/keys/:id | creator (verified to issue) | POST {product_id, label?, monthly_quota?, expires_at?} returns key once. |
| POST | /v1/keys/:id/revoke · /suspend · /resume | creator | Effective on the next validate. |
| GET/POST | /v1/api-secrets; POST /v1/api-secrets/:id/revoke | creator (verified to create) | Secret shown once. |
| GET | /v1/usage?product_id=&days=30, /v1/revenue | creator | Daily usage, top keys, simulated revenue. |
| POST | /v1/ledger/simulate-sale | creator | {product_id, gross_cents, key_id?}, simulated, CAD. |
| POST | /v1/demo/keys, /v1/demo/revoke | none | Sandbox demo product only. |
Validation statuses: active, invalid, missing_key, suspended, revoked, expired, over_quota, wrong_product, product_archived. Rate-limited calls return 429 with Retry-After.