Error Handling
How to handle payment failures gracefully in your MCP server.
Error Codes
When payre.charge() fails, it throws an error with a code property:
| Code | HTTP Status | Meaning | What to Tell the User |
|---|---|---|---|
insufficient_balance | 402 | Consumer's balance is too low | "Please top up at console.payre.dev" |
invalid_api_key | 401 | Missing or invalid consumer key | "Invalid API key. Get one at console.payre.dev" |
payre_unavailable | -- | Network error or timeout | Your choice: fail or allow free access |
Recommended Pattern
ts
async function handleToolCall(params, request) {
const apiKey = request.headers['x-payre-key'];
// No API key at all
if (!apiKey) {
return {
content: [{
type: 'text',
text: 'This tool requires payment. Add X-Payre-Key header. Get a key at console.payre.dev'
}],
isError: true,
};
}
try {
const receipt = await payre.charge({
apiKey,
tool: 'search',
amount: 0.005,
});
// Payment succeeded -- run the tool
const result = await doSearch(params.query);
return {
content: [{ type: 'text', text: JSON.stringify(result) }],
_meta: { receipt },
};
} catch (err) {
if (err.code === 'insufficient_balance') {
return {
content: [{
type: 'text',
text: `Insufficient balance. Top up at console.payre.dev (need $${0.005}, have $${err.balance ?? 0})`
}],
isError: true,
};
}
if (err.code === 'invalid_api_key') {
return {
content: [{
type: 'text',
text: 'Invalid API key. Get one at console.payre.dev'
}],
isError: true,
};
}
if (err.code === 'payre_unavailable') {
// Option 1: Fail closed (strict)
// return { content: [{ type: 'text', text: 'Payment service unavailable' }], isError: true };
// Option 2: Fail open (generous) -- allow free access when Payre is down
const result = await doSearch(params.query);
return {
content: [{ type: 'text', text: JSON.stringify(result) }],
_meta: { paymentSkipped: true, reason: 'payre_unavailable' },
};
}
throw err;
}
}Graceful Degradation
The payre_unavailable error means the SDK couldn't reach api.payre.dev within the timeout (default 5s). This happens if:
- Payre API is down
- Network issues between your server and Payre
- DNS resolution failure
You have two choices:
- Fail closed -- return an error. The consumer can't use the tool until Payre is back. This is safer for expensive tools.
- Fail open -- run the tool for free. This keeps your service available but you don't get paid for those calls. Better for tools where uptime matters more than billing accuracy.
ts
// Configurable per tool:
const FAIL_OPEN_TOOLS = new Set(['search', 'echo']);
const FAIL_CLOSED_TOOLS = new Set(['generate', 'premium-analysis']);
if (err.code === 'payre_unavailable') {
if (FAIL_OPEN_TOOLS.has(toolName)) {
return runToolForFree(params);
}
return { error: 'Payment service temporarily unavailable. Please try again.' };
}Timeout Configuration
If your server is in a region far from the Payre API, increase the timeout:
ts
const payre = new PayrePay({
secretKey: process.env.PAYRE_SECRET_KEY,
timeout: 10000, // 10 seconds (default is 5s)
});