Skip to main content
Glama

x402_session_start

Establish a payment session for AI agents to make a single on-chain payment, then access API endpoints multiple times using a signed session token without additional payments.

Instructions

Establish an x402 V2 payment session: make a SINGLE on-chain payment and receive a cryptographically signed session token. All subsequent calls to the same endpoint within the session lifetime use x402_session_fetch — no additional payments required. Agents pay once per session rather than once per API call. Session tokens are signed locally by your wallet key (non-custodial). Returns a session_id you pass to x402_session_fetch for all future calls.

Input Schema

TableJSON Schema
NameRequiredDescriptionDefault
endpointYesBase URL to establish a session for (e.g., "https://api.example.com/v1")
scopeNo"prefix": covers all paths under this URL (default). "exact": single URL only.prefix
ttl_secondsNoSession TTL in seconds (default: 3600 / 1 hour). Max: 30 days.
labelNoOptional label for this session (e.g., "Premium API session")
max_payment_ethNoMaximum ETH to pay for this session. Rejects if price exceeds this.
methodNoHTTP method for the initial request (default: GET)GET
headersNoAdditional request headers
bodyNoRequest body for POST/PUT/PATCH session-start requests
timeout_msNoRequest timeout in milliseconds (default: 30000)

Implementation Reference

  • The handleX402SessionStart function processes the x402_session_start tool, including payment handling using the X402 client and creating a session record.
    export async function handleX402SessionStart(
      input: X402SessionStartInput
    ): Promise<{ content: Array<{ type: 'text'; text: string }>; isError?: boolean }> {
      try {
        const wallet = getWallet();
        const config = getConfig();
        const timeoutMs = input.timeout_ms ?? 30000;
    
        // Parse optional payment cap
        let maxPaymentWei: bigint | undefined;
        if (input.max_payment_eth) {
          const cap = parseFloat(input.max_payment_eth);
          if (isNaN(cap) || cap <= 0) {
            throw new Error(`Invalid max_payment_eth: "${input.max_payment_eth}"`);
          }
          maxPaymentWei = BigInt(Math.round(cap * 1e18));
        }
    
        // Track payment
        let paymentMade = false;
        let paymentAmount = 0n;
        let paymentTxHash = '';
        let paymentRecipient = '';
        let paymentToken = '0x0000000000000000000000000000000000000000';
    
        // x402 client to handle the one-time session payment
        const x402Client = createX402Client(wallet, {
          autoPay: true,
          maxRetries: 1,
          globalPerRequestMax: maxPaymentWei,
          onBeforePayment: (req) => {
            const amount = BigInt(req.amount);
            if (maxPaymentWei && amount > maxPaymentWei) {
              throw new Error(
                `Session payment (${amount} wei) exceeds max_payment_eth cap ` +
                `(${maxPaymentWei} wei = ${input.max_payment_eth} ETH). ` +
                `Set a higher max_payment_eth to proceed.`
              );
            }
            return true;
          },
          onPaymentComplete: (log) => {
            paymentMade = true;
            paymentAmount = log.amount;
            paymentTxHash = log.txHash;
            paymentRecipient = log.recipient;
            paymentToken = log.token ?? '0x0000000000000000000000000000000000000000';
          },
        });
    
        const method = input.method ?? 'GET';
        const reqHeaders: Record<string, string> = {
          'Accept': 'application/json, text/plain, */*',
          ...(input.headers ?? {}),
        };
    
        if (input.body && ['POST', 'PUT', 'PATCH'].includes(method)) {
          if (!reqHeaders['Content-Type']) {
            reqHeaders['Content-Type'] = 'application/json';
          }
        }
    
        const requestInit: RequestInit = {
          method,
          headers: reqHeaders,
          ...(input.body ? { body: input.body } : {}),
          signal: AbortSignal.timeout(timeoutMs),
        };
    
        // Make the payment request
        const response = await x402Client.fetch(input.endpoint, requestInit);
        const responseText = await response.text();
    
        if (!paymentMade) {
          // Endpoint didn't require payment — still create a session if response was OK
          // This allows agents to pre-establish sessions for endpoints that may start
          // requiring payment, or to track usage even for free endpoints.
          return {
            content: [
              textContent(
                `ℹ️ **No Payment Required**\n\n` +
                `  Endpoint: ${input.endpoint}\n` +
                `  Status:   ${response.status} ${response.statusText}\n\n` +
                `No x402 payment was needed. The endpoint responded without requiring payment.\n` +
                `You do not need a session token — use x402_pay directly for free endpoints.\n\n` +
                `📄 **Response Body**\n` +
                '```\n' + responseText.slice(0, 4000) + (responseText.length > 4000 ? '\n... [truncated]' : '') + '\n```'
              ),
            ],
          };
        }
    
        // Create signed session record
        // The signMessage function uses viem's wallet client, signing locally with the agent's key
        const signMessage = async (message: string): Promise<string> => {
          // Access the walletClient from the wallet instance for local signing
          const wc = (wallet as unknown as { walletClient: { signMessage: (args: { message: string }) => Promise<string> } }).walletClient;
          return wc.signMessage({ message });
        };
    
        const session = await createSession({
          endpoint: input.endpoint,
          scope: input.scope ?? 'prefix',
          ttlSeconds: input.ttl_seconds,
          label: input.label,
          paymentTxHash,
          paymentAmount,
          paymentToken,
          paymentRecipient,
          walletAddress: config.walletAddress,
          signMessage,
        });
    
        const ttlRemaining = session.expiresAt - Math.floor(Date.now() / 1000);
        const expiresAt = new Date(session.expiresAt * 1000).toISOString();
    
        let out = `🔐 **x402 Session Established**\n\n`;
        out += `  Session ID:    ${session.sessionId}\n`;
        out += `  Endpoint:      ${session.endpoint}\n`;
        out += `  Scope:         ${session.scope}\n`;
        if (session.label) out += `  Label:         ${session.label}\n`;
        out += `  Network:       ${chainName(config.chainId)}\n`;
        out += `  TTL:           ${Math.ceil(ttlRemaining / 60)}m (expires ${expiresAt})\n\n`;
        out += `💳 **Session Payment**\n`;
        out += `  Amount:    ${paymentAmount.toString()} (base units)\n`;
        out += `  Recipient: ${paymentRecipient}\n`;
        out += `  TX Hash:   ${paymentTxHash}\n\n`;
        out += `✅ **Next Steps**\n`;
        out += `  Use \`x402_session_fetch\` with session_id="${session.sessionId}" for all subsequent\n`;
        out += `  requests to ${input.endpoint} — no further payments will be made during this session.\n`;
        out += `  Check session status with \`x402_session_status\`.\n\n`;
        out += `📄 **Initial Response** (${response.status})\n`;
        const truncated = responseText.length > 4000;
        out += '```\n' + responseText.slice(0, 4000) + (truncated ? '\n... [truncated]' : '') + '\n```';
    
        return { content: [textContent(out)] };
      } catch (error: unknown) {
        if (error instanceof Error && error.name === 'AbortError') {
          return {
            content: [textContent(`❌ x402_session_start failed: Request timed out after ${input.timeout_ms ?? 30000}ms`)],
            isError: true,
          };
        }
        return {
          content: [textContent(formatError(error, 'x402_session_start'))],
          isError: true,
        };
      }
    }
  • The X402SessionStartSchema defines the input validation rules for the x402_session_start tool.
    export const X402SessionStartSchema = z.object({
      endpoint: z
        .string()
        .url()
        .describe(
          'The base URL or endpoint to establish a session for. ' +
          'The agent pays once and the session covers all subsequent requests to this endpoint.'
        ),
      scope: z
        .enum(['prefix', 'exact'])
        .optional()
        .default('prefix')
        .describe(
          '"prefix" (default): session covers all paths under the endpoint URL. ' +
          '"exact": session only covers this exact URL.'
        ),
      ttl_seconds: z
        .number()
        .int()
        .min(60)
        .max(86400 * 30)
        .optional()
        .describe(
          'Session lifetime in seconds (default: SESSION_TTL_SECONDS env var or 3600). ' +
          'Min: 60 seconds. Max: 30 days.'
        ),
      label: z
        .string()
        .max(100)
        .optional()
        .describe('Optional human-readable label for this session (e.g. "Premium API session")'),
      max_payment_eth: z
        .string()
        .optional()
        .describe('Maximum ETH to pay for session establishment. Rejects if exceeded.'),
      method: z
        .enum(['GET', 'POST', 'PUT', 'PATCH', 'DELETE'])
        .optional()
        .default('GET')
        .describe('HTTP method for the initial payment request (default: GET)'),
      headers: z
        .record(z.string())
        .optional()
        .describe('Additional headers for the session-start request'),
      body: z
        .string()
        .optional()
        .describe('Request body for the initial session-start request (if POST/PUT/PATCH)'),
      timeout_ms: z
        .number()
        .int()
        .min(1000)
        .max(60000)
        .optional()
        .default(30000)
        .describe('Request timeout in milliseconds (default: 30000)'),
    });
  • The x402SessionStartTool definition registers the MCP tool with its name, description, and input schema.
    export const x402SessionStartTool = {
      name: 'x402_session_start',
      description:
        'Establish an x402 V2 payment session: make a SINGLE on-chain payment and receive ' +
        'a cryptographically signed session token. All subsequent calls to the same endpoint ' +
        'within the session lifetime use x402_session_fetch — no additional payments required. ' +
        'Agents pay once per session rather than once per API call. ' +
        'Session tokens are signed locally by your wallet key (non-custodial). ' +
        'Returns a session_id you pass to x402_session_fetch for all future calls.',
      inputSchema: {
        type: 'object' as const,
        properties: {
          endpoint: {
            type: 'string',
            description: 'Base URL to establish a session for (e.g., "https://api.example.com/v1")',
          },
          scope: {
            type: 'string',
            enum: ['prefix', 'exact'],
            description: '"prefix": covers all paths under this URL (default). "exact": single URL only.',
            default: 'prefix',
          },
          ttl_seconds: {
            type: 'number',
            description: 'Session TTL in seconds (default: 3600 / 1 hour). Max: 30 days.',
          },
          label: {
            type: 'string',
            description: 'Optional label for this session (e.g., "Premium API session")',
          },
          max_payment_eth: {
            type: 'string',
            description: 'Maximum ETH to pay for this session. Rejects if price exceeds this.',
          },
          method: {
            type: 'string',
            enum: ['GET', 'POST', 'PUT', 'PATCH', 'DELETE'],
            description: 'HTTP method for the initial request (default: GET)',
            default: 'GET',
          },
          headers: {
            type: 'object',
            additionalProperties: { type: 'string' },
            description: 'Additional request headers',
          },
          body: {
            type: 'string',
            description: 'Request body for POST/PUT/PATCH session-start requests',
          },
          timeout_ms: {
            type: 'number',
            description: 'Request timeout in milliseconds (default: 30000)',
            default: 30000,
          },
        },
        required: ['endpoint'],
      },
    };

Latest Blog Posts

MCP directory API

We provide all the information about MCP servers via our MCP API.

curl -X GET 'https://glama.ai/api/mcp/v1/servers/up2itnow0822/claw-pay-mcp'

If you have feedback or need assistance with the MCP directory API, please join our Discord server