pay.sh docs
Building with paySubscriptions

Examples

Drop-in subscription specs — monthly access, weekly content, tiers, expiry caps, custom routing, mixed pricing — each downloadable and runnable with pay server start.

Each example below is a full, runnable provider spec. Click the download button, save the file, then in one terminal:

pay --sandbox server start <file>.yml

The first launch prompts you to publish each subscription's on-chain Plan PDA (sandbox covers the rent). Subsequent launches reuse the same PDAs.

From another terminal, activate as a subscriber with pay curl:

pay --sandbox curl http://127.0.0.1:1402/<path-from-the-spec>

Every spec uses routing.type: respond so it boots without any upstream service — the gateway returns 200 with the verified payment signature once the subscription activates. Each one also has a commented-out proxy block ready to flip on when you wire in a real upstream.

Agent summary

  • Every spec validates with pay --sandbox server start (verified locally before publishing this page).
  • The first call activates the subscription; subsequent calls within the period skip the activation prompt entirely.
  • pay subscriptions list shows every subscription the local wallet has activated.

Monthly API access ($9.99 / 30 days)

The canonical recurring API plan — one endpoint, $9.99 USDC every 30 days.

endpoints:
  - method: GET
    path: 'api/v1/pro/feed'
    subscription:
      period: '30d'
      price_usd: 9.99
      currency: USDC
monthly.yml

Subscriber flow:

# First call — activates the subscription, $9.99 charge settles.
pay --sandbox curl http://127.0.0.1:1402/api/v1/pro/feed

# Same call within 30 days — no payment prompt, just the response.
pay --sandbox curl http://127.0.0.1:1402/api/v1/pro/feed

Weekly content access ($1.99 / week)

Lower-priced, higher-cadence — a recurring content drop.

endpoints:
  - method: GET
    path: 'api/v1/daily-brief'
    subscription:
      period: '1w'
      price_usd: 1.99
      currency: USDC
weekly.yml
pay --sandbox curl http://127.0.0.1:1402/api/v1/daily-brief

The merchant's renewal worker pulls $1.99 every 7 days until cancellation.

Annual access with an expiry cap

52w is the longest supported period. Combine with expires_at to put a hard HTTP-layer ceiling on how long renewals keep firing.

endpoints:
  - method: GET
    path: 'api/v1/research/{report}'
    subscription:
      period: '52w'
      price_usd: 199.00
      currency: USDC
      expires_at: 2027-12-31T00:00:00Z
annual-with-expiry.yml
pay --sandbox curl http://127.0.0.1:1402/api/v1/research/q4-2026

After 2027-12-31T00:00:00Z the gateway stops renewing — the next gated request returns 402 with a fresh challenge regardless of on-chain state.

Multiple tiers on the same provider

Subscription tiers are just separate endpoints. Each tier gets its own on-chain Plan PDA.

endpoints:
  - method: GET
    path: 'api/v1/basic/quote/{symbol}'
    subscription:
      period: '30d'
      price_usd: 5.00
      currency: USDC

  - method: GET
    path: 'api/v1/pro/quote/{symbol}'
    subscription:
      period: '30d'
      price_usd: 25.00
      currency: USDC

  - method: GET
    path: 'api/v1/enterprise/quote/{symbol}'
    subscription:
      period: '30d'
      price_usd: 199.00
      currency: USDC
tiers.yml
# A basic subscriber can read /basic/ — but /pro/ still 402s for them.
pay --sandbox curl http://127.0.0.1:1402/api/v1/basic/quote/AAPL

Per-product revenue routing

Different endpoints can settle to different wallets via subscription.recipient. The operator-level recipient acts as the default; any endpoint can override.

operator:
  # recipient: '<your-default-revenue-wallet>'

endpoints:
  - method: GET
    path: 'api/v1/data/{stream}'
    subscription:
      period: '30d'
      price_usd: 49.00
      currency: USDC
      # recipient: '<dedicated-data-product-wallet>'

  - method: GET
    path: 'api/v1/ai/{model}'
    subscription:
      period: '30d'
      price_usd: 99.00
      currency: USDC
per-product-routing.yml

The recipient overrides are commented out in the downloadable spec so it boots cleanly under sandbox — uncomment and substitute real wallet addresses for production.

Subscription alongside metered pricing

Subscription and metered endpoints can live in the same spec. The two paths are independent — a pro/quote subscriber still pays $0.01 per call if they ever hit the metered /quote path.

endpoints:
  - method: GET
    path: 'api/v1/quote/{symbol}'
    metering:
      dimensions:
        - direction: usage
          unit: requests
          tiers:
            - price_usd: 0.01

  - method: GET
    path: 'api/v1/pro/quote/{symbol}'
    subscription:
      period: '30d'
      price_usd: 9.99
      currency: USDC
with-metered.yml
# Per-call: pays $0.01 every time.
pay --sandbox curl http://127.0.0.1:1402/api/v1/quote/AAPL

# Flat-fee: pays $9.99 once, free for 30 days.
pay --sandbox curl http://127.0.0.1:1402/api/v1/pro/quote/AAPL

Inspecting active subscriptions

After activating, every subscription is tracked locally under the active wallet.

# Every subscription the local wallet has activated, any network.
pay subscriptions list

# Just the mainnet ones.
pay subscriptions list --network mainnet

# Full detail for a specific subscription.
pay subscriptions status BXQGmO5VwTrl5RfFr6Y8XQZ4nPj9QqMOiKkRn3pZ4ZE

# JSON for scripting.
pay subscriptions list --json

The subscription_id is the one returned in the Payment-Receipt header at activation time.

Cancelling

pay subscriptions cancel BXQGmO5VwTrl5RfFr6Y8XQZ4nPj9QqMOiKkRn3pZ4ZE

cancel submits an on-chain cancel_subscription instruction. The currently-paid period is honored until its end; renewals stop after that. Use --local-only to flip the local status without an on-chain transaction (e.g. when the subscription has already been revoked on-chain through another path).

Next

  • Protocol & internals — the 402 challenge JSON, the activation transaction, and how renewal works under the hood.

On this page