Headless x402 subscriptions

Register, obtain and validate a live offer, authorize and pay, complete provisioning, use the account, and renew an API-only Ambient subscription headlessly over x402.

External clients can create and fund an API-only Ambient account without an email, browser login, wallet login, or the Ambient UI. The Ambient API key is the account credential. A wallet is used only to authorize the x402 payment.

See the restart-safe TypeScript x402 subscription example for a working client.

The integration has six steps:

  1. Register with POST /billing/x402/register-and-subscribe.
  2. Securely save the one-time-displayed API key before paying.
  3. Send the opaque payment_url with an x402 v2 client and select a live Solana or Base offer.
  4. Save the payment receipt and both Ambient grant headers, then call POST /billing/x402/subscription-complete.
  5. Use the API key for inference and key management.
  6. Use the same API key to create future renewal intents.

The billing API origin is https://api.ambient.xyz. After provisioning, configure OpenAI-compatible clients with:

Base URL:      https://api.ambient.xyz/v1
Authorization: Bearer <api_key>

Supported inference endpoints include POST /responses, POST /chat/completions, and GET /models relative to that base URL.

Credentials and addresses#

  • Ambient API key: bearer credential for the API-only Ambient account; it never authorizes a blockchain transfer.
  • Wallet private key or signer: authorizes the x402 payment and stays inside the client's secure wallet boundary. Never send it to Ambient or Jumpgate.
  • Payer address: public wallet address recorded in the settlement receipt; it is payment metadata, not an Ambient login.
  • USDC asset: the Base token contract or Solana mint identifying the accepted currency.
  • Payment recipient (payTo): public Base or Solana address receiving USDC. Validate it against the trusted allowlist below.
  • Ambient grant and signature: short-lived opaque credentials proving a settled order to the completion endpoint; they are not API keys.

If you need one-off inference without creating an account or API key, use the separate account-free x402 client flow.

Plans, duration, and expiry#

Supported plan_id values are:

  • starter
  • basic
  • power
  • pro

See Ambient pricing for current monthly prices and plan allowances. The signed order and live 402 determine the exact amount for a purchase.

months is an integer from 1 through 12 and defaults to 1. A bulk purchase is charged at the monthly price multiplied by months; there is no bulk discount. Each month is provisioned as a consecutive monthly period so its allowance resets independently.

Renewals must use the plan at the account's farthest paid-through boundary. Ambient rejects a different plan before creating a payment order. When an order is created, all nonexpired current and future periods, unexpired pending orders, and the requested purchase may not exceed 12 months in total. A valid grant settled earlier can exceptionally take the account past that cap if it completes later; Ambient honors paid value instead of discarding the settlement.

Registration and renewal payment URLs expire after 24 hours. That is not the payment authorization lifetime: create the authorization immediately before transmission and obey the shorter live rail-specific validity window. Base challenges currently use a 300-second authorization window; Solana blockhash validity is shorter and dynamic. A grant created by a successful settlement expires after seven days. An expired pending order becomes eligible for cleanup eight days after its payment URL expired; cleanup is asynchronous, so do not use retention as a payment or completion deadline.

Client safety#

Persist the registration response and API key before paying. Serialize concurrent payment attempts and maintain at most one live or possibly transmitted authorization for an order. An expired authorization may be replaced only when durable state proves it was never marked attempted; never replace one after transmission was attempted or became ambiguous.

Persist the raw response status and headers, including any receipt and grant, as soon as fetch resolves; read and store the body afterward as diagnostic data. Jumpgate is stateless, and Ambient does not expose a public settlement-status or grant-recovery endpoint.

For compile-tested TypeScript reference code with injected signer, durable storage, and locking boundaries, see Restart-safe TypeScript x402 subscription example.

