Subscriptions
Subscriptions let you run services billed on a monthly or yearly cycle (SaaS plans, memberships, content access, etc.) entirely on Byl.
Mongolian payment systems have no automatic charging (auto-charge), so Byl subscriptions work on a request-to-pay basis: at the end of each billing period, the customer receives a reminder and renews their access by paying themselves. Byl handles reminders, expiry tracking, status transitions, and webhook notifications automatically.
Customers manage their subscriptions themselves in the billing portal — a page accessed via a temporary session link, with no Byl account or password required, where they can renew, change plans, cancel, and view their payment history.
How it works
Section titled “How it works”- In the dashboard, create a recurring price on your product (with a monthly or yearly cycle).
- When your customer wants to subscribe, you create a checkout with the recurring price.
- As soon as the checkout is paid, a subscription is created automatically and a
subscription.createdwebhook is sent. - Before the billing period ends, Byl emails the customer a reminder (with a portal link) and sends you a
subscription.renewal_duewebhook. - If the customer pays in the portal, the period is renewed (
subscription.renewed); if they don’t, the subscription is canceled once the period ends (subscription.canceled).
Statuses
Section titled “Statuses”| Status | Description |
|---|---|
trialing |
In trial. Becomes active if the customer pays when the trial ends. |
active |
Active. Access remains open until the end of the billing period. |
past_due |
Overdue (only on products with a grace period configured). |
canceled |
Canceled. The status at which the customer’s access should be revoked. |
Reminder schedule
Section titled “Reminder schedule”Depending on the billing cycle, reminder emails are sent to the customer on the following schedule, and you receive a subscription.renewal_due webhook each time:
- Monthly plans — 7 days and 1 day before expiry
- Yearly plans — 30 days, 7 days, and 1 day before expiry
Customers who have requested cancellation do not receive reminders.
Starting a subscription
Section titled “Starting a subscription”A subscription is created automatically when a checkout with a recurring price is paid. Compared to a regular checkout, two extra requirements apply:
customer_idis required — the subscription is registered to a specific customer- A single item — a checkout with a recurring price cannot contain any other items
Get the customer_id from the Create customer endpoint — when you pass a client_reference_id it behaves as an upsert, so calling it before every checkout will not create duplicates.
Example request
BYL_PROJECT_ID="your project ID"BYL_TOKEN="your API token"
curl -X POST https://byl.mn/api/v1/projects/$BYL_PROJECT_ID/checkouts \ -H "Authorization: Bearer $BYL_TOKEN" \ -H 'Accept: application/json' \ -H 'Content-Type: application/json' \ -d '{ "customer_id": 12, "success_url": "https://example.mn/subscribe/success", "items": [ { "price": "starter_monthly", "quantity": 1 } ] }'As soon as the checkout is paid, the subscription is created and a subscription.created webhook is sent. The customer receives an email with a portal link.
Renewals
Section titled “Renewals”Customers renew by clicking “Renew” and paying in the portal, so in most cases no extra work is required on your side. If you want to create a renewal checkout from your own system, pass the subscription_id parameter:
curl -X POST https://byl.mn/api/v1/projects/$BYL_PROJECT_ID/checkouts \ -H "Authorization: Bearer $BYL_TOKEN" \ -H 'Accept: application/json' \ -H 'Content-Type: application/json' \ -d '{ "customer_id": 12, "subscription_id": 4, "items": [ { "price": "starter_monthly", "quantity": 3 } ] }'- The renewal is added on top of the current expiry date — no remaining days are lost.
quantityspecifies how many billing periods to renew for (3 months in this example).
Changing plans
Section titled “Changing plans”Passing a different recurring price on a checkout with a subscription_id performs a plan change — the subscription moves to the new plan and a new billing period starts on the day of payment. You can even switch to a price on a different product (e.g. Starter → Growth).
The value of the remaining days in the current period becomes a credit and is automatically deducted from the new checkout’s total. If the credit is greater than or equal to the new plan’s price (e.g. downgrading from a yearly to a monthly plan), a mid-period change is not possible — the change happens when the period ends.
The portal’s “Change plan” section automatically shows the project’s other recurring prices, so you don’t need to build this flow yourself.
Trials
Section titled “Trials”A trial subscription starts without payment, so it is created directly via the API rather than through a checkout. This is the only way to create a subscription via the API — paid subscriptions always start from a checkout.
POST /v1/projects/:project_id/subscriptions| Parameter | Type | Required | Description |
|---|---|---|---|
customer_id |
Number | true | Customer ID. |
price |
String | true* | The recurring price’s lookup_key. |
price_id |
Number | true* | The recurring price’s ID — may be passed instead of price. |
trial_days |
Number | true | Trial length in days (1–365). |
* You must pass either price or price_id. If both are given, price_id takes precedence. The price must be recurring.
curl -X POST https://byl.mn/api/v1/projects/$BYL_PROJECT_ID/subscriptions \ -H "Authorization: Bearer $BYL_TOKEN" \ -H 'Accept: application/json' \ -H 'Content-Type: application/json' \ -d '{ "customer_id": 12, "price": "starter_monthly", "trial_days": 14 }'Before the trial ends, the customer receives the usual reminders; the subscription becomes active when they pay in the portal, and is canceled if they don’t.
Cancellation
Section titled “Cancellation”By default, cancellation takes effect at the end of the billing period:
- At the moment of cancellation, the status remains
activeand asubscription.updatedwebhook is sent (with thecanceled_atfield populated). - The customer’s access remains valid until the end of the period, but no further reminders are sent.
- Once the period ends, the status becomes
canceledand asubscription.canceledwebhook is sent — this is when you revoke access. - The cancellation can be reversed (resumed) before the period ends.
Pass mode: immediately to cancel right away: the status becomes canceled immediately and a subscription.canceled webhook is sent. This also works on an active subscription whose cancellation has already been requested (canceled_at populated). Immediate cancellation cannot be reversed.
Customers can cancel themselves in the portal, and you can also cancel via the API:
POST /v1/projects/:project_id/subscriptions/:subscription_id/cancelPOST /v1/projects/:project_id/subscriptions/:subscription_id/resume| Parameter | Type | Description |
|---|---|---|
mode |
String | at_period_end (default) — cancel at the period end; immediately — cancel and revoke access now. |
# Cancel at the period endcurl -X POST https://byl.mn/api/v1/projects/$BYL_PROJECT_ID/subscriptions/4/cancel \ -H "Authorization: Bearer $BYL_TOKEN" \ -H 'Accept: application/json'
# Cancel immediately (access is revoked right away)curl -X POST https://byl.mn/api/v1/projects/$BYL_PROJECT_ID/subscriptions/4/cancel \ -H "Authorization: Bearer $BYL_TOKEN" \ -H 'Accept: application/json' \ -H 'Content-Type: application/json' \ -d '{"mode": "immediately"}'
# Resume (undo cancellation)curl -X POST https://byl.mn/api/v1/projects/$BYL_PROJECT_ID/subscriptions/4/resume \ -H "Authorization: Bearer $BYL_TOKEN" \ -H 'Accept: application/json'These requests return 409 Conflict on a subscription that has already been fully canceled.
Portal
Section titled “Portal”Customers manage their subscriptions themselves in the billing portal: renew, change plans, cancel/resume, resubscribe, and view their payment history. Whenever a subscription is created, a reminder is due, or a subscription is canceled, Byl automatically emails the customer a branded message with a portal link, so in most cases no extra work is required on your side.
To give customers access from inside your own application (a “My subscription” menu, for example), create a temporary session link with POST /billing-portal/sessions and redirect them to it.
The Subscription object
Section titled “The Subscription object”| Field | Type | Description |
|---|---|---|
id |
Number | Subscription ID. |
status |
String | Status: trialing, active, past_due, canceled. |
customer_id |
Number | Customer ID. |
product_id |
Number | Product ID. |
price_id |
Number | Price ID (changes on plan change). |
current_period_start |
Date | Start of the current billing period. |
current_period_end |
Date | End of the current billing period (when access expires). |
trial_ends_at |
Date | When the trial ends (null if there is no trial). |
canceled_at |
Date | When cancellation was requested (null if not canceled). |
is_test |
Boolean | Whether the subscription was created in test mode. |
customer |
Object | Customer details (included in retrieve requests). |
price |
Object | Price and product details (included in retrieve requests). |
List subscriptions
Section titled “List subscriptions”GET /v1/projects/:project_id/subscriptionsReturns 25 records per page; fetch the next page with ?page=2. Filter with the following parameters:
| Parameter | Description |
|---|---|
customer_id |
Subscriptions belonging to a specific customer. |
price_id |
Subscriptions on a specific price. |
price |
Filter by a price’s lookup_key. |
status |
trialing, active, past_due, canceled. |
curl -X GET "https://byl.mn/api/v1/projects/$BYL_PROJECT_ID/subscriptions?customer_id=12&status=active" \ -H "Authorization: Bearer $BYL_TOKEN" \ -H 'Accept: application/json'Retrieve a subscription
Section titled “Retrieve a subscription”GET /v1/projects/:project_id/subscriptions/:subscription_idExample response
{ "data": { "id": 4, "status": "active", "project_id": 1, "customer_id": 12, "product_id": 7, "price_id": 3, "current_period_start": "2026-07-23T04:10:00.000000Z", "current_period_end": "2026-08-23T15:59:59.000000Z", "trial_ends_at": null, "canceled_at": null, "is_test": false, "customer": { "id": 12, "name": "Бат-Эрдэнэ", "phone": "99999999", "client_reference_id": "user_842" }, "price": { "id": 3, "type": "recurring", "unit_amount": 30000, "recurring_interval": "month", "recurring_interval_count": 1, "lookup_key": "starter_monthly", "product": { "id": 7, "name": "Starter багц", "client_reference_id": null } }, "created_at": "2026-06-23T04:10:00.000000Z", "updated_at": "2026-07-23T04:10:00.000000Z" }}Checking whether a customer has access
Section titled “Checking whether a customer has access”The simplest way to answer “is this user subscribed?” — retrieving a customer includes their entitled subscriptions:
GET /v1/projects/:project_id/customers/:customer_idGET /v1/projects/:project_id/customers/by-client-reference-id/:client_reference_idThe data.subscriptions array contains only entitled subscriptions (trialing, active, past_due).
Webhook events
Section titled “Webhook events”| Event | When it is sent |
|---|---|
subscription.created |
A new subscription was created (a checkout was paid or a trial started). |
subscription.renewed |
The billing period was successfully renewed. |
subscription.updated |
The plan was changed, cancellation was requested, or a cancellation was reversed. |
subscription.renewal_due |
A renewal reminder was sent. |
subscription.past_due |
The subscription is overdue (only on products with a grace period configured). |
subscription.canceled |
The subscription was fully canceled — revoke the customer’s access at this point. |
Each event’s data.object contains the subscription object (with customer, product, and price details). Read about webhook configuration and signature verification on the Webhook page.
Next steps
Section titled “Next steps”- Billing portal — the self-service page where customers manage their subscriptions
- Checkout — create a checkout to start a subscription
- Webhook — receive subscription events in your system