Handsel. Vendor runbook
Reference implementation · Stripe test mode · No external vendor live yet
Quickstart · for API vendors

From nothing to a settled test receipt, no sales call in the middle.

You onboard the way your users will: the vendor account is a lazily-materialized side effect of integrating. Mount ~20 lines of config, keep your tool handler, and let a self-grading conformance check tell you you're done.

Status, up front: the handsel CLI and @handsel/* packages are not yet on npm (every command below is marked accordingly). The stack is a working reference implementation with Stripe in test mode; no external vendor is live yet. What you can run today is the real zero-form e2e from a clone — see the sourcenot yet public.

1Register — claim a tenant

Scaffold the edge, mint a signing key, and register your resource origin with the core. Registration pins your origin and your authoritative public key — the trust root receipt integrity rests on. No forms, no dashboard.

$ npm create @handsel/vendor shipping — not yet on npm
$ npx handsel keygen shipping — not yet on npm
$ npx handsel register shipping — not yet on npm

2Verify — the self-grading exam

"A phase is done iff its prove command is green" is the project's own law; it becomes your onboarding contract. verify runs a vendor-facing conformance slice against your integration and says, definitively, "you're done" — or exactly what's broken.

$ npx handsel verify shipping — not yet on npm

3Claim — connect Stripe

"Connect with Stripe." Connect OAuth hands over verified business identity, entity, and payouts — the entire company-details form, deleted, because Stripe already collected it. Handsel is not merchant of record on the primary rail and holds $0.

$ npx handsel claim shipping — not yet on npm

Stripe stays in test mode until you say otherwise; no live payments move.

4Connect — go live on the ladder

Point your MCP surface at the mounted routes and you're on the ladder: guest calls get a price-0 co-signed receipt, and the same grant climbs to claimed, mandated, and assured without re-onboarding.

$ npx handsel connect shipping — not yet on npm

5Three integration depths, one gradient

SDK

Full @handsel/sdk — ~20 lines of config over your existing endpoint. You keep your tool handler; the protocol does the rest.

Proxy

Zero code: CNAME a subdomain or add three lines of middleware and Handsel fronts your existing API. Day-one shallow entry.

Your agent

The onboarding surface is a prompt, not docs. /handsel-integrate reads your OpenAPI, wires the SDK, and opens the PR.

Vendors climb from shallow to deep exactly like users climb guest → mandated.

6The vendor edge — hello world

The whole integration is config plus your own handler. Field names are verbatim from @handsel/sdk; tier values are illustrative.

import { createHandselVendor, mountHandselRoutes } from '@handsel/sdk'
import { Hono } from 'hono'

const app = new Hono()

const vendor = createHandselVendor({
  product:      'lexica',
  name:         'Lexica',
  resource:     'https://api.example.com',
  handselCore:  'https://core.handsel.ai',
  signingKey:   SIGNING_SEED,             // 32-byte Ed25519 seed
  pricing:  { unit: 'call', price_usd_micros: 2000 },  // $0.002
  tiers: {
    guest:    { calls_per_day: 5 },      // zero-form taste
    claimed:  { calls_per_day: 500 },    // silent step-up
    trial:    { sponsored: false, ttl_s: 1_209_600 },
    mandated: {},                         // terms are the mandate
  },
  termsHash:    TERMS_HASH,               // sha256 of your consent text
  deletionSlaS: 604_800,               // one week
})

mountHandselRoutes(app, vendor)           // well-knowns + /handsel/*

app.all('/mcp', async (c) => {
  const ok = await vendor.authorize(c.req.raw)
  if (ok instanceof Response) return ok   // 401/403 challenge, verbatim
  const out = await yourTool(c.req.raw)      // keep your own handler
  await vendor.emitReceipt(ok, { path: '/mcp' })
  return out
})
For agent buildersstock @modelcontextprotocol/sdk

Zero Handsel-specific code. Your client's own OAuth machinery climbs.

No token means a 401 with the RFC 9728 pointer, and the MCP SDK's own OAuth flow runs discovery → registration → authorize → token. The client retries with the bearer and calls the real tool. The signup never happened.

const client = new Client({ name: 'my-agent', version: '0.0.0' })
const url = new URL('https://api.example.com/mcp')
const transport = () => new StreamableHTTPClientTransport(url, { authProvider })

// First connect: 401 → the SDK's OWN auth flow runs. No forms.
await client.connect(transport()).catch(() => {})

// Second connect: retry with the bearer, then call the REAL tool.
await client.connect(transport())
const result = await client.callTool({
  name: 'define',
  arguments: { word: 'serendipity' },
})
// → the real definition returns; the account is now a side effect of use.

Condensed from apps/mcp-e2e — a real, passing end-to-end test against the live stack over real Postgres.

Building an agent that discovers and onboards vendors autonomously? Point it at /llms.txt — the machine-readable what-Handsel-is, the discovery chain, and the hello-world, written to be pasted into an agent.

Playground — a live, hosted climb against your API is on the way. It ships from another workstream; this page will link it when it lands.