> ## Documentation Index
> Fetch the complete documentation index at: https://docs.mobula.io/llms.txt
> Use this file to discover all available pages before exploring further.

# x402 Get subscription status

> GET /agent/x402/subscription — Check plan stats for a wallet before subscribing or topping up. Requires a $0.001 USDC facilitator fee.

<Note>
  **Agent endpoints are in beta.** MPP equivalent: `GET /agent/mpp/subscription`. Overview: [Agentic payments](/guides/agentic-payments). x402 scripts: [x402 Subscription and top-up](/guides/x402-subscription-and-topup).
</Note>

`GET /agent/x402/subscription` returns current plan stats for the paying wallet. Call this **before** subscribing or topping up — it prevents duplicate subscriptions and confirms whether a wallet already has an active plan.

***

## Pricing

**\$0.001 USDC** — a facilitator fee for x402 payment-protocol settlement. This does not go to Mobula.

***

## Request flow

1. Call `GET /agent/x402/subscription` with no payment → **402 Payment Required** with a `payment-required` header containing the amount, network, and pay-to address.
2. Sign the required USDC amount and retry with an `x-payment` header → **200 OK** with the response body below.

***

## Response

**200 OK** — Plan and usage details for the wallet:

| Field               | Type             | Description                                                  |
| ------------------- | ---------------- | ------------------------------------------------------------ |
| `user_id`           | `string`         | Agent identifier — use as `agent_id` for top-up              |
| `api_keys`          | `string[]`       | API keys linked to the wallet                                |
| `plan`              | `string`         | Active plan name (`startup`, `growth`, `enterprise`, `free`) |
| `last_payment`      | `string \| null` | Timestamp of the most recent payment                         |
| `payment_frequency` | `string`         | Billing cadence (`monthly` or `yearly`)                      |
| `left_days`         | `number`         | Days remaining in the current billing period                 |
| `credits_left`      | `number`         | Credits remaining for API usage                              |
| `plan_active`       | `boolean`        | `true` if the plan is currently active                       |

```json theme={null}
{
  "user_id": "agt_xxxxxxxxxxxxxxxxxxxxxxxx",
  "api_keys": ["mbl_xxxxxxxxxxxxxxxxxxxxxxxx"],
  "plan": "growth",
  "last_payment": "2025-01-01T00:00:00.000Z",
  "payment_frequency": "yearly",
  "left_days": 312,
  "credits_left": 4800,
  "plan_active": true
}
```

**404 Not Found** — No agent exists for this wallet. Subscribe first via [x402 Subscribe to a plan](/guides/x402-agent-subscribe).

***

## Check before you act

Always call this endpoint first and use the response to decide your next step:

* **Before subscribing:** if `plan_active === true` and the plan is not Free, do not subscribe again — the wallet is already active.
* **Before topping up:** if `plan_active === false`, subscribe or renew first. Top-up requires an active plan.

***

## Common errors

| Situation                         | What happens                    | What to do                                                                   |
| --------------------------------- | ------------------------------- | ---------------------------------------------------------------------------- |
| 404 response                      | No agent exists for this wallet | Subscribe first via [x402 Subscribe to a plan](/guides/x402-agent-subscribe) |
| Missing `payment-required` header | Script exits with error         | Retry — usually a transient network issue                                    |
| No Solana/EVM option in 402       | Script exits with error         | Confirm `API_URL` is set to `https://api.mobula.io`                          |
| Wrong network                     | Payment rejected on-chain       | Ensure wallet is on Base mainnet or Solana mainnet, not devnet               |

***

## Reference scripts

Both scripts pay \$0.001 USDC, fetch plan status, and print all subscription fields. Use the same wallet that was used to subscribe.

