Subscription Plans

Charge your app's users a recurring price through Hypery and give each payment credit grants that can only be spent in your app — price and grants configured independently.

Overview

Subscription plans let your app sell a recurring subscription (a monthly price, plus an optional annual price) and give subscribers credit grants to spend on AI inside your app.

  • You're the merchant of record. Payments go to your Stripe Connect account. Hypery takes its platform fee.
  • Price and grants are separate. A plan can charge $10/month and grant $5, $25, several grants of different types, or nothing at all.
  • Grants stay in your app. A grant can only pay for requests made through your app. Other apps, and the user's own direct API usage, can't spend it.
  • Grants expire and never roll over. Subscribers get fresh grants every month, and each grant expires after the number of days you set.
  • Annual billing, monthly grants. Annual subscribers pay once a year but still receive the plan's grants every month, so credits never land all at once.
  • Usage keeps working when a grant runs out. Once a grant is used up or expired, the user's usage bills the normal way (their balance or card).
  • No re-entering a card. Subscribing uses a card the user already gave Hypery: their wallet card, or the card they added when they set up billing.

Authentication:

  • Plan management: your personal API key (ak_...) or a session/OAuth token with write (read for GET). Only the app's owner or a billing admin can manage plans.
  • Subscribing: an OAuth token for your app with the billing:charge scope.

Plans require Stripe Connect onboarding to be complete for the app (chargesEnabled). Until then, creating a plan returns 409 MARKETPLACE_NOT_ONBOARDED.


Grant types

Every grant on a plan is one of two types:

TypeWhat the subscriber receivesCovers
hyperyHypery credits held on the grant itself (100 credits = $1). Requests from your app draw from it before the user's balance.Chat completions (streaming and non-streaming), OpenAI-compatible media endpoints, and /api/v1/usage/charge.
stripeA Stripe Billing credit grant on a Stripe customer dedicated to this user and your app.Metered AI usage billed through Vercel AI Gateway on /api/v1/chat/completions.

How each type is spent:

  • Hypery grants:
    • A request is paid from the grant first; anything left over is charged to the user's balance.
    • A request normally billed through Vercel AI Gateway is routed through the credit ledger instead, but only when the grant covers the whole request's estimate.
    • Hypery grants don't cover /v1/ai (the Vercel AI Gateway protocol endpoint) or predictions.
  • Stripe grants:
    • While the grant has value left, your app's metered usage is billed to the dedicated customer, where the credit grant offsets it.
    • Once it's spent, usage goes back to the user's regular Stripe customer.

Funding: you pay for every grant.

  • When a grant is issued, its USD value is debited from your team's Hypery balance.
  • If the balance can't cover it, the amount is added to your team's Stripe invoice instead.
  • If neither works, the grant is marked funding_failed (not spendable) and retried automatically.

Plans

List plans

Endpoint: GET /api/apps/{appId}/plans

curl https://hypery.ai/api/apps/{appId}/plans \
  -H "Authorization: Bearer ak_your_key"
{
  "success": true,
  "data": {
    "connectReady": true,
    "plans": [
      {
        "_id": "66f…",
        "name": "Pro",
        "status": "active",
        "priceCents": 1000,
        "interval": "month",
        "prices": [
          { "interval": "month", "priceCents": 1000, "stripePriceId": "price_…", "active": true },
          { "interval": "year", "priceCents": 10000, "stripePriceId": "price_…", "active": true }
        ],
        "grants": [
          { "id": "g_1a2b…", "type": "hypery", "amountUsd": 10, "expiresAfterDays": 31, "issueOn": "each_payment" }
        ],
        "activeSubscribers": 42,
        "economics": {
          "priceUsd": 10, "monthlyPriceUsd": 10, "annualPriceUsd": 100, "annualEffectiveMonthlyUsd": 8.33,
          "annualSavingsPct": 17, "recurringGrantUsd": 10, "firstPaymentGrantUsd": 10, "grantsExceedPrice": true
        }
      }
    ]
  }
}

