Skip to content

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:

CodeHTTP StatusMeaningWhat to Tell the User
insufficient_balance402Consumer's balance is too low"Please top up at console.payre.dev"
invalid_api_key401Missing or invalid consumer key"Invalid API key. Get one at console.payre.dev"
payre_unavailable--Network error or timeoutYour choice: fail or allow free access
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:

  1. Fail closed -- return an error. The consumer can't use the tool until Payre is back. This is safer for expensive tools.
  2. 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)
});