PREVIEW · TEST MODE — no real money moves yet. Payouts are not live.
Stevens Keys by Marc Stevens

Stevens Keys creator docs

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

Quickstart

  1. Sign up at /dashboard. New accounts start unverified.
  2. Get verified. Click Request early access verification in the dashboard. During early access this is reviewed by hand; see below.
  3. Create a product for your MCP server (Products tab or POST /v1/products). You get a product_id. Unverified accounts can do this.
  4. Issue a key (Keys tab or POST /v1/keys). The plaintext key is shown once; we store only its SHA-256 hash.
  5. Validate the key on every tool call from your server (examples).

Want to see it work first? The demo lets you issue, validate and revoke a sandbox key without an account.

Verification during early access

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).

Validating from an MCP server

TypeScript SDK

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.

Raw fetch

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);

Revocation behaviour

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.

Quotas

Pricing: 85/15 and free tier

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.

API reference

Base URL https://stevens-keys.pages.dev. Creator endpoints take the session cookie (dashboard) or Authorization: Bearer sk_creator_test_.... JSON in and out.

MethodPathAuthNotes
POST/v1/validatelicense keyBearer or {key}; body {product_id?, units=1, meter=true}. CORS open.
GET/v1/config, /healthznoneRev-share, free tier, test_mode.
POST/v1/auth/signup · /login · /logoutnoneRate limited.
POST/v1/auth/password-reset/request · /confirmnoneReset emails aren't delivered in preview.
GET/POST/v1/verification-requestcreatorEarly-access manual verification. POST {note?}; 3/hour per creator.
GET/v1/mecreatorIncludes email_verified.
GET/POST/v1/products; GET/PATCH/DELETE /v1/products/:idcreatorDELETE archives.
GET/POST/v1/keys (?product_id=); GET /v1/keys/:idcreator (verified to issue)POST {product_id, label?, monthly_quota?, expires_at?} returns key once.
POST/v1/keys/:id/revoke · /suspend · /resumecreatorEffective on the next validate.
GET/POST/v1/api-secrets; POST /v1/api-secrets/:id/revokecreator (verified to create)Secret shown once.
GET/v1/usage?product_id=&days=30, /v1/revenuecreatorDaily usage, top keys, simulated revenue.
POST/v1/ledger/simulate-salecreator{product_id, gross_cents, key_id?}, simulated, CAD.
POST/v1/demo/keys, /v1/demo/revokenoneSandbox 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.

More from Marc Stevens