Skip to content
Micup API

Shop

The shop sells owned cosmetic items, bundles of them, premium packages, Visionary, the boost slot and username cards through Payriff, as Shop describes. These routes set the sales switches, the member discount, the rate, the prices and the campaigns, manage the bundles, look up an order or an account’s premium subscription, manage the Visionary numbers, and manage the username cards. Every route on this page requires shop:manage and records an Admin audit entry, reads included. Mark shop order refunded also requires user:update:username and username:reserve; Mark Payriff boost order refunded needs shop:manage alone. The routes answer in every premium mode, so an admin can prepare the shop before opening it.

A value the shop cannot accept returns 400 INVALID_FORM_BODY, with one element in errors that names the field and carries an English message. Make user a Visionary and Take Visionary from user are on the Admin users page.

Every write that changes a price, a campaign, the member discount or the rate is checked against the worst case, which is the campaign together with the member discount on an item. A write that would leave any product below 1 cent or below 0.10 AZN returns 400 INVALID_FORM_BODY, and nothing is written. Create shop order checks the final price again.

FieldTypeDescription
premium_sales_openbooleanThe switch for premium packages, Visionary and the boost slot
item_sales_openbooleanThe switch for owned items and bundles
premium_discount_percentintegerThe member discount on items and bundles for accounts with paid premium (0-95)
usd_to_azn_rate_microintegerManat per US dollar in millionths, so a rate of 1.70 is 1700000
try_to_azn_rate_micro?integerLast accepted CBAR rate, in millionths of AZN per TRY; display only
try_rate_date?dateBulletin date from CBAR’s XML, in YYYY-MM-DD format
try_rate_fetched_at?ISO8601 timestampLast successful rate refresh
try_to_azn_rate_override_micro?integerManual display rate; null uses the automatic rate
try_rate_override_at?ISO8601 timestampWhen the current override was set; null when cleared
payriff_configured1booleanWhether the deployment has a Payriff configuration
visionary_on_salebooleanWhether Visionary is shown and sold, or hidden
visionary_reward_item_idsarray[snowflake]The items delivered and worn with every Visionary number
early_plus_badge_id?snowflakeThe catalog badge that paid premium and Visionary orders get while Early Plus is open
early_plus_openbooleanWhether paid premium and Visionary orders get the Early Plus badge
early_plus_opened_oncebooleanWhether Early Plus has opened at least once, after which its badge can no longer change
payment_default_route?payment routeThe route of a country without its own row
payment_country_routes?objectThe route of each listed country, by ISO 3166-1 alpha-2 code (at most 250)
payment_providers?objectpayriff and paddle, each {orders_open}: whether the provider takes new orders
paddle_configured?booleanWhether Paddle is switched on with a key, a webhook secret and products
paddle_environment??stringsandbox or production; null when Paddle is not configured
payriff_last_order_at??ISO8601 timestampWhen the last Payriff order opened; keep the Payriff key at least 7 days after it
payment_route_warnings?array[route warning]The routes that sell nothing now, and why, as payment routes describes
paddle_test_user_ids?array[snowflake]The accounts that buy through Paddle whatever their route and the Paddle switch say
paddle_test_currency?stringUSD or TRY: what Paddle charges a test account in
paddle_trial_days?integerFree days (0-30) before a new Paddle Plus subscription’s first charge, once an account; 0 offers none
username_card_sale_1booleanThe switch for the 1 character username card
username_card_sale_2booleanThe switch for the 2 character username card
username_card_sale_3booleanThe switch for the 3 character username card
username_card_asset_1?stringThe image hash of the 1 character card, null without an image
username_card_asset_2?stringThe image hash of the 2 character card, null without an image
username_card_asset_3?stringThe image hash of the 3 character card, null without an image

1 False keeps every sale closed whatever the switches say. It comes from the deployment configuration, and no route changes it. A Micup process refuses to start while FLUXER_PAYRIFF_BASE_URL is not an https URL, even with Payriff switched off

Both sales switches, visionary_on_sale, early_plus_open, early_plus_opened_once and the three username card switches start off, early_plus_badge_id starts null, the card images start null, the member discount starts at 0, and the rate starts at 1700000. The settings live in the instance configuration under the shop_config key.

Payriff charges AZN; users see TL (CBAR daily rate) and the AZN amount. The price anchor remains the stored US-cent price and usd_to_azn_rate_micro. Changing either TRY rate never changes a quote, an order or the Payriff charge. Legacy dollar fields remain in the API for older clients.

The effective display rate is the override, or the automatic rate if its bulletin is no more than 10 days old. Without an effective rate, the client shows AZN alone and sales continue. The worker checks CBAR every six hours, and queues an initial refresh when no automatic rate is stored. Catalog requests use the existing shared cache and never fetch CBAR themselves.

