How It Works
The loop
Section titled “The loop”env.<NAME>
.ns.method()
WorkerEntrypoint
fetchProof()
act + assert
Stripe
GitLab
Jira
fetchProof makes the upstream HTTP call, runs the built-in checks plus any method-specific checks, and returns the API result when those checks pass. The JSON receipt keeps the detail if you want to log or inspect it.
RuntimeConfig (base URL override, extra headers, prefix replacement) flows from the hand-written index.ts through the generated entrypoint to every method call.
Why JSRPC, not HTTP
Section titled “Why JSRPC, not HTTP”A capa capability is meant to sit behind your Worker, not act as another public proxy. That keeps credentials and API calls on the service-binding path.
Each capability returns 404 on direct HTTP requests. Reach it through a declared service binding instead.
Why one Worker per capability
Section titled “Why one Worker per capability”One Worker per API keeps setup understandable: one upstream service, one secret set, one service binding. It also lets you deploy and update capabilities independently.
Static secret by default, runtime auth when needed
Section titled “Static secret by default, runtime auth when needed”The basic deployment keeps its upstream credential as a Worker secret:
wrangler secret put STRIPE_API_KEYMulti-tenant platforms need a second mode: one shared capability Worker, many tenant credentials selected per RPC call.
await env.STRIPE.paymentIntents.create( { amount: 2500, currency: "usd" }, { auth: { apiKey: tenant.stripeKey } },);The implementation target is deliberately narrow: optional auth.apiKey plus capability-specific extra auth headers where a provider needs them. No per-call upstream URL changes, no tenant database inside capa, and no credential data in receipts. The full target contract is documented in Runtime auth.
Why no registry
Section titled “Why no registry”For now, the repo is the catalog. The site reads the manifests already checked in here, and you can fork or deploy the capabilities you actually want.
Returned receipt
Section titled “Returned receipt”{ capability: "stripe"; // or "gitlab", "jira", ... operationId: "PostCharges"; // upstream operation identifier namespace: "charges"; // RPC namespace method: "create"; // RPC method name http: "post"; // underlying HTTP verb path: "/v1/charges"; // upstream path risk: "high"; // low | medium | high startedAt: "2026-04-25T12:00:00Z"; durationMs: 234; act: { request: { method: "POST"; url: "https://api.stripe.com/v1/charges" }; status: 200; }; assert: [ { kind: "httpStatus", expected: "2xx", actual: 200, passed: true }, { kind: "field:id:matches", expected: "^ch_", actual: "ch_123", passed: true }, { kind: "field:status:==", expected: "succeeded", actual: "succeeded", passed: true }, { kind: "field:paid:==", expected: true, actual: true, passed: true }, ]; verdict: "pass";}Every call produces this record. Save it for debugging or reporting when it helps; otherwise treat it as useful extra context.
How capabilities are built
Section titled “How capabilities are built”Capabilities are generated from upstream OpenAPI specs rather than written endpoint by endpoint.
spec.openapi.json │ ▼ capa-codegen ──▶ schema.gen.ts (types from openapi-typescript) ──▶ capability.gen.ts (RpcTarget classes per namespace) ──▶ manifest.gen.ts (operationId → metadata) ──▶ runtime.ts (HTTP call + checks) │ ▼ src/index.ts (~30 LOC, applies per-method overrides) │ ▼ deployed WorkerThe hand-written layer stays thin. Add per-method overrides only where you want stronger checks than “the upstream accepted the call.”
Override examples
Section titled “Override examples”Stripe
Section titled “Stripe”charges: { create: { asserts: [ (body) => ({ kind: "id~^ch_", expected: "^ch_", actual: body.id, passed: /^ch_/.test(body.id) }), (body) => ({ kind: "status==succeeded", expected: "succeeded", actual: body.status, passed: body.status === "succeeded" }), ], },}GitLab
Section titled “GitLab”mergeRequests: { createNote: { asserts: [ (body) => ({ kind: "id:exists", expected: "non-null", actual: body.id, passed: body.id != null }), (body) => ({ kind: "id:number", expected: "number", actual: typeof body.id, passed: typeof body.id === "number" }), ], },}issues: { createIssue: { asserts: [ (body) => ({ kind: "id:exists", expected: "non-null", actual: body.id, passed: body.id != null }), (body) => ({ kind: "key:exists", expected: "non-null", actual: body.key, passed: body.key != null }), ], },}Self-managed routing
Section titled “Self-managed routing”GitLab behind Cloudflare Access
Section titled “GitLab behind Cloudflare Access”this.runtimeConfig = { baseUrl: env.GITLAB_BASE_URL_OVERRIDE, extraHeaders: { "CF-Access-Client-Id": env.CF_ACCESS_CLIENT_ID, "CF-Access-Client-Secret": env.CF_ACCESS_CLIENT_SECRET, },};Jira Server / Data Center
Section titled “Jira Server / Data Center”this.runtimeConfig = { baseUrl: env.JIRA_BASE_URL_OVERRIDE, prefixOverride: "/rest/api/2", // replaces /rest/api/3};