Lifecycle#

  1. Register#

    Registration is public and does not use an Ambient credential:

    POST https://api.ambient.xyz/billing/x402/register-and-subscribe
    Content-Type: application/json
     
    {
      "plan_id": "basic",
      "months": 3
    }

    A successful request returns 201 Created and Cache-Control: no-store:

    {
      "api_key": "<50-character Ambient API key>",
      "order_id": "7592578c-37b3-441e-a97c-7d262ebc0a72",
      "plan_id": "basic",
      "months": 3,
      "payment_url": "https://jumpgate.ambient.xyz/paid/subscription/v2/basic/3/7592578c-37b3-441e-a97c-7d262ebc0a72?intent=<opaque>",
      "expires_at": "<RFC 3339 timestamp about 24 hours after registration>",
      "completion_url": "/billing/x402/subscription-complete"
    }

    Registration creates a pending order containing the bootstrap key prefix and hash. It does not create the API-only team, activate the key, or add subscription periods until a valid paid grant completes atomically.

    Registration is not idempotent: every successful call creates a distinct order and bootstrap key. A 429 Too Many Requests response includes Retry-After. Only create a replacement order before any payment authorization has been created for the previous order; unexpired pending renewal orders also reserve months against the 12-month cap.

    When x402 sales are paused, registration and renewal intent creation return 403 Forbidden. Completion remains available so a valid saved grant for a payment already made can still provision idempotently; a sales pause is not a reason to abandon that grant.

    Do not parse, reconstruct, shorten, or change payment_url. The signed intent binds the order, plan, duration, amount, and expiry.

  2. Obtain and validate the live offer#

    Send an empty unsigned POST to the opaque payment_url with redirects disabled. A valid challenge returns 402 Payment Required with a base64-encoded x402 v2 object in Payment-Required.

    Ambient supports these USDC rails when they appear in the live response:

    Network CAIP-2 identifier Canonical USDC asset Accepted recipient
    Solana mainnet solana:5eykt4UsFv8P8NJdTREpY1vzqKqZKvdp EPjFWdd5AufqSSqeM2qN1xzybapC8G4wEGGkZwyTDt1v AYYo37bgztqAgP6wp2S9SiYWcijr8gz2o14c8RGWojJZ
    Base mainnet eip155:8453 0x833589fcd6edb6e08f4c7c32d4f71b54bda02913 0x6992c688c56BE442EfE1E1cE7D5c95212ED7d59B

    The live accepts array is authoritative for currently available rails and amounts, but it is not by itself a trusted recipient allowlist. A client must fail closed unless the selected offer satisfies all of these conditions:

    • x402Version is 2.
    • scheme is exact.
    • network, asset, and payTo match one allowed rail above. For Base, normalize both addresses to lowercase before comparison because a live challenge may use checksum casing. Compare Solana identifiers exactly because they are case-sensitive.
    • amount is a canonical positive base-10 integer string. Parse it with BigInt or an equivalent checked integer type, require every offered rail to have the same value, and enforce the client's configured or user-approved maximum.
    • The resource identifies the intended Jumpgate payment URL.

    Do not accept an arbitrary payTo from the live challenge. Configure or publish the trusted recipients above with the client, and update that allowlist before paying a rotated recipient. The challenge itself is not signed and does not let the client select Jumpgate's configured facilitator.

    Choose one offered rail supported by the wallet. Do not create a payment payload merely to inspect the offer.

  3. Authorize and pay#

    Use the linked TypeScript reference for the exact package pins and injected Base or Solana signer boundary. Do not use an automatic paid-fetch wrapper for a subscription payment_url.

    For each order:

    1. Create or reuse one current authorization under the per-order lock. Replace an expired one only when durable state proves it was never attempted.
    2. Persist its exact encoded Payment-Signature before transmission when the client supports a pre-send hook.
    3. Send an empty POST to the exact saved payment_url, with redirects disabled, the saved Payment-Signature, and a timeout of at least 125 seconds.
    4. As soon as fetch resolves, persist the raw status and all headers, including any Payment-Response and Ambient grants. Only then read and store the body as best-effort diagnostic data. Capture headers on non-2xx responses too.

    Subscription settlement happens before Jumpgate creates the grant and attempts Ambient's callback. The longer timeout covers facilitator discovery, settlement, confirmation, and the bounded synchronous callback attempts; a shorter client timeout can turn a successful on-chain payment into an ambiguous client result.

    A successful Jumpgate response is 200 OK with this JSON shape:

    {
      "order_id": "7592578c-37b3-441e-a97c-7d262ebc0a72",
      "plan_id": "basic",
      "months": 3,
      "amount_micro_usdc": 60000000
    }

    The numeric amount is illustrative; the validated live offer is authoritative. Save these response headers:

    Payment-Response
    X-Ambient-X402-Grant
    X-Ambient-X402-Grant-Signature

    X-Payment-Response may be accepted as a legacy fallback when Payment-Response is absent. New integrations should use the standard Payment-Response header.

    Header names are case-insensitive. The selected network, payer, and transaction are in the payment response and the signed grant, not the JSON response body. Before treating the payment as successful, require:

    • HTTP status 200.
    • A decoded settlement receipt with success=true.
    • A nonempty payer and transaction.
    • A network matching the selected offer.
    • A payer matching the expected signer address; normalize Base addresses to lowercase and compare Solana addresses exactly.
    • If the receipt includes amount, an amount matching the selected offer. Receipt amount is optional, so absence alone is not an error.
    • Both Ambient grant headers.

    The unsigned JSON response body is diagnostic only and is not proof of settlement or provisioning. Treat the grant headers as opaque credentials. The signed grant binds the order, plan, months, amount, selected network, canonical asset, configured recipient, payer, transaction, issue time, and expiry. Base identifiers are normalized to lowercase; Solana identifiers remain case-sensitive.

    Jumpgate starts Ambient's completion callback independently of the downstream connection and normally waits for it, retrying only transient failures. Callback rejection or exhaustion still returns the valid grant and 200; explicit client completion remains required. A downstream timeout or disconnect does not cancel that tracked callback or itself prove payment failure.

    An unsigned request or a header rejected before authorization acceptance returns 402 Payment Required with a Payment-Required challenge. A missing, expired, or path-mismatched Ambient intent returns 400 Bad Request before payment handling. Once the saved authorization may have been transmitted, do not infer from a status code or intermediary-generated error page that settlement did not occur.

    Treat every non-success after the authorization may have been transmitted as terminal for automatic payment handling. The outcome is unknown and funds may be at risk if any of these occur:

    • A timeout, disconnect, or other transport loss.
    • A challenge-free non-success without a structured facilitator_rejected or settlement_unavailable classification.
    • A 200 with an invalid or unsuccessful receipt.
    • A missing grant header, empty payer or transaction, network mismatch, or a present receipt amount that mismatches the selected offer.

    Jumpgate uses 409 Conflict, X-Ambient-X402-Error-Code: AMBIGUOUS_X402_SETTLEMENT, and the shared error code as a compatibility envelope for all structured post-authorization failures. Every variant stops automatic retries, but only payment_status=unknown or payment_status=facilitator_reported_success indicates funds may be at risk.

    When Jumpgate cannot confirm settlement, its JSON body includes bounded context that is safe to persist or display:

    {
      "code": "AMBIGUOUS_X402_SETTLEMENT",
      "error": "Payment settlement outcome is ambiguous; do not create another payment authorization.",
      "order_id": "7592578c-37b3-441e-a97c-7d262ebc0a72",
      "phase": "settlement",
      "reason": "settlement_unconfirmed",
      "payment_status": "unknown",
      "next_action": "do_not_create_new_authorization"
    }

    When the facilitator reports a recognized pre-broadcast validation rejection without a settlement identifier, Jumpgate keeps the same challenge-free compatibility response but distinguishes it from an unknown outcome:

    {
      "code": "AMBIGUOUS_X402_SETTLEMENT",
      "error": "Payment settlement failed; do not create another payment authorization automatically.",
      "order_id": "7592578c-37b3-441e-a97c-7d262ebc0a72",
      "phase": "settlement",
      "reason": "facilitator_rejected",
      "facilitator_reason": "insufficient_funds",
      "payment_status": "facilitator_reported_failure",
      "next_action": "do_not_create_new_authorization"
    }

    facilitator_reason is optional and limited to insufficient_funds, authorization_not_yet_valid, authorization_expired, invalid_authorization, payment_mismatch, transaction_rejected, unsupported, or invalid_response. It reports the facilitator's bounded classification, not independent proof of the on-chain outcome. A missing, unrecognized, or conflicting reason, or a failure carrying a transaction or legacy signature, is classified as settlement_unconfirmed with payment_status=unknown.

    If Jumpgate proves that a local failure happened before it submitted the authorization to the facilitator, the same 409 instead uses reason=settlement_unavailable and payment_status=not_submitted. This does not trigger Ambient's payment-risk pager, but the client must still preserve the saved authorization and stop automatic payment handling.

    For other failures, phase is settlement, receipt_validation, or post_settlement. Receipt-validation reasons are limited to missing_receipt, missing_network, missing_payer, missing_transaction, and unsupported_network; post-settlement processing uses post_settlement_failure. payment_status=facilitator_reported_success means the facilitator reported success but does not replace the requirement to validate and persist Payment-Response. Jumpgate does not put raw facilitator errors, payer or transaction identifiers, payment credentials, or grants in this JSON body.

    Ambient's operational pager is narrower than the client's safety rule. It fires only when Jumpgate cannot determine the settlement outcome, cannot turn a reported success into a valid grant, or the Ambient callback is rejected or exhausts its retries. A downstream client timeout or disconnect does not page by itself or cancel an already-started callback; if that callback succeeds, the account is provisioned even when the client did not receive the response.

    Treat next_action=do_not_create_new_authorization as authoritative. An intermediary-generated response such as a Cloudflare 524 cannot contain these Jumpgate fields, so the broader challenge-free safety rule still applies.

    Do not follow Retry-After, invoke generic HTTP retries, resend the paid request, or create a replacement authorization after any post-transmission non-success. A genuine pre-payment retry remains safe only when the response is a normal 402 carrying a valid Payment-Required challenge and the client has not transmitted an authorization.

  4. Complete provisioning#

    Resolve the relative completion_url against https://api.ambient.xyz, not the /v1 inference base. The endpoint is public: the signed grant is its authorization, so do not send the bootstrap API key.

    POST https://api.ambient.xyz/billing/x402/subscription-complete
    Content-Type: application/json
     
    {
      "grantToken": "<X-Ambient-X402-Grant value>",
      "signature": "<X-Ambient-X402-Grant-Signature value>"
    }

    A successful completion returns:

    {
      "team_id": "344740c2-fcb4-470f-8c88-721765743b67",
      "plan_id": "basic",
      "months": 3,
      "current_period_end": "<RFC 3339 paid-through timestamp>",
      "already_completed": true
    }

    Completion is idempotent for the same order and payment. Always call it after receiving a grant, even if Jumpgate's callback may already have provisioned the account. already_completed is normally true when that callback succeeded and false when the client performed the first successful provisioning. Repeating the same order and payment never adds more months.

    A still-valid seven-day grant can complete after the original 24-hour payment URL expires. Complete promptly instead of relying on the retention window. Treat a completion 409 Conflict as a terminal reconciliation case; do not try another payment.

  5. Use the account#

    Example activation and usage check:

    GET https://api.ambient.xyz/billing/usage-summary
    Authorization: Bearer <api_key>

    After provisioning, the response is authenticated and reports the expected active subscription. Before provisioning, the bootstrap key returns 401 Unauthorized.

    Example model listing:

    GET https://api.ambient.xyz/v1/models
    Authorization: Bearer <api_key>

    Example Chat Completions request:

    POST https://api.ambient.xyz/v1/chat/completions
    Authorization: Bearer <api_key>
    Content-Type: application/json
     
    {
      "model": "<model from /v1/models>",
      "messages": [{"role": "user", "content": "Hello"}]
    }

    The same API key can use the Responses API and manage additional Ambient API keys through these routes:

    • GET https://api.ambient.xyz/keys
    • POST https://api.ambient.xyz/keys
    • PATCH https://api.ambient.xyz/keys/{prefix}
    • DELETE https://api.ambient.xyz/keys/{prefix}

    These routes use Authorization: Bearer <api_key> and resolve against https://api.ambient.xyz, not the /v1 inference base.

  6. Renew#

    Create a renewal intent with any API key belonging to the account:

    POST https://api.ambient.xyz/billing/x402/subscription-intent
    Authorization: Bearer <api_key>
    Content-Type: application/json
     
    {
      "planId": "basic",
      "months": 2
    }

    planId is the canonical example field; the API also accepts the compatibility spelling plan_id.

    The response contains another opaque payment URL:

    {
      "order_id": "bb26e7f0-ef27-4dde-a6b7-35ad2e13f0fe",
      "plan_id": "basic",
      "months": 2,
      "network": "solana:5eykt4UsFv8P8NJdTREpY1vzqKqZKvdp",
      "payment_url": "https://jumpgate.ambient.xyz/paid/subscription/v2/basic/2/bb26e7f0-ef27-4dde-a6b7-35ad2e13f0fe?intent=<opaque>"
    }

    network is a legacy Solana compatibility hint, not a complete list of available rails. Select a rail from the live 402 response. Pay and complete the renewal through the same payment, grant-persistence, and completion steps as registration. The bootstrap-key activation probe used for initial registration does not apply to renewals. The new periods begin at the account's farthest paid-through boundary.

    Renewal intent creation is not idempotent. An active Stripe subscription or another non-x402 subscription cannot be extended through this endpoint; Ambient returns 409 Conflict.