The refresh reads only the TRY entry, divides Value by Nominal, and uses the XML bulletin date even when the requested day is a weekend. It rejects malformed XML, DOCTYPE declarations, older bulletins, rates outside 0.001–1.0 AZN per TRY, and changes greater than 25% from the previous automatic rate. A rejected refresh logs a warning and preserves the last accepted rate and admin settings. Requests have a timeout, a response-size limit and no redirects.

A payment route says who sells to a country and in which currency. It is an object with a provider of paddle, payriff or off.

ProviderCurrencyMeaning
paddleUSD or TRYPaddle charges in this currency
payriffTRY or AZNThe price is shown in this currency, and Payriff always charges AZN. TRY falls back to AZN without a TL rate
offnoneNobody sells in the country

The country comes from GeoIP. A country missing from payment_country_routes, or an unknown country, takes payment_default_route. The default route starts as payriff with TRY, the country table starts empty, Payriff orders start open, and Paddle orders start closed. A closed provider still checks and delivers the orders it already has.

A route warning holds country, null for the default route and for geoip_unavailable, an optional provider, and a reason:

  • not_configured: the provider is not configured, so the route sells nothing.
  • orders_closed: the provider’s switch is off.
  • try_rate_missing: the route needs a TL rate that is not known.
  • geoip_unavailable: no country is known, so every buyer takes the default route.
FieldTypeDescription
skustringThe product
base_usd_centsintegerThe list price in US cents
campaign_percent?integerThe campaign discount as stored, including a campaign that has ended
campaign_ends_at?ISO8601 timestampWhen the campaign ends, null for a campaign with no end
base_try_minor??integerThe list price in kuruş where a route charges TRY; null converts from US cents
base_azn_minor??integerThe list price in qəpik where a route charges AZN; null converts from US cents
nowshop price objectWhat the shop shows for the product now
FieldTypeDescription
skustringThe product
base_usd_cents??integerList price in US cents (1-10000000); null takes the product off the shop; absent keeps the stored price (400 if none)
base_try_minor??integerThe list price in kuruş (1-1000000000) where a route charges TRY; null clears it, absent keeps it
base_azn_minor??integerThe list price in qəpik (1-1000000000) where a route charges AZN; null clears it, absent keeps it
FieldTypeDescription
idsnowflakeThe ID of the bundle
skustringThe product bundle:<id>, priced with Set shop prices
namestringThe display name of the bundle (1-64 characters)
description?stringA short description (1-256 characters)
item_idsarray[snowflake]The items the bundle gives, in bundle order (2-10)
FieldTypeDescription
busy_refusals_last_hourintegerThe orders refused with SHOP_BUSY in the current and the previous clock hour
reconcile_lag_secondsintegerThe age of the oldest scheduled check still waiting, 0 while checks are current
payriff_backing_offbooleanWhether Payriff answered 429 in the last 30 seconds

The budget is a temporary limit sized for the shop’s traffic today. Every API and worker process shares about 2 requests per second to Payriff, split into three buckets so that none can starve another. Opening orders has about 0.3 per second, callback checks about 0.5 per second, and scheduled checks about 1.2 per second. The shop therefore opens about 0.3 orders per second, roughly 18 a minute, and refuses the rest with 503 SHOP_BUSY. The API logs such a refusal at debug level, as expected load shedding, and counts it in busy_refusals_last_hour.

The three buckets hold at most 6, 10 and 12 requests, 28 together, which stays under the burst of 30 observed from Payriff. The reconcile job runs once a minute or more often, and each run spends only what the reconcile bucket holds, 12 requests when it is full. While reconcile_lag_seconds is above 900, every reconcile run also logs a warning.

A username card order first spends a share of its own, about 0.15 per second with at most 3 at once, then holds its username, and only then spends a request from the opening bucket, so a rush for short usernames cannot take the whole opening budget, and an order that loses the username spends no opening request. A refusal from either returns 503 SHOP_BUSY and counts in busy_refusals_last_hour too, and a refusal from the opening bucket frees the username the order held.

Grow the budget when any of these appears:

  • busy_refusals_last_hour above 0.
  • reconcile_lag_seconds above 900, which is 15 minutes.
  • payriff_backing_off true, after the first 429 from Payriff.
  • Daily orders reaching half of the limit Payriff approved.

The first step is a higher Payriff quota, with the three buckets grown together in the same 1 : 1.7 : 4 ratio. The next is a server-side order queue in place of SHOP_BUSY. The last is a second payment provider, for which every order already records its provider.

