Official TypeScript SDK for JECP — Joint Execution & Commerce Protocol. The open protocol for agent-to-service commerce.
npm install @jecpdev/sdkJECP serves two opposite intents through one protocol:
- Sell to agents — earn revenue from AI agent traffic on your service
- Build with agents — give your agent its own wallet and budget cap
Both share the same SDK.
import { JecpClient, InsufficientBalanceError } from '@jecpdev/sdk';
const jecp = new JecpClient({
agentId: process.env.AGENT_ID!, // jdb_ag_*
apiKey: process.env.AGENT_KEY!, // jdb_ak_*
});
try {
const { output, billing, wallet_balance_after } = await jecp.invoke(
'jobdonebot/content-factory',
'translate',
{ text: 'Hello, world!', target_lang: 'JA' },
{ mandate: { budget_usdc: 1.00 } }, // pre-auth budget cap
);
console.log(output); // { translated: 'こんにちは、世界!' }
console.log('charged:', billing.charged);
console.log('balance after:', wallet_balance_after);
} catch (e) {
if (e instanceof InsufficientBalanceError) {
// Auto-recovery — open the topup URL
console.log('Top up here:', e.nextAction?.api);
} else {
throw e;
}
}import { JecpClient } from '@jecpdev/sdk';
const { agent_id, api_key, free_calls_remaining } = await JecpClient.register({
name: 'MyResearchAgent',
agent_type: 'research',
description: 'Reads PDFs, writes summaries',
});
console.log('Save these forever:');
console.log('AGENT_ID =', agent_id);
console.log('AGENT_KEY =', api_key);
console.log('Free calls:', free_calls_remaining);const { url } = await jecp.topup(20);
// open `url` in browser → pay via Stripe → balance += $20If you're a service provider receiving JECP invocations:
import { JecpProvider } from '@jecpdev/sdk';
const provider = new JecpProvider({
hmacSecret: process.env.JECP_HMAC_SECRET!, // from /v1/providers/register
});
// Works with Bun.serve, Cloudflare Workers, Next.js Route Handlers, Hono...
const handler = provider.createHandler(async (req) => {
// req.capability, req.action, req.input are validated and parsed
switch (req.action) {
case 'translate':
return { translated: await myTranslate(req.input) };
default:
throw new Error(`unknown action: ${req.action}`);
}
});
// Express:
app.post('/jecp', async (req, res) => {
const fetchReq = new Request(`https://${req.hostname}${req.url}`, {
method: 'POST',
headers: req.headers as any,
body: JSON.stringify(req.body),
});
const fetchRes = await handler(fetchReq);
res.status(fetchRes.status).json(await fetchRes.json());
});
// Bun.serve:
Bun.serve({ port: 3000, fetch: handler });Every JECP error includes a machine-readable next_action so agents can recover automatically:
try {
await jecp.invoke('any/cap', 'action', {});
} catch (e) {
if (e instanceof JecpError) {
switch (e.nextAction?.type) {
case 'topup': // open Stripe checkout
await jecp.topup(20);
break;
case 'register': // agent not authenticated
await JecpClient.register({ name: 'NewAgent' });
break;
case 'discover': // capability typo
const cat = await jecp.catalog();
// pick a real one
break;
case 'retry_after': // rate limited
await sleep(60_000);
break;
// ...
}
}
}| Class | Code | When |
|---|---|---|
InsufficientBalanceError |
INSUFFICIENT_BALANCE |
Wallet too low for action |
InsufficientBudgetError |
INSUFFICIENT_BUDGET |
Mandate budget < action price |
MandateExpiredError |
MANDATE_EXPIRED |
expires_at in the past |
AuthError |
AUTH_REQUIRED / INVALID_AGENT |
Missing or wrong credentials |
RateLimitError |
RATE_LIMITED |
60 RPM/agent default cap |
CapabilityNotFoundError |
CAPABILITY_NOT_FOUND |
Unknown namespace/capability |
ActionNotFoundError |
ACTION_NOT_FOUND |
Action not in manifest |
InsufficientTrustError |
INSUFFICIENT_TRUST |
Action requires higher Trust Tier |
ProviderError |
PROVIDER_ERROR / PROVIDER_UNREACHABLE |
Provider endpoint failure |
JecpError |
(anything else) | Generic |
All errors carry .code, .status, .message, .nextAction, .raw.
For machine-readable explanation of JECP (used by AI agents to understand the protocol):
const guide = await JecpClient.agentGuide();
// or fetch directly: https://jecp.dev/.well-known/agent-guide.jsonconst jecp = new JecpClient({
agentId, apiKey,
// All optional, sensible defaults:
timeoutMs: 30_000, // per-call default
retryConfig: { maxRetries: 3 }, // exp backoff + jitter on 5xx/408/429/network
logger: console, // observe retries/timeouts/errors
});
// AbortSignal + per-call timeout supported on every method
const ctl = new AbortController();
const r = await jecp.invoke('a/b', 'c', input, {
signal: ctl.signal,
timeoutMs: 60_000,
});
console.log('attempts taken:', r.attempts);
console.log('idempotency key:', r.request_id);Auto-retry preserves the same request_id across attempts so the Hub's idempotency
cache prevents double-charging.
For Cloudflare Workers, Deno, Vite/webpack browser builds, or any runtime without
node:crypto:
import { JecpClient, JecpProvider } from '@jecpdev/sdk/browser';The browser entry uses Web Crypto API exclusively. Same public API, different
HMAC backend. Build output is split (dist/index.js for Node,
dist/index-browser.js for edge).
When the Hub posts asynchronous events (invocation.completed,
wallet.low_balance, provider.kyc_status_changed):
import { verifyWebhook } from '@jecpdev/sdk';
app.post('/jecp/webhook', async (req) => {
try {
const event = await verifyWebhook({
body: req.rawBody, // raw bytes
signature: req.headers['x-jecp-webhook-signature'],
timestamp: req.headers['x-jecp-webhook-timestamp'],
secret: process.env.JECP_WEBHOOK_SECRET!,
});
// event.type, event.data
} catch (e) {
return new Response('invalid signature', { status: 401 });
}
});Replay window defaults to ±5 min. Configurable via replayWindowSec.
Runnable examples in examples/:
01-register-and-invoke.ts— register + first call02-error-recovery.ts—next_actiondiscriminated-union recovery03-mandate-budget-cap.ts— pre-authorized spend cap04-provider-server.ts— Provider endpoint with HMAC
- Spec: https://github.com/jecpdev/jecp-spec
- Live catalog: https://jecp.dev/v1/capabilities
- Health: https://jecp.dev/health
- Discussions: https://github.com/jecpdev/jecp-spec/discussions
- Changelog: CHANGELOG.md
- Email: hello@jecp.dev
Apache License 2.0 · Maintained by Tufe Company Inc.