Create a plan

Endpoint: POST /api/apps/{appId}/plans

curl -X POST https://hypery.ai/api/apps/{appId}/plans \
  -H "Authorization: Bearer ak_your_key" \
  -H "Content-Type: application/json" \
  -d '{
    "name": "Pro",
    "monthlyPriceCents": 1000,
    "annualPriceCents": 10000,
    "grants": [
      { "type": "hypery", "amountUsd": 10, "expiresAfterDays": 31, "issueOn": "each_payment" },
      { "type": "hypery", "amountUsd": 5,  "expiresAfterDays": 7,  "issueOn": "first_payment_only" }
    ]
  }'
FieldTypeNotes
namestring1–120 chars.
descriptionstring?Up to 1000 chars.
monthlyPriceCentsintegerRequired. Minimum 50 ($0.50).
annualPriceCentsinteger?Optional annual price. Minimum 50.
priceCents + intervallegacyStill accepted: a single price billed every interval.
grants[]array0–10 grants.
grants[].typehypery | stripeSee Grant types.
grants[].amountUsdnumberGreater than 0. Set per grant; not tied to the price.
grants[].expiresAfterDaysinteger1–366, counted from when the grant is issued.
grants[].issueOneach_payment | first_payment_onlyDefaults to each_payment (every month). first_payment_only is issued in the subscription's first month only.

plans[].priceCents / stripePriceId / interval mirror the monthly price, for older clients; read prices[] for both.

Update a plan

Endpoint: PATCH /api/apps/{appId}/plans/{planId}

All fields are optional: name, description, monthlyPriceCents, annualPriceCents, grants, status (active / archived).

  • Price changes create a new Stripe price. Existing subscribers on that interval move to it at their next renewal, with no proration.
  • annualPriceCents: null removes the annual option for new subscribers. Existing annual subscribers keep renewing until they cancel or switch.
  • monthlyPriceCents: null (only when an annual price exists) makes the plan annual-only. Existing monthly subscribers keep renewing; clients that don't send interval get 400 INTERVAL_NOT_OFFERED, so update them to send "year".
  • Grant changes apply from the next grant issued (including the next monthly grant of annual subscribers). Grants already issued keep their original amount and expiry.
    • To keep an existing grant, send its id back.
    • A grant without an id is treated as new.
    • A grant you leave out is removed.
  • Archiving hides the plan from new subscribers. Existing subscriptions keep renewing until they're canceled.

Plan details

Endpoint: GET /api/apps/{appId}/plans/{planId}

Returns the plan, its economics, and grant totals grouped by status: grants issued, credits issued, used, and remaining.


Subscribing (from your app)

With @hyperyai/sdk, useAppSubscription(appId) and <SubscribeButton planId interval> handle the whole flow: sign-in, subscribe, and card entry only if the user has no card on Hypery. The endpoints below are what they call.

Show available plans

Endpoint: GET /api/marketplace/plans (scope read)

With an OAuth token, this returns the plans of the app the token belongs to. Session or API-key callers pass ?appId={appId}.

{
  "plans": [
    {
      "id": "66f…",
      "appId": "66e…",
      "name": "Pro",
      "priceCents": 1000,
      "currency": "usd",
      "interval": "month",
      "prices": [{ "interval": "month", "priceCents": 1000 }, { "interval": "year", "priceCents": 10000 }],
      "grants": [{ "type": "hypery", "amountUsd": 10, "expiresAfterDays": 31, "issueOn": "each_payment" }]
    }
  ]
}

Subscribe

Endpoint: POST /api/marketplace/subscribe (scope billing:charge)

curl -X POST https://hypery.ai/api/marketplace/subscribe \
  -H "Authorization: Bearer <user OAuth token>" \
  -H "Content-Type: application/json" \
  -d '{ "planId": "66f…", "interval": "year", "idempotencyKey": "sub-7f3c9a1e" }'