FieldTypeDescription
settingsshop admin settings objectThe stored settings
pricesarray[shop admin price object]Every price row, in SKU order
healthshop health objectThe Payriff budget triggers
bundlesarray[shop admin bundle object]Every bundle, priced or not, in ID order
FieldTypeDescription
idsnowflakeThe ID of the order
user_idsnowflakeThe account that bought
skustringThe product
statusstringThe shop order status
failure_reason?stringThe failure reason, null unless the order failed
billing_mode?stringone_time, or recurring for the first payment of a premium subscription
interval_months??integerRecurring only: the period, 1, 3 or 12 months
terms_version??stringRecurring only: the subscription terms the buyer accepted
base_usd_centsintegerThe list price in US cents when the order opened
campaign_percentintegerThe campaign discount applied, 0 without one
premium_percentintegerThe member discount applied, 0 when it did not apply
final_usd_centsintegerThe US dollar price charged
amount_azn_minorintegerThe price in qəpik: what Payriff charged, and the AZN equivalent for Paddle
usd_to_azn_ratestringThe rate used, with four decimal places
currencystringThe currency the provider charged: AZN for Payriff, USD or TRY for Paddle
display_currency??stringCurrency of the price shown in the shop when the order opened: TRY or AZN; null on legacy orders
display_amount_minor??integerThat shown price in minor units (kuruş or qəpik), frozen on the order; null on legacy orders
display_rate_micro??integerFrozen AZN per TRY in millionths used for the shown TRY price; null for AZN or legacy orders
provider?stringWho took the payment, payriff or paddle; absent means Payriff
charge_amount_minor?integerWhat the provider charged, in the minor unit of currency; absent means amount_azn_minor
provider_order_id?stringThe Payriff order ID or the Paddle transaction ID, null until the provider opened the order
provider_status?stringThe last payment status the provider reported
country_code??stringThe GeoIP country of the order, null when unknown
provider_price_id??stringThe Paddle price ID of the order’s one line, null for Payriff
provider_adjustment??stringThe last Paddle adjustment as action:status, such as refund:approved
adjusted_at??ISO8601 timestampWhen that adjustment was recorded
created_atISO8601 timestampWhen the order opened
paid_at?ISO8601 timestampWhen the payment was confirmed
granted_at?ISO8601 timestampWhen delivery finished
check_countintegerThe scheduled checks made so far (0-4)
item_ids?array[snowflake]Bundle orders only: the items frozen when the order opened, in bundle order
username??stringUsername card orders only: the username the order buys, null once the account is deleted

Older Paddle orders may carry the buyer’s consent to immediate delivery; new orders record none.

FieldTypeDescription
immediate_performance_accepted_at??ISO8601 timestampWhen the buyer accepted immediate delivery, null without consent
immediate_performance_terms_version??stringThe version of the consent text the buyer accepted
FieldTypeDescription
totalintegerThe number of Visionary numbers
soldintegerThe numbers with an owner, including taken-back numbers, which are never sold again
heldintegerThe numbers a pending order holds while its 15 minute hold lasts
remaining1integerThe numbers with no owner and no live hold
over_cap_grantsintegerThe numbers given above total because a payment arrived after its hold ended

1 Read fresh on every request. A number row without a version, which only upstream Micup code writes, is never handed out and never counts here

FieldTypeDescription
lengthintegerThe username length the card sells: 1, 2 or 3
skustringThe product username:<length>
tierstringThe username policy tier of the length: locked, for_sale or public
on_salebooleanThe switch of the length, username_card_sale_<length>
asset?stringThe image hash, null without an image
base_usd_cents?integerThe list price in US cents, null without a price
soldintegerThe delivered orders of the card, less the refunded ones
free1?integerThe sellable usernames of the length that nobody has, holds or embargoes, null before the first count
sellable1?integerThe usernames of the length that the dictionary and the username rules allow, null before the first count
counted_at?ISO8601 timestampWhen the hourly count last ran, null before the first

1 From the hourly count, which runs at minute 17 of every hour. It makes one point read for each allowed username of the three lengths, at most 47,988, and never scans the table. A username whose read fails counts sellable but not free, so free is never too high. More than 16 failed reads in one length fail the count: that length and the ones after it keep their last count and counted_at

