Skip to content

Billing API

These client-facing endpoints manage account plans, wallet funds, project add-ons, coupons, referrals, and renewal settings. Billing-provider callbacks are not part of the public API.

GET /api/billing/overview

Returns the current account billing overview.

GET /api/billing/wallet/overview

Returns the account balance and wallet overview.

POST /api/billing/wallet/crypto-topup

Creates a crypto balance top-up. When returnUrl or cancelUrl is supplied, the API sets its externalReference query parameter to the exact reference of the created top-up, replacing any previous value. Preserve that reference across the provider redirect.

POST /api/billing/wallet/topup/refresh

Refreshes the state of the top-up identified by topupId or externalReference. Use an identifier returned by the create response or the redirect URL; do not infer the target from the most recent top-up. A return page must not request a refresh when the identifier is missing, repeated, or conflicts with another identifier.

POST /api/billing/account-plan/balance-purchase

Purchases or upgrades an account plan using the wallet balance.

Request body:

FieldRequiredDescription
planIdYesTarget plan: advanced or pro.
durationMonthsYesBilling period: 1, 3, 6, or 12 months.
autoRenewNoWhether the resulting subscription renews automatically. Defaults to false when omitted.
requestIdNoIdempotency key containing 8-128 RFC 3986 unreserved characters: A-Z, a-z, 0-9, ., _, ~, and -. It may be omitted for backward compatibility, but is recommended.

Use one stable requestId for one confirmed user intent. If a timeout or another unknown result occurs, retry with the same payload and requestId. An exact replay returns the original subscriptionId and invoiceId without a second wallet debit. Reusing that requestId with a different planId, durationMonths, or autoRenew is rejected. Generate a new requestId for a new purchase intent. Without requestId, the request remains accepted for backward compatibility, but separate HTTP attempts are neither retry-safe nor idempotent.

Idempotency caseRequired behavior
same-intentSend the same-requestId with the same-payload.
exact-replayReturn the original-subscriptionId and original-invoiceId with no-second-debit.
conflicting-payloadThe request is rejected.
new-intentGenerate a new-requestId.
missing-requestIdRemains backward-compatible, but is not-retry-safe and not-idempotent-across-HTTP-attempts.

Response: HTTP 200 OK.

FieldRequiredDescription
subscriptionIdYesIdentifier of the activated subscription.
invoiceIdYesIdentifier of the paid invoice.

POST /api/billing/account-plan/checkout

Creates a checkout for an account plan.

POST /api/billing/account-plan/crypto-checkout

Creates a direct crypto checkout for an account plan.

POST /api/billing/project-addon/balance-purchase

Purchases a project free-access add-on using the wallet balance.

Request body:

FieldRequiredDescription
projectFullnameYesFully qualified name of the project receiving the add-on.
addonCodeYesAdd-on identifier: project-free-access.
durationMonthsYesBilling period: 1, 3, 6, or 12 months.
autoRenewNoWhether the resulting subscription renews automatically. Defaults to false when omitted.
requestIdNoIdempotency key containing 8-128 RFC 3986 unreserved characters: A-Z, a-z, 0-9, ., _, ~, and -. It may be omitted for backward compatibility, but is recommended.

Use one stable requestId for one confirmed user intent. If a timeout or another unknown result occurs, retry with the same requestId and the same full payload, including projectFullname, addonCode, durationMonths, and autoRenew. An exact replay returns the original subscriptionId and invoiceId without a second wallet debit. Reusing that requestId after changing any of those payload fields is rejected. Generate a new requestId for a new purchase intent. Without requestId, the request remains accepted for backward compatibility, but separate HTTP attempts are neither retry-safe nor idempotent.

Idempotency caseRequired behavior
same-intentSend the same-requestId with the same-full-payload: projectFullname, addonCode, durationMonths, and autoRenew.
exact-replayReturn the original-subscriptionId and original-invoiceId with no-second-debit.
conflicting-payloadChanging projectFullname, addonCode, durationMonths, or autoRenew is rejected.
new-intentGenerate a new-requestId.
missing-requestIdRemains backward-compatible, but is not-retry-safe and not-idempotent-across-HTTP-attempts.

Response: HTTP 200 OK.

FieldRequiredDescription
subscriptionIdYesIdentifier of the activated project add-on subscription.
invoiceIdYesIdentifier of the paid invoice.