Ambiguous results#

If both saved grant headers are available, submit them to the idempotent completion endpoint.

For initial registration, GET /billing/usage-summary with the saved bootstrap key can confirm whether the callback activated the account. A successful authenticated response reporting the expected active plan confirms activation; 401 Unauthorized means the key remains inactive. This check cannot prove a renewal completed because the account was already active before renewal.

If no grant is available and activation cannot be confirmed, do not automatically resend the payment, generate another authorization, or create a replacement order. Stop and contact [email protected] for reconciliation.

For support, provide only:

  • Order ID.
  • Payment rail/network.
  • Payer address.
  • Transaction ID or EVM authorization nonce.

Current limitations#

  • Renewal is manual; there is no unattended auto-renewal.
  • API-only accounts cannot log into the Ambient web UI. Losing every API key means losing account access; there is no email, wallet-signature, or browser recovery flow.
  • Jumpgate is stateless and exposes no public settlement-status or grant-recovery endpoint.
  • The callback is best effort. Deterministic client completion remains required.
  • There is no cancellation or modification endpoint for a pending order.
  • Two independently authorized transactions can still pay the same order. Ambient detects the second receipt but cannot prevent the second on-chain transfer; support reconciliation may be required.
  • Settlement can succeed immediately before a Jumpgate crash loses both the paid response and callback, requiring manual reconciliation or refund.
  • A late valid settlement can exceptionally take the account above the normal 12-month prepaid cap because Ambient honors paid value.