FieldTypeDescription
migration_statestringThe username migration state: legacy, preparing or selecting
cardsarray[shop admin username card object]The cards of lengths 1, 2 and 3, in that order
refunds_dueusername refunds due objectThe paid username card orders waiting for a refund
FieldTypeDescription
totalintegerHow many paid username card orders wait for a refund
itemsarray[username refund due object]The oldest of them first, at most 100
FieldTypeDescription
order_idsnowflakeThe ID of the order
user_idsnowflakeThe account that bought
skustringThe product username:<length>
amount_azn_minorintegerThe amount charged, in minor units of currency. For amount_mismatch, the amount Payriff reported
currencystringThe currency of amount_azn_minor: AZN unless Payriff reported another
failure_reasonstringThe failure reason: a username reason, account_deleted or amount_mismatch
failed_atISO8601 timestampWhen the order failed
FieldTypeDescription
ordershop admin order objectThe order, now refunded
name_actionstringThe username refund name action
ValueDescription
noneNo name step: nothing was delivered, or the username changed hands or is held by another order
heldThe username went into an admin hold
reselectedThe buyer must pick a new username of 4 or more characters
reselected_and_heldBoth: the buyer must pick a new username, and the username went into an admin hold

Shop admin user Paddle subscription object

Section titled “Shop admin user Paddle subscription object”
FieldTypeDescription
subscription?shop admin Paddle subscription objectThe account’s latest Paddle subscription, null when it never had one
FieldTypeDescription
subscription_idstringThe Paddle subscription ID
customer_idstringThe Paddle customer ID
order_idsnowflakeThe shop order of the first payment
statusstringThe Paddle subscription status
interval_monthsintegerThe period: 1, 3 or 12 months
amount_minorintegerWhat each period charges, in the minor unit of currency
currencystringUSD or TRY
paid_until?ISO8601 timestampThe end of the last paid period
next_billed_at?ISO8601 timestampWhen Paddle charges next, as Paddle last reported
cancel_at?ISO8601 timestampThe cancel scheduled for the end of the paid period
pending_cancel?stringWhy Micup still owes Paddle a cancel, such as a refund, a chargeback or the account’s deletion; null when none waits
review_reason?stringWhy the subscription needs support, null when it does not
created_atISO8601 timestampWhen the first payment bound the subscription

The price and campaign operations record what they changed in the changes metadata key. It lists each product whose price or campaign changed as <sku>:<old>→<new>, joined with commas, and leaves out a product that stayed the same. The key is absent when nothing changed.

  • A price reads as US cents, such as premium:1:500→450, and none stands for no price.
  • A campaign reads as its percentage, such as 20%, or with its end as 20%@2026-10-01T00:00:00.000Z, and none stands for no campaign.

A bundle sells 2-10 owned-availability items as one product, bundle:<id>. Create shop bundle makes it, and it enters the shop once Set shop prices gives it a price. Campaigns and the member discount apply as they do to an item. An item sold only in bundles is an owned-availability item with no price of its own.

  • A null price takes a bundle out of the shop and keeps it. Delete shop bundle removes the bundle and its price.
  • A bundle leaves the catalog while any of its items is off sale, and cannot be bought until the item is back or the bundle changes.
  • A Visionary reward cannot be in a bundle, and an item in a bundle cannot become a Visionary reward.
  • An account that owns any item of a bundle cannot buy the bundle, so an item bought alone keeps its buyer from every bundle that holds it.
  • An order freezes the bundle’s items in item_ids when it opens. Open and paid orders deliver those items even after the bundle changes or is deleted.

Delivery gives each item as its own delivery, keyed by the order and the item, then wears the first item of each type it gave in one user update. An item deleted before delivery is skipped, the rest are delivered, the order becomes granted, and the API logs a warning with the order and item IDs. Refund by hand.

To roll the bundle release back:

  1. Give every bundle: product a null price with Set shop prices, so no bundle order opens.
  2. Wait 24 hours after the last bundle order opened, the time of its fourth scheduled check.
  3. Check that reconcile_lag_seconds in Get shop is 0.
  4. Roll the code back. The schema only adds, so it stays.
  5. For a bundle order still paid, give each of its item_ids with Give user a cosmetic item. No route closes an order: it stays paid and holds the reconcile cursor until its check row expires after 7 days.

A username card sells a username of 1, 2 or 3 characters, as Username cards describes. Each length is a product, username:1, username:2 or username:3, priced with Set shop prices and discounted with Apply shop campaign. The member discount never applies. Get username cards shows the cards, their counts and the refund list, and Set username card image sets a card’s image.

A card is sold while item sales are open, Payriff is configured, username_card_sale_<length> is on, and the card has a price, and while the username policy tier of its length is for_sale and the username migration is selecting. The switches belong to the shop settings, so turning one on never changes the username policy.

A paid username card order that fails with username_taken, username_not_allowed, username_already_owned, username_delivery_stalled, account_deleted or amount_mismatch waits in refunds_due until it is marked refunded. Refund the amount and currency the list shows: for amount_mismatch, that is what Payriff reported, not the order’s price. An order that a later delivery try still delivers leaves refunds_due by itself. Micup sends no refund through Payriff:

  1. Refund the payment in the Payriff panel.
  2. Mark the order with Mark shop order refunded. It becomes refunded and leaves refunds_due, and a granted order no longer counts in sold.
  3. For a granted order whose buyer still has the username, the route takes it back: the buyer must pick a new username of 4 or more characters, even while suspended, and the username goes into an admin hold with the note refund <order_id>.