FieldNotes
planIdAn active plan of the app the token belongs to.
intervalmonth (default) or year. 400 INTERVAL_NOT_OFFERED if the plan has no such price.
paymentMethodIdOptional. Must be a card the user already has on Hypery.
idempotencyKeyOptional but recommended. Use a stable id for each subscribe action.

Responses:

StatusMeaning
201Subscribed. The first month's grants are already issued.
200alreadySubscribed: true. The user already has a live subscription to this plan.
402 PAYMENT_METHOD_REQUIREDNo card on file. Send the user through the add-card flow, then retry.
402 PAYMENT_INCOMPLETEThe card needs authentication (3-D Secure). Confirm error.clientSecret with Stripe.js initialized with stripeAccount: error.stripeAccount.
402 PAYMENT_DECLINEDThe card was declined. message is safe to show the user.
403 PERMISSION_DENIEDThe token belongs to a different app.
409 MARKETPLACE_NOT_ONBOARDEDThe app can't accept payments yet.
409 SUBSCRIPTION_IN_PROGRESSAnother subscribe for the same user and plan is still running. Retry shortly.

The grants go to the user's billing team: their personal team, or the organization selected with x-hypery-active-organization-id.

Status and remaining grants

Endpoint: GET /api/marketplace/subscriptions?appId={appId} (scope read)

{
  "subscriptions": [
    {
      "id": "670…",
      "appId": "66e…",
      "plan": { "id": "66f…", "name": "Pro", "priceCents": 1000, "interval": "month", "grants": [] },
      "status": "active",
      "cancelAtPeriodEnd": false,
      "interval": "year",
      "pendingInterval": null,
      "currentPeriodEnd": "2027-09-15T12:00:00.000Z",
      "nextGrantAt": "2026-10-15T12:00:00.000Z",
      "grants": [
        { "type": "hypery", "amountUsd": 10, "remainingUsd": 6.42, "expiresAt": "2026-10-15T12:00:00.000Z" }
      ]
    }
  ]
}

Cancel and resume

  • POST /api/marketplace/subscriptions/{id}/cancel: stops renewal at the end of the current period. Grants already issued stay usable until they expire.
  • POST /api/marketplace/subscriptions/{id}/resume: undoes a pending cancellation.

Switch between monthly and annual

Endpoint: POST /api/marketplace/subscriptions/{id}/interval with { "interval": "year" | "month" }

  • Monthly → annual happens immediately. The unused part of the current month is credited, the year is charged now, and a new yearly period starts. The current month's grants aren't issued twice. A declined card returns 402 PAYMENT_DECLINED and nothing changes.
  • Annual → monthly happens at the end of the paid year (effective: "period_end", pendingInterval: "month"). Monthly grants continue until then. Calling it again with year keeps annual billing.
  • Switching to annual is refused on an archived plan (409 PLAN_ARCHIVED).

All three require billing:charge, and an OAuth token can only manage subscriptions for its own app.


Lifecycle

EventResult
Monthly invoice paid (first payment or renewal)One grant is issued for each plan grant that applies. This is idempotent per invoice and grant.
Annual invoice paidThe first month's grants are issued immediately; one more set is issued each calendar month of the paid year (checked hourly, catches up if delayed). Each month is funded when it's issued.
Payment failedThe subscription becomes past_due and no grants are issued.
Subscription set to cancel at period endNo further renewals. Annual subscribers keep receiving monthly grants until the paid year ends.
Subscription canceled immediatelyNo further renewals or monthly grants. Issued grants run until they expire.
Full refund or dispute on an invoiceThat invoice's unspent grants are revoked. For an annual invoice, the remaining months are not issued.
Grant expiresIts remaining value is no longer spendable (swept hourly).
User changes their default cardLive subscriptions switch to the new card automatically.