Skip to main content
Glama

x402_session_fetch

Make HTTP requests within an authenticated x402 V2 session without requiring additional payments. Automatically attaches session tokens for access to authorized endpoints.

Instructions

Make an HTTP request within an established x402 V2 session — NO payment required. The session token (signed by your wallet) is automatically attached to the request. The server recognises your session and grants access without a new on-chain payment. Requires a session_id from x402_session_start. Returns an error if the session has expired (call x402_session_start again to renew).

Input Schema

TableJSON Schema
NameRequiredDescriptionDefault
session_idYesSession ID from x402_session_start
urlYesURL to fetch (must be covered by the session)
methodNoHTTP method (default: GET)GET
headersNoAdditional headers (session token is injected automatically)
bodyNoRequest body for POST/PUT/PATCH
timeout_msNoTimeout in milliseconds (default: 30000)

Implementation Reference

  • The handler function responsible for executing the x402_session_fetch tool, which performs an HTTP request using an established session.
    export async function handleX402SessionFetch(
      input: X402SessionFetchInput
    ): Promise<{ content: Array<{ type: 'text'; text: string }>; isError?: boolean }> {
      try {
        const timeoutMs = input.timeout_ms ?? 30000;
    
        // Look up session
        const lookup = lookupSession(input.session_id);
    
        if (!lookup.found) {
          return {
            content: [
              textContent(
                `❌ Session not found: "${input.session_id}"\n\n` +
                `Create a new session with x402_session_start first.`
              ),
            ],
            isError: true,
          };
        }
    
        if (lookup.expired) {
          const expiredAt = new Date(lookup.session.expiresAt * 1000).toISOString();
          return {
            content: [
              textContent(
                `⏰ **Session Expired**\n\n` +
                `  Session ID: ${input.session_id}\n` +
                `  Endpoint:   ${lookup.session.endpoint}\n` +
                `  Expired at: ${expiredAt}\n\n` +
                `Call x402_session_start to establish a new session for this endpoint.`
              ),
            ],
            isError: true,
          };
        }
    
        const { session } = lookup;
    
        // Validate URL is covered by the session scope
        const urlCovered = isUrlCoveredBySession(input.url, session);
        if (!urlCovered) {
          return {
            content: [
              textContent(
                `❌ URL not covered by this session.\n\n` +
                `  Session endpoint: ${session.endpoint}\n` +
                `  Session scope:    ${session.scope}\n` +
                `  Requested URL:    ${input.url}\n\n` +
                `This URL is outside the session's ${session.scope === 'exact' ? 'exact match' : 'prefix match'} scope.\n` +
                `Create a new session for this URL with x402_session_start, or use x402_pay for a one-time request.`
              ),
            ],
            isError: true,
          };
        }
    
        // Build request headers — inject session token automatically
        const sessionHeaders = buildSessionHeaders(session);
        const method = input.method ?? 'GET';
    
        const mergedHeaders: Record<string, string> = {
          'Accept': 'application/json, text/plain, */*',
          ...sessionHeaders,
          ...(input.headers ?? {}),
        };
    
        if (input.body && ['POST', 'PUT', 'PATCH'].includes(method)) {
          if (!mergedHeaders['Content-Type']) {
            mergedHeaders['Content-Type'] = 'application/json';
          }
        }
    
        const requestInit: RequestInit = {
          method,
          headers: mergedHeaders,
          ...(input.body ? { body: input.body } : {}),
          signal: AbortSignal.timeout(timeoutMs),
        };
    
        // Make request — plain fetch, NO x402 payment client
        // The session token headers tell the server to bypass the payment flow
        const response = await fetch(input.url, requestInit);
        const responseText = await response.text();
    
        // Record the call in the session
        recordSessionCall(input.session_id);
    
        // Handle edge case: server still returned 402 (session not recognised)
        if (response.status === 402) {
          return {
            content: [
              textContent(
                `⚠️ **Server returned 402 — Session Not Recognised**\n\n` +
                `  URL:        ${input.url}\n` +
                `  Session ID: ${input.session_id}\n\n` +
                `The server returned HTTP 402 despite session headers being sent.\n` +
                `This means the server does not support x402 V2 session tokens yet,\n` +
                `or the session has been invalidated server-side.\n\n` +
                `Options:\n` +
                `  • Use x402_pay for a one-time payment to this URL\n` +
                `  • Contact the API provider about x402 V2 session support\n\n` +
                `📄 **Response Body**\n` +
                '```\n' + responseText.slice(0, 2000) + '\n```'
              ),
            ],
            isError: true,
          };
        }
    
        // Truncate very large responses
        const MAX_LEN = 8000;
        const truncated = responseText.length > MAX_LEN;
        const displayText = truncated
          ? responseText.slice(0, MAX_LEN) + '\n\n... [response truncated]'
          : responseText;
    
        const ttlRemaining = session.expiresAt - Math.floor(Date.now() / 1000);
        const callNumber = session.callCount; // after recordSessionCall incremented it
    
        let out = `⚡ **x402 Session Fetch** (call #${callNumber})\n\n`;
        out += `  Session ID:  ${session.sessionId}\n`;
        if (session.label) out += `  Label:       ${session.label}\n`;
        out += `  URL:         ${input.url}\n`;
        out += `  Method:      ${method}\n`;
        out += `  Status:      ${response.status} ${response.statusText}\n`;
        out += `  Session TTL: ${Math.ceil(ttlRemaining / 60)}m remaining\n`;
        out += `  💰 No payment — session token used\n\n`;
        out += `📄 **Response Body**\n`;
        out += '```\n' + displayText + '\n```';
    
        return { content: [textContent(out)] };
      } catch (error: unknown) {
        if (error instanceof Error && error.name === 'AbortError') {
          return {
            content: [textContent(`❌ x402_session_fetch failed: Request timed out after ${input.timeout_ms ?? 30000}ms`)],
            isError: true,
  • Input schema definition for the x402_session_fetch tool, using Zod for validation.
    export const X402SessionFetchSchema = z.object({
      session_id: z
        .string()
        .uuid()
        .describe('Session ID returned by x402_session_start.'),
      url: z
        .string()
        .url()
        .describe('URL to fetch within the session. Must be covered by the session endpoint/scope.'),
      method: z
        .enum(['GET', 'POST', 'PUT', 'PATCH', 'DELETE'])
        .optional()
        .default('GET')
        .describe('HTTP method (default: GET)'),
      headers: z
        .record(z.string())
        .optional()
        .describe('Additional HTTP headers (session token is injected automatically)'),
      body: z
        .string()
        .optional()
        .describe('Request body for POST/PUT/PATCH requests'),
      timeout_ms: z
        .number()
        .int()
        .min(1000)
        .max(60000)
        .optional()
        .default(30000)
        .describe('Request timeout in milliseconds (default: 30000)'),
    });
  • The MCP tool definition for x402_session_fetch, specifying its name, description, and input schema.
    export const x402SessionFetchTool = {
      name: 'x402_session_fetch',
      description:
        'Make an HTTP request within an established x402 V2 session — NO payment required. ' +
        'The session token (signed by your wallet) is automatically attached to the request. ' +
        'The server recognises your session and grants access without a new on-chain payment. ' +
        'Requires a session_id from x402_session_start. ' +
        'Returns an error if the session has expired (call x402_session_start again to renew).',
      inputSchema: {
        type: 'object' as const,
        properties: {
          session_id: {
            type: 'string',
            description: 'Session ID from x402_session_start',
          },
          url: {
            type: 'string',
            description: 'URL to fetch (must be covered by the session)',
          },
          method: {
            type: 'string',
            enum: ['GET', 'POST', 'PUT', 'PATCH', 'DELETE'],
            description: 'HTTP method (default: GET)',
            default: 'GET',
          },
          headers: {
            type: 'object',
            additionalProperties: { type: 'string' },
            description: 'Additional headers (session token is injected automatically)',
          },
          body: {
            type: 'string',
            description: 'Request body for POST/PUT/PATCH',
          },
          timeout_ms: {
            type: 'number',
            description: 'Timeout in milliseconds (default: 30000)',
            default: 30000,
          },
        },
        required: ['session_id', 'url'],
      },
    };

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