A username that changed hands after delivery, or that another buyer’s open order holds, is left alone, and the refund still completes. name_action in the response reports the name step.

A username_delivery_stalled order was paid, but the username store could not deliver it within a day of the order opening and an hour of its payment. Its delivery can still finish later, so the route takes the username back the same way from every username card order whose delivery reached the buyer, granted or not, and leaves alone a username the order never delivered. A purchase from the order that is still open is repaired first: one already committed is finished, and one past its time is aborted. While it is still settling after that, the route marks the order refunded and returns 503 USERNAME_UNAVAILABLE: repeat it once the purchase settles, and the repeat finishes the name step.

GET/v1/admin/shopAudit reason

Returns the shop admin object. The settings, prices and bundles are read from storage, not from the caches the shop pages use.

StatusBodyCondition
200shop admin objectThe shop was returned

The operation records one Admin audit entry with action view_shop, target type shop, target ID 0, and metadata key result_count, the number of price rows.

60 requests per minute for each authenticated user, on the admin:shop:view bucket.

PATCH/v1/admin/shop/settingsAudit reason

Opens or closes sales, and sets the member discount, the rate, the Visionary settings and Early Plus. Returns the shop admin settings object.

FieldTypeDescription
try_to_azn_rate_override??stringManual TL display rate in AZN per TRY, "0.001" through "1" with up to six decimals; null resumes the automatic CBAR rate

The payment fields are optional too, and are payment routes and Paddle test accounts.

FieldTypeDescription
payment_default_route?payment routeThe route of a country without its own row
payment_country_routes?objectReplaces the whole country table; {} clears it
payment_providers?objectpayriff and paddle, each {orders_open}; both are written together
paddle_test_user_ids?array[snowflake]Replaces the Paddle test accounts (at most 20); [] clears the list
paddle_test_currency?stringUSD or TRY: what Paddle charges a test account in
paddle_trial_days?integerFree days (0-30) before a new Paddle Plus subscription’s first charge, once an account; 0 offers none

The sales switches, premium_discount_percent, usd_to_azn_rate_micro, visionary_on_sale, visionary_reward_item_ids, early_plus_badge_id, early_plus_open and the username card switches username_card_sale_1 to username_card_sale_3 are optional. An omitted field stays as it is. CBAR fields, override timestamps and the card images are read-only here, and Set username card image sets an image.

  • premium_discount_percent is 0 through 95.
  • usd_to_azn_rate_micro is 1000000 through 3000000, which catches a rate entered as 0.017 or as 17.
  • try_to_azn_rate_override is a decimal string from "0.001" through "1", with up to six decimal places, in AZN per TRY, or null to resume the automatic rate. It only changes the informational TL display.
  • visionary_reward_item_ids replaces the list and holds at most 10 IDs. Each must be an owned-availability item without a shop price and in no bundle, and a repeated ID counts once.
  • early_plus_badge_id is a badge from the catalog whose text has no {n}, or null. It needs badges:manage as well. Once Early Plus has opened, the badge is fixed for good. Choose a badge made only for Early Plus, because a take-back removes that badge even when an admin gave it by hand.
  • payment_default_route, payment_country_routes and payment_providers set the payment routes. A country code is two upper case letters.
  • paddle_test_user_ids needs gift_codes:generate as well. A test account buys through Paddle in paddle_test_currency whatever its route and the Paddle switch say. With sandbox test cards it gets real premium and items, a free grant like a gift code. While Paddle runs on a sandbox key, only these accounts can buy through Paddle, unless the deployment sets FLUXER_PADDLE_SANDBOX_SELLS_ALL.
  • early_plus_open set to true needs badges:manage as well, and a badge. Closing needs only shop:manage.
  • A username card switch sells its length only while the username policy tier of that length is for_sale. Writing it never changes the username policy or its revision.
StatusBodyCondition
200shop admin settings objectThe settings were saved
400error responseA value is refused, or a product would fall below the minimum price
403error responseMISSING_ACL: badges:manage is missing for Early Plus, or gift_codes:generate for paddle_test_user_ids

The settings are saved with a compare-and-set, and every API process picks them up through the instance configuration refresh. The shop catalog can show the old values for up to 60 seconds. A new reward item reaches only numbers delivered after the change.