POST /api/billing/project-addon/checkout

Creates a checkout for a project add-on.

When the matching hosted checkout is still pending, a retry returns the existing subscriptionId, invoiceId, and externalReference, together with a newly signed checkout form. Submit the latest returned form.

Checkout caseRequired behavior
pending-hosted-retryReturn the existing subscriptionId, invoiceId, and externalReference with a fresh signed checkout form.

POST /api/billing/project-addon/crypto-checkout

Creates a direct crypto checkout for a project add-on.

If a pending checkout is already linked to a provider order, retrying returns the same providerOrderId and paymentUrl. If provider order creation or linkage has an ambiguous result, the checkout remains pending and fails closed: do not start another checkout until the pending one is canceled or reconciled.

Checkout caseRequired behavior
pending-linked-order-retryReturn the same providerOrderId and paymentUrl.
ambiguous-provider-creation-or-linkageKeep fail-closed-pending; require cancel-or-reconcile-before-new-attempt.

POST /api/billing/coupon/redeem

Redeems a coupon for a paid account plan.

Request body:

FieldRequiredDescription
codeYesCoupon code. Leading and trailing whitespace is removed (trim), and matching is case-insensitive.

The coupon's paid account-plan period is applied once. Depending on the current paid plan period, redemption extends it, overlays the applicable period, or starts a new term.

Redemption caseRequired behavior
code-normalizationApply trim and case-insensitive matching.
same-account-retryReturn same-couponId, same-subscriptionId, and same-invoiceId with no-second-application.
unavailable-or-unsafeA coupon that is redeemed-by-another-account, deleted, invalid, or has an unsafe-subscription-lineage must fail-closed.
paid-account-plan-periodextend, overlay, or start a new-term, as-applicable.

Response: HTTP 200 OK.

FieldRequiredDescription
couponIdYesIdentifier of the redeemed coupon.
subscriptionIdYesIdentifier of the resulting account-plan subscription.
invoiceIdYesIdentifier of the paid coupon invoice.

POST /api/billing/coupon/gift-purchase

Purchases a gift coupon using the wallet balance.

Request body:

FieldRequiredDescription
planIdYesPaid account plan: advanced or pro.
durationMonthsYesCoupon duration: 1, 3, 6, or 12 months.
requestIdNoIdempotency key containing 8-128 RFC 3986 unreserved characters: A-Z, a-z, 0-9, ., _, ~, and -. It may be omitted for backward compatibility, but is recommended.

Within the same account, use one stable requestId for one confirmed gift purchase. If a timeout or another unknown result occurs, retry with the same plan and duration.

Purchase caseRequired behavior
same-intentSend the same-requestId, same-planId, and same-durationMonths.
exact-replayReturn the same-couponId and same-code with no-second-debit and no-second-referral-reward.
conflicting-payloadReusing the request identifier with a different plan or duration is rejected.
new-intentGenerate a new-requestId.
missing-requestIdRemains backward-compatible, but is not-retry-safe and not-idempotent-across-HTTP-attempts.

Response: HTTP 200 OK.

FieldRequiredDescription
couponIdYesIdentifier of the purchased gift coupon.
codeYesCode to give to the coupon recipient.

GET /api/billing/referral/overview

Returns the referral balance and link summary.

POST /api/billing/referral/link/create

Creates a referral link.

POST /api/billing/referral/claim

Claims a referral code.

POST /api/billing/subscription/update-renewal

Updates automatic billing renewal.

POST /api/billing/crypto-checkout/refresh

Refreshes the state of a crypto checkout.

For a project add-on, a late payment after checkout cancellation or project transfer does not reactivate the subscription or project access. The payment is handled through reconciliation or refund instead.

Lifecycle caseRequired behavior
late-payment-after-cancel-or-project-transferPreserve no-reactivation; use reconciliation-or-refund.

POST /api/billing/crypto-checkout/cancel

Cancels a crypto checkout.

Cancellation is terminal for project add-on activation. If payment is observed later, including after the project was transferred, it does not reactivate the subscription or project access and is handled through reconciliation or refund.

Lifecycle caseRequired behavior
late-payment-after-cancel-or-project-transferPreserve no-reactivation; use reconciliation-or-refund.