API reference
Prices
Set how much a product costs and how often it renews.
A price sets how much a product costs and how often it renews. A product can have several prices, like a monthly and a yearly one.
The price object
Attributes
idstringUnique identifier for the price.product_idstringThe product the price belongs to.namestringThe price's name, like Monthly.currencystringXAForXOF.amountintegerWhat the customer pays each billing period, in the currency's smallest unit.intervalstringThe unit of a billing period:day,week,month, oryear.interval_countintegerHow many intervals make up one billing period.monthwith3bills every three months.created_attimestampWhen the price was created, in Unix seconds.archived_attimestampnullableWhen the price was archived. An archived price starts no new subscriptions.pending_changeobjectnullableThe amount change scheduled for the price, or null when none is. A price has at most one.Show child attributesHide child attributes
idstringUnique identifier for the change. Price change events carry it as price_change_id.amountintegerThe new amount, in the currency's smallest unit.previous_amountintegerThe amount before the change.currencystringThe price's currency,XAForXOF.effective_attimestampThe start of the day the new amount applies from, in your app's time zone.keep_current_subscribersbooleantruewhen everyone subscribed on that day keeps the old amount.created_attimestampWhen the change was scheduled.
{
"id": "01J9ZQ5N1P2Q3R4S5T6V7W8XYZ",
"product_id": "01J9ZQ5A2B3C4D5E6F7G8H9JKM",
"name": "Monthly",
"currency": "XAF",
"amount": 5000,
"interval": "month",
"interval_count": 1,
"created_at": 1727600000,
"archived_at": null,
"pending_change": null
}Create a price
POST/v1/apps/{appID}/prices
Creates a price for one of the app's products.
Parameters
product_idstringrequiredThe product to price.namestringrequiredThe price's name, like Monthly.currencystringrequiredXAForXOF. Lowercase works too.amountintegerrequiredWhat the customer pays each billing period, in the currency's smallest unit. Must be greater than 0.intervalstringrequiredday,week,month, oryear.interval_countintegerHow many intervals make up one billing period. Defaults to1. At most 365 days, 52 weeks, 12 months, or 1 year.
Returns the price with status 201. An invalid field returns 422 with code invalid. If the app has no product with that product_id, returns 404 with code not_found.
curl https://api.abuna.app/v1/apps/01J9ZQ4Y7R3T6V8W2X5B1C0DEF/prices \
-H "Authorization: Bearer sk_test_..." \
-H "Content-Type: application/json" \
-d '{"product_id": "01J9ZQ5A2B3C4D5E6F7G8H9JKM", "name": "Monthly", "currency": "XAF", "amount": 5000, "interval": "month"}'{
"id": "01J9ZQ5N1P2Q3R4S5T6V7W8XYZ",
"product_id": "01J9ZQ5A2B3C4D5E6F7G8H9JKM",
"name": "Monthly",
"currency": "XAF",
"amount": 5000,
"interval": "month",
"interval_count": 1,
"created_at": 1727600000,
"archived_at": null,
"pending_change": null
}List prices
GET/v1/apps/{appID}/prices
Lists every price in the app, across all products and including archived ones, newest first.
Returns an array of price objects. It isn't paginated.
curl https://api.abuna.app/v1/apps/01J9ZQ4Y7R3T6V8W2X5B1C0DEF/prices \
-H "Authorization: Bearer sk_test_..."[
{
"id": "01J9ZQ5N1P2Q3R4S5T6V7W8XYZ",
"product_id": "01J9ZQ5A2B3C4D5E6F7G8H9JKM",
"name": "Monthly",
"currency": "XAF",
"amount": 5000,
"interval": "month",
"interval_count": 1,
"created_at": 1727600000,
"archived_at": null,
"pending_change": null
}
]Update a price
PATCH/v1/apps/{appID}/prices/{id}
Renames the price. Its amount, currency, billing period, and checkout link stay the same. To change what customers pay, schedule a price change.
Parameters
namestringrequiredThe new name, like Monthly plan.
Returns the price. A blank name returns 422 with code invalid. If the app has no price with that ID, returns 404 with code not_found.
curl https://api.abuna.app/v1/apps/01J9ZQ4Y7R3T6V8W2X5B1C0DEF/prices/01J9ZQ5N1P2Q3R4S5T6V7W8XYZ \
-X PATCH \
-H "Authorization: Bearer sk_test_..." \
-H "Content-Type: application/json" \
-d '{"name": "Monthly plan"}'{
"id": "01J9ZQ5N1P2Q3R4S5T6V7W8XYZ",
"product_id": "01J9ZQ5A2B3C4D5E6F7G8H9JKM",
"name": "Monthly plan",
"currency": "XAF",
"amount": 5000,
"interval": "month",
"interval_count": 1,
"created_at": 1727600000,
"archived_at": null,
"pending_change": null
}Archive a price
POST/v1/apps/{appID}/prices/{id}/archive
Archives the price. It starts no new subscriptions, and subscriptions already on it keep renewing. Archiving an archived price changes nothing. To undo it, unarchive the price.
Returns the price. If the app has no price with that ID, returns 404 with code not_found.
curl https://api.abuna.app/v1/apps/01J9ZQ4Y7R3T6V8W2X5B1C0DEF/prices/01J9ZQ5N1P2Q3R4S5T6V7W8XYZ/archive \
-X POST \
-H "Authorization: Bearer sk_test_..."{
"id": "01J9ZQ5N1P2Q3R4S5T6V7W8XYZ",
"product_id": "01J9ZQ5A2B3C4D5E6F7G8H9JKM",
"name": "Monthly",
"currency": "XAF",
"amount": 5000,
"interval": "month",
"interval_count": 1,
"created_at": 1727600000,
"archived_at": 1727686400,
"pending_change": null
}Unarchive a price
POST/v1/apps/{appID}/prices/{id}/unarchive
Unarchives the price. It can start new subscriptions again, and its checkout link works again. Unarchiving a price that isn't archived changes nothing.
Returns the price. If the app has no price with that ID, returns 404 with code not_found.
curl https://api.abuna.app/v1/apps/01J9ZQ4Y7R3T6V8W2X5B1C0DEF/prices/01J9ZQ5N1P2Q3R4S5T6V7W8XYZ/unarchive \
-X POST \
-H "Authorization: Bearer sk_test_..."{
"id": "01J9ZQ5N1P2Q3R4S5T6V7W8XYZ",
"product_id": "01J9ZQ5A2B3C4D5E6F7G8H9JKM",
"name": "Monthly",
"currency": "XAF",
"amount": 5000,
"interval": "month",
"interval_count": 1,
"created_at": 1727600000,
"archived_at": null,
"pending_change": null
}Schedule a price change
POST/v1/apps/{appID}/prices/{id}/change
Schedules a new amount for the price, starting on a day at least 14 days ahead. The amount never changes right away. The currency and billing period can't change. For a different one, create a new price.
From the start of that day in your app's time zone, checkouts and new subscriptions pay the new amount. Each current subscriber pays it from their first renewal on or after that day. Abuna emails each of them now with the new amount and the date it first applies to them, and messages them on Telegram if they connected it. Customers who subscribe while the change is pending get the same notice when they subscribe.
With keep_current_subscribers, everyone subscribed on that day keeps paying the old amount for as long as they stay subscribed, and only later subscribers pay the new one. A subscriber who moves to another price and back pays the price's amount at that time.
A price has one pending change. Scheduling another replaces it. If the replacement keeps current subscribers on the old amount, those told about the earlier change get an email saying their price stays. Abuna records a price_change.scheduled event, and price_change.applied when the new amount starts.
Parameters
amountintegerrequiredThe new amount, in the currency's smallest unit. Must be greater than 0 and different from the current amount.effective_attimestamprequiredAny time on the day the new amount starts, in Unix seconds. Abuna moves it to the start of that day in your app's time zone. The day must be at least 14 days after today there.keep_current_subscribersbooleanKeep everyone subscribed on that day on the old amount. Defaults tofalse.
Returns the price with pending_change set. A failed check returns 422 with code invalid, and each errors item names the field: amount with invalid or unchanged, or effective_at with required or too_soon. Any other field, like currency or interval, returns 400 with code invalid_json. If the app has no price with that ID, returns 404 with code not_found. Once the pending change reaches its day, it can't be replaced, and the request returns 409 with code change_in_effect.
curl https://api.abuna.app/v1/apps/01J9ZQ4Y7R3T6V8W2X5B1C0DEF/prices/01J9ZQ5N1P2Q3R4S5T6V7W8XYZ/change \
-X POST \
-H "Authorization: Bearer sk_test_..." \
-H "Content-Type: application/json" \
-d '{"amount": 6000, "effective_at": 1730458800, "keep_current_subscribers": false}'{
"id": "01J9ZQ5N1P2Q3R4S5T6V7W8XYZ",
"product_id": "01J9ZQ5A2B3C4D5E6F7G8H9JKM",
"name": "Monthly",
"currency": "XAF",
"amount": 5000,
"interval": "month",
"interval_count": 1,
"created_at": 1727600000,
"archived_at": null,
"pending_change": {
"id": "01J9ZQ7C1D2E3F4G5H6J7K8M9N",
"amount": 6000,
"previous_amount": 5000,
"currency": "XAF",
"effective_at": 1730415600,
"keep_current_subscribers": false,
"created_at": 1727700000
}
}Cancel a price change
POST/v1/apps/{appID}/prices/{id}/change/cancel
Cancels the price's pending change, so the amount stays as it is. If current subscribers were going to move to the new amount, Abuna emails them that their price stays. Abuna records a price_change.canceled event.
Returns the price with pending_change set to null. If the app has no price with that ID, returns 404 with code not_found. With no pending change, returns 409 with code no_pending_change. Once the change reaches its day, it can't be canceled, and the request returns 409 with code change_in_effect.
curl https://api.abuna.app/v1/apps/01J9ZQ4Y7R3T6V8W2X5B1C0DEF/prices/01J9ZQ5N1P2Q3R4S5T6V7W8XYZ/change/cancel \
-X POST \
-H "Authorization: Bearer sk_test_..."{
"id": "01J9ZQ5N1P2Q3R4S5T6V7W8XYZ",
"product_id": "01J9ZQ5A2B3C4D5E6F7G8H9JKM",
"name": "Monthly",
"currency": "XAF",
"amount": 5000,
"interval": "month",
"interval_count": 1,
"created_at": 1727600000,
"archived_at": null,
"pending_change": null
}