While Early Plus is open, every delivered premium or Visionary order gives its buyer the Early Plus badge. Gift codes, admin grants, referral rewards and trials do not, and neither does an order support refunds by hand: a premium order paid by a Visionary, or a Visionary order paid by an account that already is one. Every request that sets early_plus_open to true queues a backfillEarlyPlusBadges job, at most one a minute, shown on the jobs page. The job gives the badge to the buyer of every premium order delivered so far, and of every Visionary order whose buyer still is a Visionary. It skips deleted accounts and accounts with the EARLY_PLUS_REFUNDED premium flag. Its result holds granted, scanned and duration_ms, and a cancel on the jobs page stops it. Closing stops new badges. Badges already given stay.

Ending paid premium and taking Visionary back both take the badge away and set EARLY_PLUS_REFUNDED, and the next delivered premium or Visionary order clears the flag. A refund made before Early Plus existed has no flag, so its badge must be taken away by hand.

The operation records one Admin audit entry with action update_shop_settings, target type shop, and target ID 0. For every field the request changed, the metadata holds <field>_old and <field>_new.

30 requests per minute for each authenticated user, on the admin:shop:manage bucket.

POST/v1/admin/shop/settings/refresh-try-rateAudit reason

Queues the existing worker task to fetch the current CBAR bulletin. Requires shop:manage; the admin interface also verifies CSRF. No payment or order is created. The automatic-rate fields are updated only after a valid response, without replacing other shop settings. A manual override stays in force.

StatusBodyCondition
204emptyThe refresh was queued

The route uses the shop management rate limit and records an admin write audit entry. A queued refresh may fail or be rejected; check the last-success timestamp and bulletin date to confirm a new rate. The catalog can retain the previous display rate for up to 60 seconds.

PUT/v1/admin/shop/pricesAudit reason

Sets the list price of up to 200 products, and returns the shop admin object. The visionary and boost:30 products are priced here too.

FieldTypeDescription
pricesarray[shop price entry object]The prices to set (1-200 entries)
  • A cosmetic SKU must name an owned-availability item, and a Visionary reward item cannot take a price.
  • A bundle SKU must name an existing bundle. A null price is accepted for any bundle SKU, which clears a price left behind by a deleted bundle.
  • A new price keeps the product’s campaign. A null price removes the product and its campaign.
  • A SKU repeated in the list takes its last value.
StatusBodyCondition
200shop admin objectThe prices were set
400error responseAn entry is refused, or a product would fall below the minimum price

A quote shows a new price within 5 seconds and the catalog within 60 seconds. An order opened after the write is charged the new price.

The operation records one Admin audit entry with action set_shop_prices, target type shop, target ID 0, and metadata keys count, skus, the SKUs joined with commas, and changes, as Shop audit changes describes.

30 requests per minute for each authenticated user, on the admin:shop:manage bucket.

POST/v1/admin/shop/campaignsAudit reason

Puts one campaign discount on up to 200 priced products, until the given time or with no end, and returns the shop admin object. Visionary takes campaigns too. It never takes the member discount, which items still take on top of the campaign.

FieldTypeDescription
skusarray[string]The products, each with a price (1-200 entries)
percentintegerThe campaign discount (1-95)
ends_at?ISO8601 timestampWhen the campaign ends, in the future, or null for no end

A campaign replaces the product’s previous one, and a repeated SKU counts once. The campaign ends by the server clock. Its row keeps the percentage until it is removed or replaced, and now shows the price without it.

StatusBodyCondition
200shop admin objectThe campaign was applied
400error responseA SKU has no price, the end is in the past, or a product would fall below the minimum price

The operation records one Admin audit entry with action apply_shop_campaign, target type shop, target ID 0, and metadata keys count, percent, ends_at and changes, as Shop audit changes describes.

30 requests per minute for each authenticated user, on the admin:shop:manage bucket.

POST/v1/admin/shop/campaigns/removeAudit reason

Ends the campaign of up to 200 products now, and returns the shop admin object. A SKU with no price is skipped.

FieldTypeDescription
skusarray[string]The products (1-200 entries)
StatusBodyCondition
200shop admin objectThe campaigns were removed
400error responseThe body fails validation

The operation records one Admin audit entry with action remove_shop_campaign, target type shop, target ID 0, and metadata keys count and changes, as Shop audit changes describes.

30 requests per minute for each authenticated user, on the admin:shop:manage bucket.

POST/v1/admin/shop/bundlesAudit reason

Creates a bundle, and returns the shop admin bundle object. The bundle is not in the shop until Set shop prices gives it a price.

FieldTypeDescription
namestringThe display name of the bundle (1-64 characters)
description??stringA short description (1-256 characters)
item_idsarray[snowflake]The items, in bundle order (2-10)
  • A repeated item counts once, in its first place, and 2 to 10 items must remain.
  • Each item must be an owned-availability item on sale, with or without a price of its own, and not a Visionary reward.
  • The shop holds at most 200 bundles.