<Tabs>
  <Tab title="Solana">
    **Prerequisites:**

    * Solana wallet holding USDC (mainnet) for the \$0.001 fee and SOL for transaction fees
    * Devnet USDC faucet: [faucet.circle.com](https://faucet.circle.com) — select Solana devnet

    **Run:**

    ```bash theme={null}
    SOLANA_PRIVATE_KEY="<base58_private_key>" \
    API_URL=https://api.mobula.io \
    bun run scripts/src/x402/agent-plan-stats-solana.ts
    ```

    **Environment variables:**

    | Variable             | Required | Default                 | Description                                     |
    | -------------------- | -------- | ----------------------- | ----------------------------------------------- |
    | `SOLANA_PRIVATE_KEY` | Yes      | —                       | Base58-encoded private key of the paying wallet |
    | `API_URL`            | No       | `http://localhost:4058` | API base URL                                    |

    **What the script does:**

    | Step | Action                                                                                     |
    | ---- | ------------------------------------------------------------------------------------------ |
    | 1    | Calls `GET /agent/x402/subscription` with no payment, receives 402                         |
    | 2    | Decodes payment details from `payment-required` header                                     |
    | 3    | Signs \$0.001 USDC payment                                                                 |
    | 4    | Retries with `x-payment` header, receives 200 with plan stats                              |
    | 5    | Prints all subscription fields: `user_id`, `plan`, `left_days`, `credits_left`, `api_keys` |

    **Full script:**

    ```typescript theme={null}
    import { createKeyPairSignerFromBytes } from '@solana/kit';
    import { x402Client, x402HTTPClient } from '@x402/core/client';
    import { registerExactSvmScheme } from '@x402/svm/exact/client';

    const config = {
      apiUrl: process.env.API_URL ?? 'https://api.mobula.io',
    };

    const SUBSCRIPTION_ENDPOINT = `${config.apiUrl}/agent/x402/subscription`;

    /**
     * Decode a base58 string (e.g. Solana private key) into raw bytes.
     * Uses the standard Bitcoin/Solana base58 alphabet (no 0, O, I, l).
     */
    function base58ToBytes(b58: string): Uint8Array {
      const ALPHABET = '123456789ABCDEFGHJKLMNPQRSTUVWXYZabcdefghijkmnopqrstuvwxyz';
      const map = new Uint8Array(256).fill(255);
      for (let i = 0; i < ALPHABET.length; i++) map[ALPHABET.charCodeAt(i)] = i;
      const bytes: number[] = [0];
      for (const char of b58) {
        const value = map[char.charCodeAt(0)];
        if (value === undefined || value === 255) throw new Error(`Invalid base58: ${char}`);
        let carry: number = value;
        for (let j = bytes.length - 1; j >= 0; j--) {
          carry += (bytes[j] as number) * 58;
          bytes[j] = carry & 0xff;
          carry >>= 8;
        }
        while (carry > 0) {
          bytes.unshift(carry & 0xff);
          carry >>= 8;
        }
      }
      for (const char of b58) {
        if (char !== '1') break;
        bytes.unshift(0);
      }
      return new Uint8Array(bytes);
    }

    async function main() {
      const privateKeyB58 = process.env.SOLANA_PRIVATE_KEY;
      if (!privateKeyB58) {
        console.error('ERROR: SOLANA_PRIVATE_KEY is not set.');
        console.error('  Provide a base58-encoded private key for a wallet holding USDC and SOL.');
        process.exit(1);
      }

      const signer = await createKeyPairSignerFromBytes(base58ToBytes(privateKeyB58));
      const walletAddress = typeof signer.address === 'string' ? signer.address : String(signer.address);

      const client = new x402Client();
      registerExactSvmScheme(client, { signer });
      const httpClient = new x402HTTPClient(client);

      // ── Step 1: Probe for 402 ─────────────────────────────────────────────────
      console.log('Step 1 — Probing subscription endpoint...');
      console.log('  Wallet:', walletAddress);
      console.log(`  GET ${SUBSCRIPTION_ENDPOINT}`);

      const probeRes = await fetch(SUBSCRIPTION_ENDPOINT);
      console.log('  Status:', probeRes.status);

      if (probeRes.status !== 402) {
        console.error('  Unexpected response (expected 402):', await probeRes.text());
        process.exit(1);
      }
      console.log('  402 received — payment details follow.');

      const paymentRequiredHeader = probeRes.headers.get('payment-required');
      if (!paymentRequiredHeader) {
        console.error('  Missing payment-required header.');
        process.exit(1);
      }

      const decoded = JSON.parse(Buffer.from(paymentRequiredHeader, 'base64').toString('utf-8'));
      const svmOption = decoded.accepts?.find(
        (a: { network: string }) => a.network?.startsWith('solana:'),
      );
      if (!svmOption) {
        console.error('  No Solana payment option found in 402 response.');
        console.error('  Check that API_URL is set to https://api.mobula.io');
        process.exit(1);
      }

      // ── Step 2: Sign payment ──────────────────────────────────────────────────
      console.log('\nStep 2 — Signing $0.001 USDC payment...');

      const paymentRequired = httpClient.getPaymentRequiredResponse(
        (name) => probeRes.headers.get(name),
        decoded,
      );

      let paymentHeaders: Record<string, string>;
      try {
        const payload = await httpClient.createPaymentPayload(paymentRequired);
        paymentHeaders = httpClient.encodePaymentSignatureHeader(payload);
      } catch (err) {
        console.error('  Failed to sign payment:', err);
        process.exit(1);
      }
      console.log('  Payment signed.');

      // ── Step 3: Submit and receive plan stats ─────────────────────────────────
      console.log('\nStep 3 — Fetching plan stats...');

      const paidRes = await fetch(SUBSCRIPTION_ENDPOINT, { headers: paymentHeaders });
      const body = await paidRes.text();
      console.log('  Status:', paidRes.status);

      if (paidRes.status === 404) {
        console.error('\n  No agent found for this wallet.');
        console.error('  Subscribe first: bun run scripts/src/x402/agent-subscribe-solana.ts');
        process.exit(1);
      }

      if (paidRes.status !== 200) {
        console.error('\n  Request failed.');
        console.error('  Response:', body || '(empty)');
        process.exit(1);
      }

      // ── Step 4: Print results ─────────────────────────────────────────────────
      const data = JSON.parse(body) as {
        user_id: string;
        api_keys: string[];
        plan: string;
        last_payment: string | null;
        payment_frequency: string;
        left_days: number;
        credits_left: number;
        plan_active: boolean;
      };

      console.log('\n✓ Subscription status retrieved.');
      console.log('');
      console.log('  user_id          :', data.user_id);
      console.log('  plan             :', data.plan);
      console.log('  plan_active      :', data.plan_active);
      console.log('  payment_frequency:', data.payment_frequency);
      console.log('  last_payment     :', data.last_payment ?? '—');
      console.log('  left_days        :', data.left_days);
      console.log('  credits_left     :', data.credits_left);
      console.log('  api_keys         :', data.api_keys.length, 'key(s)');
      data.api_keys.forEach((k, i) => {
        console.log(`    [${i + 1}]`, k);
      });

      process.exit(0);
    }

    main().catch((err) => {
      console.error(err);
      process.exit(1);
    });
    ```
  </Tab>

  <Tab title="EVM (Base)">
    **Prerequisites:**

    * EVM wallet on Base mainnet holding USDC for the \$0.001 fee

    **Run:**

    ```bash theme={null}
    TEST_PRIVATE_KEY=0x<your_private_key> \
    API_URL=https://api.mobula.io \
    bun run scripts/src/x402/agent-plan-stats-evm.ts
    ```

    **Environment variables:**

    | Variable           | Required | Default                 | Description                                            |
    | ------------------ | -------- | ----------------------- | ------------------------------------------------------ |
    | `TEST_PRIVATE_KEY` | Yes      | —                       | Hex-encoded private key of the paying wallet (`0x...`) |
    | `API_URL`          | No       | `http://localhost:4058` | API base URL                                           |

    **What the script does:**

    | Step | Action                                                                                     |
    | ---- | ------------------------------------------------------------------------------------------ |
    | 1    | Calls `GET /agent/x402/subscription` with no payment, receives 402                         |
    | 2    | Decodes payment details from `payment-required` header                                     |
    | 3    | Signs \$0.001 USDC payment on Base                                                         |
    | 4    | Retries with `x-payment` header, receives 200 with plan stats                              |
    | 5    | Prints all subscription fields: `user_id`, `plan`, `left_days`, `credits_left`, `api_keys` |

    **Full script:**

    ```typescript theme={null}
    import { x402Client, x402HTTPClient } from '@x402/core/client';
    import { registerExactEvmScheme } from '@x402/evm/exact/client';
    import { privateKeyToAccount } from 'viem/accounts';

    const config = {
      apiUrl: process.env.API_URL ?? 'https://api.mobula.io',
    };

    const SUBSCRIPTION_ENDPOINT = `${config.apiUrl}/agent/x402/subscription`;

    async function main() {
      const privateKey = process.env.TEST_PRIVATE_KEY as `0x${string}` | undefined;
      if (!privateKey) {
        console.error('ERROR: TEST_PRIVATE_KEY is required (hex string, e.g. 0x...)');
        process.exit(1);
      }

      const account = privateKeyToAccount(privateKey);
      const client = new x402Client();
      registerExactEvmScheme(client, { signer: account });
      const httpClient = new x402HTTPClient(client);

      // ── Step 1: Probe for 402 ─────────────────────────────────────────────────
      console.log('Step 1 — Probing subscription endpoint...');
      console.log('  Wallet:', account.address);
      console.log('  GET', SUBSCRIPTION_ENDPOINT);

      const probeRes = await fetch(SUBSCRIPTION_ENDPOINT);
      console.log('  Status:', probeRes.status);

      if (probeRes.status !== 402) {
        console.error('  Unexpected response (expected 402):', await probeRes.text());
        process.exit(1);
      }
      console.log('  402 received — payment details follow.');

      const paymentRequiredHeader = probeRes.headers.get('payment-required');
      if (!paymentRequiredHeader) {
        console.error('  Missing payment-required header.');
        process.exit(1);
      }

      const decoded = JSON.parse(Buffer.from(paymentRequiredHeader, 'base64').toString('utf-8'));
      const evmOption = decoded.accepts?.find(
        (a: { network: string }) => a.network?.startsWith('eip155:') || a.network?.toLowerCase().includes('base'),
      );
      if (!evmOption) {
        console.error('  No EVM (Base) payment option found in 402 response.');
        console.error('  Check that API_URL is set to https://api.mobula.io');
        process.exit(1);
      }

      // ── Step 2: Sign payment ──────────────────────────────────────────────────
      console.log('\nStep 2 — Signing $0.001 USDC payment...');

      const paymentRequired = httpClient.getPaymentRequiredResponse(
        (name) => probeRes.headers.get(name),
        decoded,
      );

      let paymentHeaders: Record<string, string>;
      try {
        const payload = await httpClient.createPaymentPayload(paymentRequired);
        paymentHeaders = httpClient.encodePaymentSignatureHeader(payload);
      } catch (err) {
        console.error('  Failed to sign payment:', err);
        process.exit(1);
      }
      console.log('  Payment signed.');

      // ── Step 3: Submit and receive plan stats ─────────────────────────────────
      console.log('\nStep 3 — Fetching plan stats...');

      const paidRes = await fetch(SUBSCRIPTION_ENDPOINT, { headers: paymentHeaders });
      const body = await paidRes.text();
      console.log('  Status:', paidRes.status);

      if (paidRes.status === 404) {
        console.error('\n  No agent found for this wallet.');
        console.error('  Subscribe first: bun run scripts/src/x402/agent-subscribe-evm.ts');
        process.exit(1);
      }

      if (paidRes.status !== 200) {
        console.error('\n  Request failed.');
        console.error('  Response:', body || '(empty)');
        process.exit(1);
      }

      // ── Step 4: Print results ─────────────────────────────────────────────────
      const data = JSON.parse(body) as {
        user_id: string;
        api_keys: string[];
        plan: string;
        last_payment: string | null;
        payment_frequency: string;
        left_days: number;
        credits_left: number;
        plan_active: boolean;
      };

      console.log('\n✓ Subscription status retrieved.');
      console.log('');
      console.log('  user_id          :', data.user_id);
      console.log('  plan             :', data.plan);
      console.log('  plan_active      :', data.plan_active);
      console.log('  payment_frequency:', data.payment_frequency);
      console.log('  last_payment     :', data.last_payment ?? '—');
      console.log('  left_days        :', data.left_days);
      console.log('  credits_left     :', data.credits_left);
      console.log('  api_keys         :', data.api_keys.length, 'key(s)');
      data.api_keys.forEach((k, i) => {
        console.log('    [' + (i + 1) + ']', k);
      });

      process.exit(0);
    }

    main().catch((err) => {
      console.error(err);
      process.exit(1);
    });
    ```
  </Tab>
</Tabs>

***

Full walkthrough: [x402 Subscription and top-up](/guides/x402-subscription-and-topup).
