Shop
The shop sells six kinds of product through the Payriff payment page, charged in Azerbaijani manat (AZN): owned cosmetic items, bundles of such items, premium packages of 1, 3 or 12 months, Visionary, a boost slot for 30 days, and username cards, which buy a username of 1, 2 or 3 characters. Prices are set in US cents and converted at a rate the admin sets. The server computes every amount, and a client sends only the product. The Admin API sets the prices, campaigns and sales switches.
Every route on this page is user-only, except Get code preview the two Payriff callback routes and Receive Paddle webhook, which take no credential. The catalog, quote and order routes answer only on a self-hosted instance in the mirror premium mode. Any other instance returns 403 FEATURE_NOT_AVAILABLE_SELF_HOSTED. The premium subscription routes check neither the premium mode nor the sales switches, so a subscription Paddle still charges can always be canceled.
Shop products
Section titled “Shop products”A product is named by its SKU.
| Value | Description |
|---|---|
cosmetic:<item_id> | An owned-availability cosmetic item, by its ID |
bundle:<bundle_id> | A bundle of 2-10 owned-availability items, by its ID |
premium:1, premium:3, premium:12 | Premium time of 1, 3 or 12 months |
visionary | Lifetime premium with a Visionary number |
boost:30 | One boost slot for 30 days |
username:1, username:2, username:3 | A username card for a username of 1, 2 or 3 characters |
An item or bundle ID is a positive integer within the signed 64-bit range. A value of any other shape fails validation with 400 INVALID_FORM_BODY.
Prices
Section titled “Prices”The server computes a price in one place, in whole numbers:
- The campaign discount comes off the list price while the campaign has not ended by the server clock.
- The premium member discount comes off that, for cosmetic items and bundles only, and only for an account with paid premium. An account in its free trial gets no member discount. Lifetime premium counts as paid.
- The US dollar price is converted to qəpik, hundredths of a manat, at the admin rate.
Each step rounds half up. A list price of 500 cents with a 20% campaign and a 30% member discount comes to 400 cents, then 280 cents ($2.80), which is 476 qəpik (4.76 AZN) at a rate of 1.7000. A product whose final price would fall below 1 cent or below 0.10 AZN is not sold.
Premium packages, Visionary and username cards take campaigns, and never take the member discount.
Each API process keeps the catalog for up to 60 seconds, and the quote reads a price list kept for up to 5 seconds, so both are for display. Create shop order reads the stored price and settings directly, and the amount it records is the amount charged.
Shop price object
Section titled “Shop price object”| Field | Type | Description |
|---|---|---|
| base_usd_cents | integer | The list price in US cents |
| campaign_percent | ?integer | The campaign discount, null without a running campaign |
| campaign_ends_at | ?ISO8601 timestamp | When the campaign ends, null without a campaign or without an end |
| sale_usd_cents | integer | The price after the campaign, equal to base_usd_cents without one |
| premium_usd_cents1 | ?integer | What an account with paid premium pays for an item or a bundle |
| payable? | ?payable amount object | What this account pays now, in its provider’s currency; null when nobody sells to it or it cannot be charged |
| list_payable? | ?payable amount object | The list price without a campaign or the member discount, in that currency |
| plus_payable? | ?payable amount object | With the member discount, for an item or a bundle; null when the account gets it already or there is none |
1 Null while the member discount is 0, and always null for premium packages and Visionary
Where the admin sets a lira or manat list price, a route charging that currency takes it, less the same campaign and member discount percentages, instead of the converted amount.
Payable amount object
Section titled “Payable amount object”| Field | Type | Description |
|---|---|---|
| amount_minor | integer | The amount in the minor unit of currency: cents, kuruş or qəpik |
| currency | string | AZN, USD or TRY |
Shop settings object
Section titled “Shop settings object”| Field | Type | Description |
|---|---|---|
| premium_sales_open | boolean | Whether premium packages and Visionary are on sale |
| item_sales_open | boolean | Whether cosmetic items and bundles are on sale |
| premium_discount_percent | integer | The member discount on items and bundles for accounts with paid premium (0-95) |
| usd_to_azn_rate | string | Manat per US dollar with four decimal places, such as 1.7000, for display |
| usd_to_azn_rate_micro? | ?integer | Exact AZN per USD in millionths; use this for catalog AZN display instead of the rounded legacy string |
| try_to_azn_rate_micro? | ?integer | Effective AZN per TRY in millionths; null means show AZN alone |
| try_rate_date? | ?date | Automatic CBAR bulletin date in YYYY-MM-DD format; null before the first accepted bulletin |
| try_rate_expires_at? | ?ISO8601 timestamp | When an automatic display rate becomes stale; null for a manual override or no effective rate |
Both sales fields read false while Payriff is not configured.
Clients display the informational TL price and the payable AZN total. Convert the selected catalog US-cent price to qepik using the exact integer rate and the server’s half-up rule, then divide that AZN amount by the effective TRY rate for the TL estimate. Quotes and orders provide the authoritative amount_azn_minor; a TRY rate must never be used to create or price an order. Existing dollar fields remain for older clients.
The effective TRY rate uses the admin override when set. Otherwise it uses the automatic CBAR rate only while the bulletin is at most 10 calendar days old in Asia/Baku. Clients stop displaying that rate at try_rate_expires_at, even if the page stays open or a catalog refresh fails. No rate or a stale automatic bulletin gives null without closing sales. CBAR is refreshed by a worker; catalog requests do not contact CBAR. Bank conversion rates and foreign transaction fees can make the card statement’s TL total differ from this estimate.
Shop catalog item object
Section titled “Shop catalog item object”| Field | Type | Description |
|---|---|---|
| item | cosmetic item object | The item |
| price1 | ?shop price object | The price of an owned item |
1 Null for a premium-availability item, which comes with premium and is not sold
Shop package object
Section titled “Shop package object”| Field | Type | Description |
|---|---|---|
| recurring_quote? | ?recurring quote object | Each period’s subscription charge; null when no subscription is sold to the account or it cannot be charged |
| sku | string | The product: premium:1, premium:3 or premium:12 |
| months | integer | The premium time the package adds: 1, 3 or 12 months |
| price | shop price object | The price |
Shop bundle object
Section titled “Shop bundle object”| Field | Type | Description |
|---|---|---|
| sku | string | The product: bundle:<id> |
| id | snowflake | The ID of the bundle |
| name | string | The display name of the bundle |
| description | ?string | A short description of the bundle |
| items1 | array[cosmetic item object] | The items the bundle gives, in bundle order |
| price | shop price object | The price of the bundle, with any running campaign |
1 Embedded whole, because an item sold only in bundles has no price of its own and is not in the catalog items
Shop Visionary object
Section titled “Shop Visionary object”| Field | Type | Description |
|---|---|---|
| sku | string | Always visionary |
| price | shop price object | The price, with any running campaign |
| total | integer | The number of Visionary numbers the admin set |
| remaining1 | integer | The numbers with no owner and no live hold |
| visible | boolean | Whether Visionary is on sale: premium sales open, Visionary switched on, and Payriff configured |
1 For display only. Every API process reads one count shared for 60 seconds and keeps its catalog for up to 60 seconds more, so the value can be up to 2 minutes old. Create shop order decides whether a number is free
Shop boost object
Section titled “Shop boost object”| Field | Type | Description |
|---|---|---|
| sku | string | Always boost:30 |
| days | integer | How long a bought slot lasts from payment, always 30 |
| price | shop price object | The price, with any running campaign, and never a member discount |
Shop username card object
Section titled “Shop username card object”| Field | Type | Description |
|---|---|---|
| sku | string | The product: username:1, username:2 or username:3 |
| length | integer | The number of characters in the username the card buys: 1, 2 or 3 |
| price | shop price object | The price, with any running campaign. premium_usd_cents is always null |
| asset1 | ?string | The hash of the card image, null until the admin uploads one |
| visible | boolean | Whether the card is on sale, as Username cards describes |
1 The media proxy serves the image at /branding/cosmetic-username_card-{length}/{asset}.webp on the instance’s media endpoint, with size between 128 and 480 and animated=true for the moving image
Username card availability object
Section titled “Username card availability object”| Field | Type | Description |
|---|---|---|
| username | string | The username checked, in lowercase |
| status | string | The username card availability status |
Username card availability statuses
Section titled “Username card availability statuses”| Value | Description |
|---|---|
| available | The username is free, and an order can hold it |
| owned | The account already has the username |
| taken | Another account has the username, or it is reserved or in its release embargo |
| held | Another account’s order holds the username, or the account’s own order does after its first 15 minutes or once paid1 |
| held_by_you | An open order of the account holds the username, in the 15 minutes after the order opened1 |
| not_allowed | The dictionary or the username rules refuse the username |
| closed | No card of the username’s length is on sale, or the username policy cannot be read |
| invalid | The username is not 1-3 Latin letters or digits |
1 An order of the account whose payment Payriff declined or cancelled holds the username as held_by_you until its hold ends, and a new order takes the hold over.
Shop viewer object
Section titled “Shop viewer object”| Field | Type | Description |
|---|---|---|
| premium_discount | boolean | Whether the account pays premium_usd_cents for items, which needs paid premium rather than a trial |
| recurring_eligibility? | recurring eligibility object | Whether the account can take a premium subscription now |
Shop catalog object
Section titled “Shop catalog object”| Field | Type | Description |
|---|---|---|
| settings | shop settings object | The sales switches, the member discount and the rate |
| items | array[shop catalog item object] | The priced owned items and the premium items on sale, in catalog order |
| packages | array[shop package object] | The priced premium packages, in 1, 3 and 12 month order |
| visionary | ?shop Visionary object | Visionary, null until the admin sets its price |
| boost | ?shop boost object | The boost slot, null without a price, or while the caller’s provider does not sell it (Boost slots) |
| bundles | array[shop bundle object] | The priced bundles whose items are all on sale, in ID order |
| username_cards? | array[shop username card object] | The priced username cards in length order, empty while the username policy cannot be read |
| server_time | ISO8601 timestamp | The server clock, for campaign countdowns |
| viewer | shop viewer object | What applies to the requesting account |
Only viewer differs between accounts. The rest is the same for every account.
Shop quote object
Section titled “Shop quote object”| Field | Type | Description |
|---|---|---|
| sku | string | The product |
| billing_mode? | string | one_time or recurring, as asked |
| interval_months? | ?integer | Recurring only: the period, 1, 3 or 12 months; null for one_time |
| terms_version? | ?string | Recurring only: the version of the subscription terms; null for one_time |
| base_usd_cents | integer | The list price in US cents |
| campaign_percent | integer | The campaign discount applied, 0 without one |
| premium_percent | integer | The member discount applied, 0 when it does not apply |
| final_usd_cents | integer | The US dollar price after both discounts |
| amount_azn_minor | integer | The price in qəpik: what Payriff would charge, and the AZN equivalent of the US dollar price for Paddle |
| usd_to_azn_rate | string | The rate used, with four decimal places |
| usd_to_azn_rate_micro? | ?integer | Exact rate used, in millionths; use it for quote subtotal display rather than a cached catalog rate |
| provider? | string | Who would take the payment, payriff or paddle; absent means Payriff |
| charge_amount_minor? | integer | What the provider would charge, in the minor unit of currency; absent means amount_azn_minor |
| currency | string | The currency the provider charges: AZN for Payriff, USD or TRY for Paddle |
| list_payable? | ?payable amount object | The list price without a campaign or the member discount, in the currency the provider charges |
Shop order statuses
Section titled “Shop order statuses”| Value | Description |
|---|---|
| pending | The order is open and waiting for payment |
| paid | Payriff confirmed the payment, and delivery has not finished |
| granted | The item, the bundle’s items, the premium time or the Visionary number was delivered |
| failed | Nothing is delivered, and failure_reason says why |
| expired | The order was still unpaid at its last check, and a payment Payriff confirms later still completes it |
| refunded | An admin marked a paid username card order refunded. It is final, and no check or delivery changes it |
Shop order failure reasons
Section titled “Shop order failure reasons”| Value | Description |
|---|---|
| amount_mismatch | Payriff reported a payment whose amount or currency differs from the order |
| account_deleted | The account was deleted before delivery |
| item_deleted | The item of a cosmetic order was deleted before delivery |
| provider_error | Payriff did not open the order |
| refunded | A Paddle refund or chargeback was approved before the payment was taken in, so nothing is delivered |
| username_taken | At delivery, the username of a username card order was taken, reserved, embargoed or held |
| username_not_allowed | At delivery, the dictionary or the username rules refused the username of a username card order |
| username_already_owned | At delivery, the account already had the username, and not from this order |
| username_delivery_stalled | A day after the order opened and an hour after payment, the username store still could not deliver the username |
Micup sends no refund through Payriff. An admin refunds a failed payment by hand in the Payriff panel. A paid username card order that fails waits in the admin’s refund list until the admin marks it refunded.
Shop order object
Section titled “Shop order object”| Field | Type | Description |
|---|---|---|
| id | snowflake | The ID of the order |
| sku | string | The product |
| status | string | The shop order status |
| failure_reason | ?string | The failure reason, null unless the order failed |
| billing_mode? | string | one_time, or recurring for the first payment of a premium subscription |
| interval_months? | ?integer | Recurring only: the period, 1, 3 or 12 months |
| terms_version? | ?string | Recurring only: the subscription terms the buyer accepted |
| base_usd_cents | integer | The list price in US cents when the order opened |
| campaign_percent | integer | The campaign discount applied, 0 without one |
| premium_percent | integer | The member discount applied, 0 when it does not apply |
| final_usd_cents | integer | The US dollar price charged |
| amount_azn_minor | integer | The amount charged, in qəpik |
| usd_to_azn_rate | string | The rate used, with four decimal places |
| usd_to_azn_rate_micro? | ?integer | Exact rate stored with this order, in millionths |
| currency | string | The currency the provider charges: AZN for Payriff, USD or TRY for Paddle |
| display_currency? | ?string | Currency of the price shown in the shop when the order opened: TRY or AZN; null on legacy orders |
| display_amount_minor? | ?integer | That shown price in minor units (kuruş or qəpik), frozen on the order; null on legacy orders |
| display_rate_micro? | ?integer | Frozen AZN per TRY in millionths used for the shown TRY price; null for AZN or legacy orders |
| provider? | string | Who takes the payment, payriff or paddle; absent means Payriff |
| charge_amount_minor? | integer | What the provider charges, in the minor unit of currency; absent means amount_azn_minor |
| payment_url | ?string | The payment page, null unless pending; a Paddle one ends in #t=, the public client-side token, and &l=, the buyer’s language as Paddle names it (en for a language Paddle’s checkout lacks) |
| paddle_checkout?1 | object | What Paddle.js needs to open the checkout; absent means use payment_url |
| created_at | ISO8601 timestamp | When the order opened |
| item_ids? | array[snowflake] | Bundle orders only: the items the order delivers, frozen when it opened, in bundle order |
| username? | ?string | Username card orders only: the username the order buys, null once the account is deleted |
1 Present on a pending Paddle order that has a client token, while the deployment sets FLUXER_PADDLE_IN_APP_CHECKOUT (the app’s domain approved by Paddle). It holds transaction_id, the Paddle transaction to open, client_token, the public Paddle.js client-side token, and environment, sandbox or production
Example
Section titled “Example”{ "id": "1501314428688998182", "sku": "premium:3", "status": "pending", "failure_reason": null, "base_usd_cents": 1200, "campaign_percent": 25, "premium_percent": 0, "final_usd_cents": 900, "amount_azn_minor": 1530, "usd_to_azn_rate": "1.7000", "currency": "AZN", "payment_url": "https://payment.example.com/pay/abc123", "created_at": "2026-09-28T20:15:00.000Z"}Shop code object
Section titled “Shop code object”| Field | Type | Description |
|---|---|---|
| code | string | The code |
| redeemed | boolean | Whether the code has been redeemed |
| grant | shop code grant object | What redeeming the code gives |
Shop code grant object
Section titled “Shop code grant object”| Field | Type | Description |
|---|---|---|
| type | string | premium for premium time, or cosmetic for an item |
| duration_type? | string | Premium only: the gift duration unit |
| duration_quantity? | integer | Premium only: the number of units |
| item? | cosmetic item object | Cosmetic only: the item |
Shop refusals
Section titled “Shop refusals”The quote and order routes refuse a product with these codes:
- 403
SHOP_SALES_CLOSEDwhen sales of the product’s kind are closed or Payriff is not configured. Premium packages, Visionary and the boost slot follow the premium sales switch, and cosmetic items, bundles and username cards follow the item sales switch. The boost slot is closed through Paddle while the instance has no Paddle boost product, and through Payriff while the account’sfeature_shop_boost_payrifflimit is 0. Username cards sell only through Payriff. - 404
SHOP_ITEM_UNAVAILABLEwhen the product has no price or no longer exists, when an item, or any item of a bundle, is not an owned-availability item on sale, when the final price would fall below 1 cent or 0.10 AZN, when an account with lifetime premium asks for a package or Visionary, or when Visionary is switched off. An order also receives it when no Visionary number is free. A username card also receives it while it is not on sale, as Username cards describes. - 409
COSMETIC_ALREADY_OWNEDwhen the account already owns the item, or any item of a bundle. A bundle is never sold in part. - 400
MAX_PURCHASED_BOOST_SLOTS, on an order only, when the account’s live bought boost slots and its open boost orders already reach itsmax_purchased_boost_slotslimit. - 409
SHOP_TOO_MANY_OPEN_ORDERS, on an order only, when the account already has three open orders. An open order is unpaid, less than 15 minutes old, and not declined, cancelled or expired at Payriff. - 409
SHOP_RECURRING_NOT_ELIGIBLEwhen the account cannot take a premium subscription now, with a recurring eligibility object asrecurring_eligibility. A one-time premium order receives it too while a subscription of the account runs or its first payment is being delivered. An unpaid recurring checkout does not block a one-time premium order: the server cancels its Paddle transaction first, so only one premium checkout can ever be paid. - 409
SHOP_RECURRING_QUOTE_CHANGED, on a recurring order only, whenrecurring_consentdiffers from the current recurring quote. Paddle is never asked. - 503
SHOP_BUSY, on an order only, when the shared Payriff request budget is spent, when Payriff is backing off after a 429 or did not open the order, while another order of the same account is being opened, or when the username store is too busy to hold a username card’s username. Retrying after a few seconds is safe.
Payment and delivery
Section titled “Payment and delivery”- Create shop order returns an order with
payment_url, the Payriff payment page, which a client opens in the browser. Payriff shows the page in English and charges manat. - Payriff calls Receive Payriff callback when the payment changes, and the browser comes back through Return from Payriff. Neither is trusted. A matching callback token only queues a check, in which the server reads the order from Payriff.
- The check accepts a payment only when Payriff reports it
APPROVEDorPAIDfor a purchase, in AZN, for exactly the amount of the order. The order becomespaid, is delivered, and becomesgranted. - A client follows the order with Get shop order, which reads only the server’s record and never reaches Payriff.
Apart from callbacks, the server checks every unpaid order with Payriff 2 minutes, 15 minutes, 3 hours and 24 hours after it opened. An order still unpaid at the fourth check expires, and so does an order Payriff reports expired. An order that has no Payriff order ID 10 minutes after it opened fails with provider_error.
Scheduled checks leave an expired order alone, so only a callback or a return finds a payment made after the order expired. The check that finds it makes the order paid and schedules one more check 2 minutes later, so the delivery still completes when the callback check gives up. A paid order whose delivery has to wait keeps its scheduled check until the delivery finishes.
Delivery depends on the product:
- A cosmetic item is added to the account and worn in the slot of its type. Deliveries of one item to one account run one at a time, and a delivery that waits more than about 5 seconds is tried again later. That sends User Update, and for an avatar decoration, ultra decoration or nameplate also Guild Member Update, as Wear or take off cosmetics does.
- A bundle delivers each item in the order’s
item_idsas its own delivery, the way a cosmetic item is delivered, and wears only the first item of each type in that list. The other items are added without being worn. A delivery that stops part way is tried again later and gives only the items still missing. - A premium package adds its months to the account’s premium and sends User Update.
- Visionary is delivered as Visionary describes.
- A boost slot is delivered as Boost slots describes.
- A username card gives the account its username, as Username cards describes.
A payment for an item the account already owns when the delivery runs grants no second copy, and neither does a second paid order for the same item delivered at the same time. The order becomes granted, and an admin refunds the payment by hand.
A paid cosmetic order whose item was deleted before delivery fails with item_deleted, and an admin refunds it by hand too.
A bundle order skips an item deleted before delivery and an item the account already owns, delivers the rest, and becomes granted. An admin refunds it by hand, as Bundles in the Admin API describes.
Order records
Section titled “Order records”An order is a financial record. Deleting the account keeps its orders, which hold the account ID, the product, the items of a bundle order, the amounts, the rate, the Payriff order ID and the times, and no email address, name or IP address. A username card order also holds the username it buys until the account is deleted, which clears it. The data harvest lists every order of the account, newest first, without the payment page, whether or not Payriff is configured.
Visionary
Section titled “Visionary”Visionary is lifetime premium with a number. The admin sets the price, the number of numbers, and whether Visionary is on sale. The catalog carries a shop Visionary object once the price is set. It stays in the catalog when every number is taken, with remaining at 0.
The visionary product takes campaigns, and never takes the member discount. It follows the premium sales switch, so closed premium sales return 403 SHOP_SALES_CLOSED. A switched-off Visionary, a missing price, or an account that already has lifetime premium, whether as a Visionary or not, returns 404 SHOP_ITEM_UNAVAILABLE.
Numbers are handed out as follows:
- Create shop order holds the lowest free number for 15 minutes before it asks Payriff. When no number is free, it returns 404
SHOP_ITEM_UNAVAILABLEand Payriff is never called. - An order that fails or expires unpaid frees its number.
- A payment that arrives after the hold ended still gets a number: the held one if it is still free, otherwise the next free one, otherwise one above the total. The Admin API counts a number above the total in
over_cap_grants. - An account that is already a Visionary when its order is delivered gets no second number. The order still becomes
granted, the number it held is freed because it was never any account’s number, and an admin refunds the payment by hand. Two orders of one account delivered together give one number. - A number is never given twice. A number an admin takes back, or one whose account is deleted, stays retired and is never sold again. An order whose number an admin took back never gives it again, even when its delivery is retried.
Delivery gives the account lifetime premium, with the number as its premium lifetime sequence, and the reward items the admin chose, each added and worn. It runs under the account’s premium lock, as Make user a Visionary does. While another premium change holds that lock, the delivery is tried again later. The account’s sessions receive User Update. The shop joins no Visionary guild.
In the mirror premium mode, only an account with lifetime premium can take the discriminator 0000. An account that already wears 0000 keeps it until it changes its username, and must then pick another discriminator. Otherwise the change fails with 400 INVALID_FORM_BODY and the validation code VISIONARY_REQUIRED_FOR_DISCRIMINATOR.
Boost slots
Section titled “Boost slots”The boost:30 product gives the account one boost slot for 30 days from payment. Any account can buy it, with or without premium, lifetime premium included. It takes campaigns, and never takes the member discount. An account holds at most max_purchased_boost_slots live bought slots, 20 in the stock configuration.
- Delivery adds the slot
shop:<order_id>with itsexpires_at. A second delivery of the order adds nothing. - The slot boosts like a premium slot, with the same 7 day wait before it moves to another guild. It is listed in List current user boost slots until it ends, and premium ending leaves it.
- When the slot ends, the hourly boost check takes its boost off the guild.
- It sells through Paddle while the instance has a Paddle boost product. A full Paddle refund, or any chargeback, ends the slot at once and takes its boost off. A boost that races the refund fails with 409
CONFLICT. A partial refund leaves the slot; an admin decides by hand. - It sells through Payriff to an account whose
feature_shop_boost_payrifflimit is 1, 0 in the stock configuration, so a trait rule can open it to chosen accounts first. Payriff reports no refund: an admin refunds the payment in the Payriff panel and marks the order with Mark Payriff boost order refunded, which ends the slot and takes its boost off the same way.
Username cards
Section titled “Username cards”A username card buys the account a username of 1, 2 or 3 characters. It is a product, username:<length>, and not a cosmetic item. The catalog lists each priced card as a shop username card object. A card is on sale only while all of these hold:
- Item sales are open and the buyer’s payment route is Payriff, with Payriff configured and taking orders. Otherwise the quote and the order return 403
SHOP_SALES_CLOSED, and the catalog shows the card withvisiblefalse. - The admin switched the card’s length on, and the card has a price.
- The username policy tier of the length is
for_sale, and the username migration isselecting. Apublictier takes the card out of the shop.
Otherwise the quote and the order return 404 SHOP_ITEM_UNAVAILABLE. A card takes campaigns and never takes the member discount, and an account with lifetime premium can buy one.
The username is 1-3 lowercase Latin letters or digits, exactly as long as the card. The server checks it against the dictionary and the username rules when the order opens and again at delivery. Check username card availability tells a client what an order would meet.
Ordering
Section titled “Ordering”- Create shop order holds the username for the account until 30 minutes after the order opened, before it asks Payriff: the order’s 15 minutes, then 15 more for a payment made in time but confirmed late. Only that order can take the username in those last 15 minutes, and any new order gets 409
USERNAME_ON_HOLD, the account’s own included, unless Payriff declined or cancelled that order’s payment. The same request again within 15 minutes returns the open order at the price it opened with, even when the card’s price or the exchange rate has changed since, and the hold does not grow. - An account has one open username card order at a time. An order for another username or card returns 409
SHOP_USERNAME_ORDER_OPENwhile it is open, and so does the same request while Payriff has not opened the order yet, and a new order for a username the account’s own paid order holds until its delivery. - An account opens at most 5 username card orders a day, counted from the first that Payriff opened, and the sixth returns 429
RATE_LIMITEDwithretry_afterset to the seconds left of that day. - An order that expired unpaid keeps its account from the same username until 75 minutes after it opened, with 429
RATE_LIMITEDandretry_afterset to the seconds left. A payment Payriff declined or cancelled has no wait, and the new order takes over the hold. A new order that then fails, at Payriff or before it, gives the hold back, and the hold ends at its own time. - A username another account has, or one reserved or in its release embargo, returns 409
USERNAME_TAKEN. One held by another account’s order returns 409USERNAME_ON_HOLD, and the account’s own username 409SHOP_USERNAME_ALREADY_OWNED. One the dictionary or the username rules refuse returns 409USERNAME_BLOCKED.
A change racing the order can also return 409 USERNAME_RESERVED, USERNAME_RELEASE_EMBARGO or USERNAME_BLOCKED when the hold is taken. None of these refusals reaches Payriff.
Delivery
Section titled “Delivery”When Payriff confirms the payment, the hold is fixed for 7 days before the order becomes paid. No other order can take the username in the 15 minutes after the order’s own 15, so a payment confirmed by then still finds the username held for it. One confirmed later finds it only while no other order has held the username in the meantime. A hold that still cannot be fixed a day after the order opened no longer waits: the order becomes paid without it, and delivery gives the username only if it is still free.
Delivery gives the account the username through the username ownership protocol. The purchase skips the 14-day wait between username changes and leaves the free change unused. The previous username is released as on any username change, and the account’s sessions receive User Update.
- A delivery the username store cannot take now, because it is busy, rate limited or unavailable, is tried again later. A delivery still not made a day after the order opened, and an hour after its payment, fails the order with
username_delivery_stalled, whatever kept it. - A retried delivery that finds the username already given by this order completes the order, even after the account changed to another username.
- A delivery the username refuses fails the order with
username_taken,username_not_allowedorusername_already_owned, as Shop order failure reasons describes. A deleted account fails it withaccount_deleted.
A paid username card order that fails, including one with amount_mismatch, goes to the admin’s refund list. For amount_mismatch, the list shows the amount and currency Payriff reported. The admin refunds the payment in the Payriff panel and marks the order with Mark shop order refunded.
An admin can also refund a granted order. An account that still has the username from that order then loses it: it must pick a new username of 4 or more characters, and the username goes into an admin hold.
Premium subscriptions
Section titled “Premium subscriptions”A premium package of 1, 3 or 12 months can also be bought as a Paddle subscription that renews every period until it is canceled. Only Paddle sells it, so only where Paddle sells to the request’s country. Cosmetic items, bundles and Visionary are never recurring.
- Get shop quote with
billing_mode=recurringreturns what every period charges, withinterval_monthsandterms_version. The catalog carries the same offer in each package’srecurring_quote. - Create shop order with
billing_moderecurringand a recurring consent object opens the checkout. The server prices the offer again, and a consent whoseterms_version,amount_minor,currency,interval_monthsortrial_daysdiffers from that answers 409SHOP_RECURRING_QUOTE_CHANGEDbefore Paddle is asked. The client shows the new offer and asks for consent again. A recurring order for anything butpremium:1,premium:3orpremium:12, or without a consent naming the same period as the SKU, fails validation with 400INVALID_FORM_BODY. While it is pending, itspayment_urlfragment also carries&p=, every Plus period on sale to the order’s country as1:9999,3:26999,12:94999,TRY(period:amount pairs, then the currency), for the payment page’s plan list only. - The first payment binds the subscription to the account. Each renewal Paddle confirms moves
paid_untilto the end of the period it paid, and the account has premium until then.
When the offer carries trial_days, the checkout charges nothing and premium starts at once for that many days; the first charge comes when they end, and a cancel before then ends premium at the trial’s end. An account gets one trial, and none after an earlier subscription or trial, nor on a verified email address another account used a trial on; an account without a verified email gets none. The member discount on items and bundles starts with the first charge, and a refused first charge gives none. The shop tab may show a trial offer or the member discount up to five minutes late; the order reads both again.
An account takes one premium subscription at a time, and none on top of other premium time. While it cannot take one, the catalog’s viewer.recurring_eligibility says why, and a recurring order answers 409 SHOP_RECURRING_NOT_ELIGIBLE with that object as recurring_eligibility.
The routes below manage the account’s own subscription. None takes a subscription, customer or user ID from the client. They answer whatever the premium mode, the sales switches and the payment routing say, because a subscription Paddle still charges must always be cancelable.
Recurring consent object
Section titled “Recurring consent object”| Field | Type | Description |
|---|---|---|
| accepted | boolean | Always true |
| terms_version | string | The terms_version of the quote the buyer accepted |
| amount_minor | integer | What each period charges, in the minor unit of currency |
| currency | string | USD or TRY |
| interval_months | integer | The period: 1, 3 or 12 months |
| trial_days? | ?integer | The offer’s trial_days; absent or null when it had none |
Recurring quote object
Section titled “Recurring quote object”| Field | Type | Description |
|---|---|---|
| billing_mode | string | Always recurring |
| terms_version | string | The version of the subscription terms |
| amount_minor | integer | What each period charges, in the minor unit of currency, at the list price |
| currency | string | USD or TRY |
| interval_months | integer | The period: 1, 3 or 12 months |
| payable? | ?payable amount object | The same amount and currency |
| list_payable? | ?payable amount object | The same amount and currency |
| plus_payable? | ?payable amount object | Always null |
| trial_days? | ?integer | Free days before the first charge this account would get now; null for none |
Recurring eligibility object
Section titled “Recurring eligibility object”| Field | Type | Description |
|---|---|---|
| eligible | boolean | Whether the account can take a premium subscription now |
| reason | ?string | The recurring eligibility reason, null when eligible |
| available_after | ?ISO8601 timestamp | prepaid_active and existing_subscription only: when premium time ends, null if it renews |
Recurring eligibility reasons
Section titled “Recurring eligibility reasons”| Value | Description |
|---|---|
| sales_closed | Premium sales or new subscriptions are closed |
| region_unavailable | Paddle does not sell to the request’s country |
| prepaid_active | The account has premium time that has not ended |
| existing_subscription | The account has a live subscription, or a canceled one whose paid time has not ended |
| checkout_pending | Another premium checkout of the account is open or being delivered |
| visionary | The account has lifetime premium |
| purchase_disabled | The account has the purchase-disabled premium flag |
Paddle subscription statuses
Section titled “Paddle subscription statuses”| Value | Description |
|---|---|
| pending | The first payment is not confirmed yet |
| active | The subscription renews |
| past_due | A renewal payment failed, and Paddle retries it |
| paused | Paddle paused the subscription |
| canceled | The subscription ended, and nothing is charged again |
| needs_review | Support has to look at the subscription |
Paddle subscription state object
Section titled “Paddle subscription state object”| Field | Type | Description |
|---|---|---|
| status | string | The Paddle subscription status |
| interval_months | integer | The period: 1, 3 or 12 months |
| amount_minor | integer | What each period charges, in the minor unit of currency |
| currency | string | USD or TRY |
| paid_until | ?ISO8601 timestamp | The end of the last paid period; premium lasts until then |
| next_billed_at | ?ISO8601 timestamp | When Paddle charges next, null once a cancel is scheduled or started by Micup, or the subscription ended |
| cancel_at | ?ISO8601 timestamp | The cancel scheduled for the end of the paid period, null without one |
| can_cancel | boolean | Whether Cancel premium subscription can schedule a cancel |
| can_reactivate | boolean | Whether Keep premium subscription can take a scheduled cancel back |
| can_update_payment_method | boolean | Whether Change subscription payment method is open |
Get shop catalog
Section titled “Get shop catalog”GET/v1/shop/catalogReturns the shop catalog object: the priced owned items, the premium items, the priced bundles, the premium packages, Visionary, the username cards, and the sales settings.
Response
Section titled “Response”| Status | Body | Condition |
|---|---|---|
| 200 | shop catalog object | The catalog was returned |
| 403 | error response | FEATURE_NOT_AVAILABLE_SELF_HOSTED: the instance is not self-hosted in the mirror premium mode |
Rate limit
Section titled “Rate limit”30 requests per minute for each authenticated user, on the shop:catalog bucket.
Get shop quote
Section titled “Get shop quote”GET/v1/shop/quoteReturns the shop quote object: what the account would pay for one product now. A quote reserves nothing and binds nothing. The order records the amount charged.
Query parameters
Section titled “Query parameters”| Field | Type | Description |
|---|---|---|
| sku | string | The product |
| billing_mode? | string | one_time (default) or recurring, which prices a premium subscription |
Response
Section titled “Response”| Status | Body | Condition |
|---|---|---|
| 200 | shop quote object | The quote was returned |
| 400 | error response | The SKU fails validation, returned as INVALID_FORM_BODY |
| 403 | error response | Sales are closed, or the instance is not self-hosted in the mirror premium mode |
| 404 | error response | The product cannot be bought now, returned as SHOP_ITEM_UNAVAILABLE |
| 409 | error response | The account already owns the item or an item of the bundle, returned as COSMETIC_ALREADY_OWNED |
| 409 | error response | No subscription is sold to the account, returned as SHOP_RECURRING_NOT_ELIGIBLE |
A recurring quote is what every period of the subscription charges: the list price without a campaign, in the currency Paddle charges the account. It answers for premium:1, premium:3 and premium:12 only, and only where Paddle sells to the request’s country. Shop refusals lists the conditions behind each code.
Rate limit
Section titled “Rate limit”60 requests per minute for each authenticated user, on the shop:quote bucket.
Check username card availability
Section titled “Check username card availability”GET/v1/shop/username-cards/availabilityReturns a username card availability object: whether the account could buy a username with a username card now. The answer never names the account that has the username, and it reserves nothing. Create shop order decides.
The server keeps the state of a checked username for up to 6 seconds, so the answer can be that old.
Query parameters
Section titled “Query parameters”| Field | Type | Description |
|---|---|---|
| username | string | The username to check (at most 32 characters) |
Response
Section titled “Response”| Status | Body | Condition |
|---|---|---|
| 200 | username card availability object | The username was checked |
| 400 | error response | The username is longer than 32 characters, returned as INVALID_FORM_BODY |
Rate limit
Section titled “Rate limit”30 requests per minute for each authenticated user, on the shop:username:availability bucket.
Create shop order
Section titled “Create shop order”POST/v1/shop/ordersOpens a Payriff order for one product at the price the server computes now, and returns the shop order object with its payment page. The order is delivered once Payriff confirms the payment, as Payment and delivery describes.
An open order of the account for the same product at the same amount, for a bundle with the same items in the same order, and for a username card with the same username, is returned instead of a new one, so repeating the request opens no second payment.
Limitations
Section titled “Limitations”- The account must be claimed, and an unclaimed account returns 400
UNCLAIMED_ACCOUNT_CANNOT_MAKE_PURCHASES. - The account must have a verified email address, and an unverified address returns 403
PURCHASE_EMAIL_VERIFICATION_REQUIRED. - The account must not have the purchase-disabled premium flag, which returns 403
PREMIUM_PURCHASE_BLOCKED. - Every shop refusal applies, and a username card order also meets the limits in Username cards.
JSON body
Section titled “JSON body”| Field | Type | Description |
|---|---|---|
| sku | string | The product to buy |
| username? | string | Username card orders only: the username to buy, 1-3 lowercase letters or digits, as long as the card |
A username card SKU without a username of its length, or a username with any other SKU, fails validation with 400 INVALID_FORM_BODY.
A Paddle order also takes a second field.
| Field | Type | Description |
|---|---|---|
| immediate_performance_consent? | boolean | Ignored; older clients still send it |
| billing_mode? | string | one_time (default), or recurring to start a premium subscription |
| recurring_consent? | recurring consent object | Recurring only, and required there: the recurring offer as the buyer accepted it |
Response
Section titled “Response”| Status | Body | Condition |
|---|---|---|
| 200 | shop order object | The order was opened, or an open order for the same product and amount was returned |
| 400 | error response | The SKU or username fails validation, the account is unclaimed, or it holds its boost limit (MAX_PURCHASED_BOOST_SLOTS) |
| 403 | error response | Sales are closed, purchases are not allowed for the account, or the instance has no shop |
| 404 | error response | The product cannot be bought now, returned as SHOP_ITEM_UNAVAILABLE |
| 409 | error response | Item already owned, three open orders, cannot subscribe now, recurring offer changed, or username card refused |
| 429 | error response | A username card order limit applies, returned as RATE_LIMITED |
| 503 | error response | The server cannot open a Payriff order now, returned as SHOP_BUSY |
Side effects
Section titled “Side effects”The order is written as pending with the amounts of the one price computation, and its first scheduled check is set. A Visionary order holds a number, and a username card order its username, before Payriff is asked. A bundle order records the bundle’s items in item_ids, and a later change to the bundle, or its deletion, leaves them as they are. The catalog can be up to 60 seconds old, so a client compares item_ids with the items it showed before it opens the payment page. The Payriff order description names the order ID, the product and the US dollar price, so an admin can find the order with Get shop order in the Admin API. The description of a username card order never contains the username. When Payriff does not open the order, the order fails with provider_error, its Visionary number or username hold is freed, and the request returns 503 SHOP_BUSY.
Rate limit
Section titled “Rate limit”3 requests per minute for each authenticated user, on the shop:order:create bucket. 20 requests per day for each authenticated user, on the shop:order:create:day bucket.
Get shop order
Section titled “Get shop order”GET/v1/shop/orders/{order_id}Returns one order of the account as the server last recorded it, as a shop order object. The route never asks Payriff, so it is the one to poll while a payment is open.
Path parameters
Section titled “Path parameters”| Field | Type | Description |
|---|---|---|
| order_id | snowflake | The ID of the order |
Response
Section titled “Response”| Status | Body | Condition |
|---|---|---|
| 200 | shop order object | The order was returned |
| 403 | error response | FEATURE_NOT_AVAILABLE_SELF_HOSTED: the instance is not self-hosted in the mirror premium mode |
| 404 | error response | NOT_FOUND: no order has this ID, or the order belongs to another account |
Rate limit
Section titled “Rate limit”60 requests per minute for each authenticated user, on the shop:order:get bucket.
Get code preview
Section titled “Get code preview”GET/v1/shop/codes/{code}Returns what a gift code gives, premium time or a cosmetic item, as a shop code object. It is the only route that shows an item code, because Get gift answers an item code with 404. Redeem gift redeems both kinds.
Every deployment serves the route. A self-hosted instance outside the mirror premium mode returns 403 FEATURE_NOT_AVAILABLE_SELF_HOSTED.
Path parameters
Section titled “Path parameters”| Field | Type | Description |
|---|---|---|
| code | string | The code to look up (1-32 characters) |
Response
Section titled “Response”| Status | Body | Condition |
|---|---|---|
| 200 | shop code object | The code was returned |
| 403 | error response | FEATURE_NOT_AVAILABLE_SELF_HOSTED: the instance is self-hosted outside the mirror premium mode |
| 404 | error response | UNKNOWN_GIFT_CODE: no code exists, the code was revoked, or its item no longer exists |
Rate limit
Section titled “Rate limit”The shop:gift:get bucket takes the limit of the gift:get bucket of Get gift: 60 requests per 10 seconds for each authenticated user or client IP address. The two buckets count separately, and a supplied credential only keys the bucket.
Return from Payriff
Section titled “Return from Payriff”GET/v1/payriff/callback/{order_id}/{token}The browser lands here from the Payriff payment page. The route redirects it to the premium callback page of the web app with a status and no order detail. It never answers with an error.
Path parameters
Section titled “Path parameters”| Field | Type | Description |
|---|---|---|
| order_id | string | The ID of the order |
| token1 | string | The callback token of the order |
1 32 random bytes in base64url, created with the order. It exists only in the callback address handed to Payriff. The server keeps its SHA-256 hash and compares in constant time, and the request and error logs write the path with the token masked
Response
Section titled “Response”| Status | Body | Condition |
|---|---|---|
| 302 | empty | Always, with Location set to /premium-callback?status= on the web app and one of the statuses below |
successwhen the order is granted, or paid and waiting for delivery.cancelwhen the order failed or was refunded.pendingin every other case: an unpaid or expired order, a token that does not match, an unknown or malformed order ID, or a server error.
Side effects
Section titled “Side effects”A matching token queues a check of the order with Payriff, as Payment and delivery describes. Repeats within 30 seconds queue one check, and each order takes at most 10 callback checks an hour, so replaying a return link cannot spend the Payriff budget. The scheduled checks run whatever the callbacks do. Anything else queues nothing.
Rate limit
Section titled “Rate limit”600 requests per minute for each client IP address, or for the authenticated user when a credential happens to be supplied, on the payriff:callback bucket.
Receive Payriff callback
Section titled “Receive Payriff callback”POST/v1/payriff/callback/{order_id}/{token}Payriff calls this when a payment changes. Payriff signs nothing, so the body is never read. Only the order ID and the token in the path count, with the same path parameters and side effects as Return from Payriff. Returns a webhook received object.
Response
Section titled “Response”| Status | Body | Condition |
|---|---|---|
| 200 | webhook received object | Always, unless the caller accepts text/html |
| 303 | empty | The caller accepts text/html, and is sent where Return from Payriff would send it |
Rate limit
Section titled “Rate limit”600 requests per minute for each client IP address, or for the authenticated user when a credential happens to be supplied, on the payriff:callback bucket.
Receive Paddle webhook
Section titled “Receive Paddle webhook”POST/v1/paddle/webhookPaddle is the second payment provider next to Payriff. Paddle posts a signed event here, and the route queues the ones the shop handles for the worker and returns a webhook received object. The Paddle-Signature header is the credential.
Limitations
Section titled “Limitations”- A deployment with no Paddle webhook secret configured returns 503. Paddle retries any non-2xx answer, so the event is delivered again once the secret is set.
- A body over 1 MiB returns 413.
Request headers
Section titled “Request headers”| Field | Type | Description |
|---|---|---|
| Paddle-Signature1 | string | ts=<unix seconds>;h1=<hex>, the HMAC-SHA256 signature of the raw body |
1 h1 is the HMAC-SHA256 of <ts>:<raw body> under the webhook secret, and a header with several h1 values is accepted when one matches. ts must be within 5 minutes of the server clock
Response
Section titled “Response”| Status | Body | Condition |
|---|---|---|
| 200 | webhook received object | The signature verified, including a redelivered event or an unhandled event type, which queue nothing |
| 401 | error response | The header is missing or malformed, the timestamp is stale, or the signature did not verify |
| 413 | empty | The body is over 1 MiB |
| 503 | error response | No Paddle webhook secret is configured |
Side effects
Section titled “Side effects”The response comes before processing. Only transaction.completed, adjustment.created and adjustment.updated events are queued, and any other verified event is acknowledged and dropped.
Rate limit
Section titled “Rate limit”600 requests per minute for each client IP address, or for the authenticated user when a credential happens to be supplied, on the paddle:webhook bucket. The bucket is exempt from the global bucket.
Cancel premium subscription
Section titled “Cancel premium subscription”POST/v1/paddle/subscription/cancelCancels the account’s own Paddle subscription and returns the Paddle subscription state object as Paddle answered. An active subscription is canceled at the end of its paid period: premium lasts until paid_until, and nothing is charged again. A past_due or paused one is canceled at once, as its paid time already ended, so Paddle stops retrying the failed renewal. A cancel already scheduled, or one Micup already owes Paddle, is returned unchanged, so repeating the request changes nothing. A subscription waiting on review, and a limited account, can cancel too. When the Paddle budget for subscription changes is spent, the cancel is kept as owed and sent by the next check, and the answer already shows it scheduled for paid_until. The route takes no body.
Response
Section titled “Response”| Status | Body | Condition |
|---|---|---|
| 200 | Paddle subscription state object | The cancel is scheduled, done or owed |
| 400 | error response | NO_ACTIVE_SUBSCRIPTION: the account has no active, past_due or paused subscription |
| 409 | error response | SHOP_SUBSCRIPTION_CHANGE_REFUSED: Paddle refuses the change now, as it does within 30 minutes of a renewal |
| 503 | error response | SHOP_BUSY: Paddle did not answer |
Rate limit
Section titled “Rate limit”10 requests per minute for each authenticated user, on the paddle:subscription:manage bucket.
Keep premium subscription
Section titled “Keep premium subscription”POST/v1/paddle/subscription/reactivateTakes back a scheduled cancel of the account’s own Paddle subscription while its paid period lasts, so it renews again, and returns the Paddle subscription state object as Paddle answered. Without a scheduled cancel it changes nothing. The route takes no body.
Response
Section titled “Response”| Status | Body | Condition |
|---|---|---|
| 200 | Paddle subscription state object | The subscription renews again, or had no scheduled cancel |
| 400 | error response | NO_ACTIVE_SUBSCRIPTION: the subscription has ended |
| 403 | error response | PREMIUM_PURCHASE_BLOCKED: the account has the purchase-disabled premium flag |
| 403 | error response | The account is limited and the request returns ACCOUNT_LIMITED |
| 409 | error response | SHOP_SUBSCRIPTION_CHANGE_REFUSED: the cancel was Micup’s own, the subscription waits on review, or Paddle refuses |
| 503 | error response | SHOP_BUSY: Paddle did not answer, or the Paddle budget for subscription changes is spent |
A cancel is Micup’s own when the server asked Paddle for it after a refund, a chargeback, a second subscription of the same account, a payment it did not accept, or the account’s deletion. Such a cancel is never taken back.
Rate limit
Section titled “Rate limit”10 requests per minute for each authenticated user, on the paddle:subscription:manage bucket.
Change subscription payment method
Section titled “Change subscription payment method”POST/v1/paddle/subscription/payment-methodReturns a redirect URL object whose url is a one-time link to the approved payment page. The server builds it from the transaction ID Paddle gives for the change, as ?_ptxn= and that ID (Paddle’s overlay, even while the deployment sets FLUXER_PAY_INLINE_LINK for orders), followed by the same #t= client-side token and &l= language a Paddle order’s payment_url carries when a token is configured. There Paddle’s checkout changes the card the account’s own subscription renews with, and while a renewal has failed it pays that renewal too. The response carries Cache-Control: no-store, and the server never stores the link. The route takes no body.
Response
Section titled “Response”| Status | Body | Condition |
|---|---|---|
| 200 | redirect URL object | The link was made |
| 400 | error response | NO_ACTIVE_SUBSCRIPTION: the account has no live subscription |
| 403 | error response | PREMIUM_PURCHASE_BLOCKED: the account has the purchase-disabled premium flag |
| 403 | error response | The account is limited and the request returns ACCOUNT_LIMITED |
| 409 | error response | SHOP_SUBSCRIPTION_CHANGE_REFUSED: the subscription has a cancel of Micup’s own, waits on review, or Paddle refuses |
| 503 | error response | SHOP_BUSY: Paddle did not answer, or the Paddle budget for subscription changes is spent |
Rate limit
Section titled “Rate limit”10 requests per minute for each authenticated user, on the paddle:subscription:manage bucket.