StatusBodyCondition
200shop admin bundle objectThe bundle was created
400error responseAn item is refused, or the shop already holds 200 bundles

The operation records one Admin audit entry with action create_shop_bundle, target type shop_bundle, the bundle ID as target ID, and metadata keys name and item_ids, the item IDs joined with commas.

30 requests per minute for each authenticated user, on the admin:shop:manage bucket.

PATCH/v1/admin/shop/bundles/{bundle_id}Audit reason

Changes the name, description or items of a bundle, and returns the updated shop admin bundle object.

FieldTypeDescription
bundle_idsnowflakeThe ID of the bundle

Every field of Create shop bundle, each optional. An omitted field stays as it is, and a null description clears it. New items are checked as on create.

StatusBodyCondition
200shop admin bundle objectThe bundle was updated
400error responseAn item is refused
404error responseNOT_FOUND: no bundle has this ID

A quote shows the change within 5 seconds and the catalog within 60 seconds. An order opened after the write gets the new items, and orders opened before keep theirs.

The operation records one Admin audit entry with action update_shop_bundle, target type shop_bundle, and the bundle ID as target ID. For every field the request changed, the metadata holds <field>_old and <field>_new, with item IDs joined with commas.

30 requests per minute for each authenticated user, on the admin:shop:manage bucket.

DELETE/v1/admin/shop/bundles/{bundle_id}Audit reason

Deletes a bundle together with its price. To take a bundle out of the shop and keep it, give it a null price with Set shop prices instead.

FieldTypeDescription
bundle_idsnowflakeThe ID of the bundle
StatusBodyCondition
204emptyThe bundle and its price were deleted
404error responseNOT_FOUND: no bundle has this ID

Open and paid orders still deliver the items they were opened with. The operation records one Admin audit entry with action delete_shop_bundle, target type shop_bundle, the bundle ID as target ID, and metadata key name.

30 requests per minute for each authenticated user, on the admin:shop:manage bucket.

GET/v1/admin/shop/orders/{order_id}Audit reason

Returns one order as a shop admin order object, for support and refunds. A bundle order lists its items in item_ids. The Payriff panel shows the order ID after Micup # in the order description, followed by the product. The description contains no US-dollar suffix.

FieldTypeDescription
order_idsnowflakeThe ID of the order
StatusBodyCondition
200shop admin order objectThe order was returned
404error responseNOT_FOUND: no order has this ID

The operation records one Admin audit entry with action view_shop_order, target type shop_order, the order ID as target ID, and metadata key order_id.

60 requests per minute for each authenticated user, on the admin:shop:view bucket.

GET/v1/admin/shop/users/{user_id}/paddle-subscriptionAudit reason

Returns the account’s latest Paddle subscription for support, as a shop admin user Paddle subscription object. The route only reads.

FieldTypeDescription
user_idsnowflakeThe ID of the account
StatusBodyCondition
200shop admin user Paddle subscription objectThe subscription, or null, was returned

The operation records one Admin audit entry with action view_user_paddle_subscription, target type user, the user ID as target ID, and metadata key has_subscription.

60 requests per minute for each authenticated user, on the admin:shop:view bucket.

GET/v1/admin/shop/visionaryAudit reason

Returns the shop admin Visionary object: how many numbers exist, and how many are sold, held and free.

StatusBodyCondition
200shop admin Visionary objectThe numbers were counted

The operation records one Admin audit entry with action view_shop_visionary, target type shop, target ID 0, and metadata key total.

60 requests per minute for each authenticated user, on the admin:shop:view bucket.

PUT/v1/admin/shop/visionary/slotsAudit reason

Sets how many Visionary numbers exist, and returns the shop admin Visionary object. Growing adds free numbers after the highest one. Shrinking removes numbers from the top.

A sold number stays counted in total and is never sold again, so selling more numbers means raising total by as many.

FieldTypeDescription
totalintegerThe number of Visionary numbers (0-100000)
StatusBodyCondition
200shop admin Visionary objectThe count was set
400error responseA removed number has an owner or a live hold, returned as CANNOT_SHRINK_RESERVED_SLOTS

A number with an owner or a live hold is never removed, and a shrink that races with a sale is refused. Read Get Visionary numbers to see how many are sold and held.

The operation records one Admin audit entry with action set_visionary_slots, target type shop, target ID 0, and metadata keys total_old and total_new.

30 requests per minute for each authenticated user, on the admin:shop:manage bucket.

GET/v1/admin/shop/username-cardsAudit reason

