curl --request GET \
--url https://api.botsubscription.com/v1/endpoint \
--header 'Authorization: Bearer sk_live_•••'Create a Subscription Bot Payment Request
Create a plan checkout for a Telegram subscription bot or Discord subscription bot. An eligible trial can complete immediately without a payment link. Check the returned status before deciding what the customer should do next.
Keep the project API key on your server. Do not expose it in browser code.
Authorization
API key scopes: payment-requests:add and payment-history:read (required for restricted keys). Applying a coupon also requires coupons:read.
Response dates use the UTC ISO format, including the manual-trial marker described below.
Supply the customer's user_id explicitly. Project API keys cannot debit the customer's wallet or charge a stored card or subscription instrument. Sending payment_method_id returns 403.
Retries: Idempotency-Key binds the request to your intent, while this endpoint owns duplicate-submit recovery. Successful rich responses are normally recoverable for ten minutes; the shared 24-hour response replay does not apply.
Handle the returned status
Your website backend creates a Payment Request for user_id
-> completed: show the receipt; no redirect is needed
-> pending with a positive amount and payment_url: open hosted checkout
-> another status: handle that state or the provider's payment instructionsAn HTTP success does not necessarily mean a charge occurred. A completed, zero-price trial receipt can have status: "completed", amount: "0" and no payment_url.
Create, resume and cancel responses use a payment-operation payload. It includes payment_request_id, request_type, amount, currency, status, provider and a required nullable merchant_account_id. provider_payment_id, failure_reason and payment_error_code may be omitted or null. payment_url may be omitted or null; terminal responses omit live checkout URLs. payment_request_data contains provider or receipt metadata.
These operation responses omit user_id, created_at and settled_at. Use Get Payment Request for the stored receipt's customer, plan and timestamps. Optional boolean reused identifies a reused checkout; optional boolean superseded identifies whether this checkout replaced an earlier one.
For hosted checkout, a browser redirect is a navigation step, not proof of payment. Treat a verified payment-request.completed webhook or the status returned by Get Payment Request as authoritative.
Automatic trials for named members
The backend decides trial eligibility from the configured plan and the customer's history. There is no request switch to force, bypass or decline an eligible trial.
For an eligible recurring-plan trial, a project API key can grant access to the named user_id without customer sign-in. This is a manual trial: no stored-card charge, wallet debit or automatic renewal. The member needs a new payment to continue after the trial ends.
Use merchant_account_id for configured checkout. An active, permitted external merchant account supporting the receipt currency is required even for a zero-price manual trial. For eligible recurring-plan trials only, provider: "internal" can also complete as a manual zero-price grant; it gives the key no authority to spend the customer's wallet. Missing or unverifiable setup is refused.
For a nonrecurring trial plan, send merchant_account_id. An eligible customer can receive the configured zero-cost trial, such as seven days, without signing in, a wallet debit, stored-card use or a subscription. For these plans, provider: "internal" remains a wallet-funding input and returns 403 FORBIDDEN, even when the customer qualifies for a trial.
Coupons cannot be combined with an eligible recurring-plan trial. Remove the coupon and retry the corrected request; the backend does not silently charge instead. Unverifiable eligibility or receipt recovery is also refused.
Where returned, payment_request_data.manual_trial.starts_at and ends_at are UTC ISO strings with milliseconds, for example "2030-01-01T00:00:00.000Z". Nonrecurring trial receipts need not include this marker. Existing or ineligible members follow normal paid-plan eligibility checks. Trials do not refund or convert existing memberships.
Open checkouts and retries
Creation also has a per-customer limit of five requests per ten-minute window, plus a project-key actor limit of sixty per ten-minute window. These checks apply in addition to HTTP route limits and can return 429 TOO_MANY_REQUESTS with error.retry_after_seconds. Wait for that delay before retrying the same request and idempotency key; creating another plan does not reset the customer's limit.
An existing payable checkout can block a new trial with 409 SUBSCRIPTION_IN_PROGRESS. Follow its cancel-or-expire guidance: use Cancel Checkout with the saved payment_request_id, or wait for provider expiry. Cancellation requires payment-requests:update for restricted keys, is provider-dependent and can itself return 409.
The same error code can also describe ordinary work in progress. With error.reason: "PREPARING", wait error.retry_after_seconds, then retry with the same key and unchanged body. A response saying that payment status is being confirmed also calls for retry recovery, rather than cancellation solely because it is a 409.
A zero-price receipt that is genuinely still pending also returns 409 while unresolved. Wait and retry unchanged; do not present it as completed or redirect it to payment. If a stored receipt is completed but its access grant is still owed, retry the original request with the same key and body so the server can finish recovery of that receipt. Do not start a new purchase to repair it.
Once a failed checkout is confirmed to be dead, the server releases its stale cache before the next trial attempt. There is no need to wait out the old cache's full lifetime. If correcting a rejected request, such as removing a coupon, use a new idempotency key for the changed request.
Use a custom return hostname when needed
You do not need to configure custom_hostname to create a Payment Request. No custom hostname setup is required when you omit success_url and cancel_url or use the default return hostname provided by BotSubscription.
Configure custom_hostname only when a supplied return URL should use your own hostname. See Configure Custom Hostname for the request, API key access requirements, value format, and possible conflicts.
Return URL contract
success_url and cancel_url are checked independently. For each field you provide:
- The complete JSON value must be an
https://URL. - Its origin must be trusted for the project. Use the default return origin provided by BotSubscription or the exact configured
custom_hostname. For example, a configured value ofexample.comdoes not authorizewww.example.comorsubdomain.example.com. - Its path and query string may vary within that trusted origin.
- Validation uses the
success_urlandcancel_urlJSON fields. HTTPOriginandRefererheaders do not authorize a return URL.
If either supplied URL is malformed or uses an unauthorized hostname, the API rejects the request with 422. It does not treat the current Origin, Referer, CORS configuration, or caller location as a substitute.
Request
Path Parameters
project_idstringrequiredThe unique identifier of the project.
Request Body
user_idstringrequiredCustomer's platform ID as a string, or a linked customer-account UUID that resolves to this project's platform. A platform ID can identify a customer without an existing project record.
plan_idstringrequiredPlan UUID. Its configuration and the customer's history determine the authoritative price, duration and trial eligibility.
merchant_account_idstringExternal merchant account UUID, required for hosted checkout and nonrecurring trial plans. A recurring-plan manual trial also needs an active, permitted external account supporting the receipt currency.
amountstring|numberOmit for plan checkout. The backend determines the price; this field cannot override it or select a trial. Supplied values still undergo decimal range and numeric precision validation.
coupon_idstringOptional coupon UUID, applied against the authoritative plan price. Rejected when combined with an eligible recurring-plan trial; remove it and retry.
pay_currencystringCrypto payment currency advertised by the selected merchant's enabled requirements. Uppercase alphanumeric, 3 to 9 characters; use the returned value, which may include a network suffix.
pay_networkstringOptional crypto network selector. Surrounding whitespace is trimmed; the value must contain 2 to 10 ASCII letters or digits and be allowed by the selected merchant.
providerstringOmit for configured checkout; merchant_account_id selects the gateway. The internal value returns 403 for nonrecurring trial plans, even when eligible. Only an eligible recurring-plan trial may complete through it as a zero-price manual grant; no wallet debit is authorized. Other values are ignored.
success_urlstringOptional complete HTTPS URL for a successful return. Its origin must be trusted for the project; paths and queries may vary. Validation uses this JSON field, not Origin or Referer.
cancel_urlstringOptional complete HTTPS URL for a cancelled return. Its origin must be trusted for the project; paths and queries may vary. Validation uses this JSON field, not Origin or Referer.
For project-key creation, omit "provider": "stripe". The selected merchant_account_id determines that Stripe is the payment gateway.
Crypto currency and network selection
Read the selected merchant with ?expand=requirements.enabled using Get Provider Details before selecting a crypto payment currency. Use its advertised pay_currency values, which can include a compound value such as USDTTRC20. There is no universal network enum shared by all merchants.
Prefer the advertised compound value unchanged, for example {"pay_currency":"USDTTRC20"}, and omit pay_network. A supported split selection can use {"pay_currency":"USDT","pay_network":"TRC20"}. A recognized currency's embedded network takes precedence over a separate network selector. Without an explicit network, selection may use one of the merchant's enabled networks; there is no fixed default.
Both selectors are optional, non-nullable strings in the general request schema, but the merchant's requirements still apply. NOWPayments requires a payment currency to complete a crypto checkout.
Invalid selector syntax returns 422 VALIDATION_ERROR. A network that is not allowed by the merchant can return 422 MERCHANT_NETWORK_NOT_ALLOWED. These selectors choose the payment route; they do not authorize a wallet debit or stored-payment-method charge.
Platform user and community behavior
A user_id may identify a platform user the project has not seen before. For Discord, you may supply that user's snowflake. The API can materialize the local user automatically, so the customer does not need to be in the corresponding Discord guild or Telegram community while paying.
Payment does not join the customer to a Discord guild, Telegram group, or Telegram channel. If the same platform account joins later, BotSubscription restores applicable access while the membership remains valid and the bot has the required permissions. For Discord, this includes restoring active membership roles.
Confirm the outcome authoritatively
For hosted checkout, do not grant access from the success_url redirect alone. Customers can revisit or copy redirect URLs, and browser navigation can occur before asynchronous payment processing finishes.
Use a verified completion webhook or fetch the Payment Request until it reaches an authoritative status. The webhook signature guide explains how to verify server-to-server events. A completed zero-price receipt can represent a free plan, fully discounted checkout or trial. If activation is still owed, use the same-request recovery described above.