Returns the shop admin username cards object: the username migration state, each card’s tier, switch, image, price and counts, and the paid username card orders waiting for a refund.

StatusBodyCondition
200shop admin username cards objectThe cards were returned

The operation records one Admin audit entry with action view_shop_username_cards, target type shop, target ID 0, and metadata key result_count, the number of orders waiting for a refund.

60 requests per minute for each authenticated user, on the admin:shop:view bucket.

PUT/v1/admin/shop/username-cards/{length}/imageAudit reason

Uploads the image of one username card, or removes it, and returns an empty 204 response.

FieldTypeDescription
lengthstringThe card length: 1, 2 or 3
FieldTypeDescription
asset?stringA base64 data URL of a PNG, APNG or WebP image of up to 512 KB and 96-480 pixels, or null to remove it
StatusBodyCondition
204emptyThe image was saved or removed
400error responseThe image is refused, returned as INVALID_FORM_BODY

The image is stored under the owner cosmetic-username_card-<length>, and its hash replaces username_card_asset_<length> in the shop settings. When the settings cannot be saved, the upload is removed again. The catalog shows the change within 60 seconds.

The operation records one Admin audit entry with action set_username_card_image, target type shop, target ID 0, and metadata keys length, asset_old and asset_new. A hash that is null is left out.

30 requests per minute for each authenticated user, on the admin:shop:manage bucket.

POST/v1/admin/shop/orders/{order_id}/refundAudit reason

Marks a username card order refunded once its payment was refunded in the Payriff panel, takes the username back from a buyer who still has it, and returns the shop admin refund object. Requires shop:manage, user:update:username and username:reserve, and an audit reason. Refunds describes the steps.

The order must be a username card order that is granted, that failed after payment with a reason listed in refunds_due, or that is already refunded. The route is safe to repeat: a repeat finishes a name step that did not finish and counts nothing twice.

FieldTypeDescription
order_idsnowflakeThe ID of the order
StatusBodyCondition
200shop admin refund objectThe order is refunded
400error responseThe order cannot be refunded here, or the audit reason is missing, returned as INVALID_FORM_BODY
403error responseACL evaluation denies the request
404error responseNo order has this ID
503error responseUSERNAME_UNAVAILABLE: the order is refunded, and the name step has to be repeated

The order becomes refunded with a compare-and-set and leaves refunds_due, and a granted order no longer counts in sold. When the buyer still has the username, as delivered by this order, the buyer must pick a new username of 4 or more characters. The username then goes into an admin hold, unless another buyer’s open order holds it.

The operation records one Admin audit entry with action refund_shop_order, target type shop_order, the order ID as target ID, and metadata keys sku, status_old, name_action and force_username_reselect, which is true when the buyer had to pick a new username. A name step that fails still records the entry before the error answers, with name_action set to failed and force_username_reselect set to true when the new username pick was attempted.

30 requests per minute for each authenticated user, on the admin:shop:manage bucket.

POST/v1/admin/shop/orders/{order_id}/boost-refundAudit reason

Marks a delivered Payriff boost order refunded once its payment was refunded in the Payriff panel, or taken back by the bank as a chargeback, and returns the shop admin order object. Requires shop:manage and an audit reason. Payriff reports no refund or chargeback, so this route is the only way such a boost slot ends early.

The order must be a boost:30 order that is granted, or already refunded. A Paddle order is refused: its refunds and chargebacks end the slot by themselves. The route is safe to repeat.

FieldTypeDescription
order_idsnowflakeThe ID of the order

The body is optional.

FieldTypeDescription
chargeback?booleantrue when the bank took the payment back: the buyer’s purchases are disabled too
StatusBodyCondition
200shop admin order objectThe order is refunded
400error responseThe order cannot be refunded here, or the audit reason is missing, returned as INVALID_FORM_BODY
403error responseACL evaluation denies the request
404error responseNo order has this ID

The order’s bought boost slot ends first, and the boost it is on comes off its guild; a boost that races it fails with 409 CONFLICT. A slot that ended this way is kept, so a late delivery of the order cannot add it again. With chargeback, the buyer gets the PURCHASE_DISABLED premium flag, as an approved Paddle chargeback gives it, and later orders return 403 PREMIUM_PURCHASE_BLOCKED; lift it by hand if the chargeback is reversed. Then the order becomes refunded with a compare-and-set, so a refunded boost order never keeps its slot.

The operation records one Admin audit entry with action refund_shop_order, target type shop_order, the order ID as target ID, and metadata keys sku, status_old, user_id and chargeback. A step that fails after the slot ended still records the entry before the error answers, with status_step set to failed; a repeat finishes the order.

30 requests per minute for each authenticated user, on the admin:shop:manage bucket.