Show your catalogue with the Storefront API
Read your organization's branding, products, locations and currencies without signing in, and take orders from your site.
The Storefront API gives your site what every visitor sees: your branding, your products and their plans, the locations and software a customer can choose, and your status page. Reading it needs no credential. Placing an order, checking out a basket, previewing a coupon and paying an invoice need a signed-in customer's token.
Before you begin
Section titled Before you begin- Set up at least one product with an active plan. See Set up products and pricing.
- Fill in your branding on the Settings tab. See Change organization settings.
- To take orders, sign customers in first. See Sign customers in to your storefront.
Show your brand
Section titled Show your brandGET /storefront/branding returns what your pages need on every load: org_name, company_name, logo_url, favicon_url, primary_color, secondary_color, support_email, terms_url, privacy_url and default_currency. Colours you have not set come back as #1FA37A and #12141A, and the currency as USD.
It also carries a block for each optional storefront feature, so your site can show only what is switched on:
| Block | What it tells your site |
|---|---|
turnstile |
Whether to show the challenge, and its public site_key. When your organization uses Wicket it also has provider (wicket) and script_url, the address to load the widget's script from. Without a provider the challenge is a Cloudflare Turnstile widget. |
maintenance |
Whether to show a maintenance page. Staff signed in to the staff console still see the site. |
coupons |
Whether customers can enter a coupon code. |
free_tier |
Whether free servers can be ordered now, and the ads shown with them. |
community_forum, community_knowledgebase |
Whether the forum and guides are on. |
community_chat |
Whether to show your Discord chat, and the invite link. See Connect a Discord server. |
public_server_list |
Whether the public server list is on, and its categories and tags. |
tags |
The analytics and ad tags to load: the public ID of each platform that is on (ga4, google_ads, meta, tiktok, microsoft, reddit, x, gtm), consent (banner and privacy_path) and the hosts the brand answers on. See Add analytics and ad tags. |
newsletter |
Whether to show the newsletter form: enabled, the consent_text to show beside it and whether it needs the Turnstile challenge. |
referrals |
Whether the referral program is on, so the account page can show its panel. |
google_analytics, trustpilot |
The Google Analytics 4 tag in its older shape (enabled and measurement_id, which tags replaces), and when to ask for a Trustpilot review. |
List products and plans
Section titled List products and plansList what you sell:
Shellcurl "https://api.coritan.com/api/v1/orgs/acme/storefront/products?category=minecraft"The answer holds
productsandorg_name. Filter withcategory(minecraft,games,cloudoraddons) orgame, and page withpageandper_page(up to 200, default 100). A product backed by a server or an instance also says whether it can be ordered right now, inis_orderable_nowandstock_status(in_stockorout_of_stock), with the reason inavailability_reason.Open one product by its slug to get its plans:
Shellcurl "https://api.coritan.com/api/v1/orgs/acme/storefront/products/survival-smp"Each plan in
pricinghas theidyou order with, itsname,billing_cycle,price,setup_feeandcurrency.config_schemadescribes the options the product asks for.
Offer add-ons with a server
Section titled Offer add-ons with a serverA checkout can sell a server's add-ons and Priority Support beside the plan, before the server exists. GET /storefront/server-addons needs no sign in and answers what your store sells. Send org_product_id with the server plan the customer is looking at, so the answer leaves out what does not suit it.
curl "https://api.coritan.com/api/v1/orgs/acme/storefront/server-addons?org_product_id=12"
{
"offers": [
{
"kind": "container_cpu",
"org_product_id": 41,
"name": "Extra CPU",
"description": "One more vCPU on one App Deployment server.",
"adds": "+1 vCPU",
"unit_label": "vCPU",
"max_quantity": 8,
"per_unit": true,
"games": null,
"prices": [
{"org_pricing_id": 118, "billing_cycle": "monthly", "amount": "1.99", "currency": "USD"},
{"org_pricing_id": 119, "billing_cycle": "quarterly", "amount": "5.61", "currency": "USD"}
]
}
],
"extras": [
{
"kind": "priority_support",
"org_product_id": 52,
"name": "Priority Support",
"description": "Priority Support puts your tickets at the front of the queue.",
"benefits": ["Your new tickets open at high priority.", "We aim to reply to your first message within 1 hour."],
"prices": [{"org_pricing_id": 140, "billing_cycle": "monthly", "amount": "4.99", "currency": "USD"}]
}
],
"snapshots_on": true
}
Each entry in offers is one add-on:
| Field | What it holds |
|---|---|
kind |
container_cpu, container_auto_backups or server_management. You order the add-on with it. |
org_product_id, name, description |
The store's product, its name and the first sentence of its description. |
adds, unit_label |
What one unit gives the server, such as +1 vCPU, and what to call one unit. |
max_quantity, per_unit |
The most one server takes, and whether the customer picks a quantity. An add-on sold one at a time has per_unit set to false and max_quantity set to 1. |
games |
The games the add-on is sold for, or null for every game. |
prices |
The price of one unit for each billing period you sell, with org_pricing_id, billing_cycle, amount and currency. |
extras lists your active Priority Support products with their benefits and prices. It is empty when your store sells none. snapshots_on is false while snapshots are off on the platform, and the answer then leaves automatic backups out.
The answer leaves out an add-on that is switched off, is not flagged as a server add-on, or has no price above 0. With org_product_id, it also leaves out Extra CPU for a plan that has no CPU limit, and an add-on whose games do not include the plan's game. Server Management is sold for Minecraft by default. A plan with no game takes every add-on. org_product_id that is not an active server plan of your store answers 404, and another store's product is never listed. The answer is public and cached for up to 30 seconds.
Offer locations and software
Section titled Offer locations and software| Route | What it returns |
|---|---|
GET /storefront/locations |
The data centre locations. Add module=container or module=vps, and optionally hardware_tier_id, to learn which have room: each location then has available, reason and usage_percent. usage_percent is the highest of CPU, memory and disk use across that location's servers, from 0 to 100, or null when it is unknown. |
GET /storefront/vps-templates |
The operating systems a Cloud Compute order can use, with each one's minimum CPU, memory and disk. |
GET /storefront/specializations |
The games and apps a server can run. product_slug narrows the list to the ones that product features, in its order. |
GET /storefront/specializations/{slug}/compose |
The versions and choices one game or app offers. When the site that lists the versions is down and we have no earlier list from the last 24 hours, it answers 503 with Software versions are unavailable right now. Try again in a minute. and a Retry-After of 60 seconds. |
GET /storefront/currencies |
The currencies customers can pay in, US dollars first, with each one's rate against 1 USD, decimal_places and name. A currency is listed only when it has a rate and at least one enabled payment account takes it. It needs no token and shows no margin. |
GET /storefront/timezones |
The time zones a customer can pick for scheduled tasks. |
For Minecraft, GET /storefront/gameproxy/policy says whether an order gets a join address automatically, on which base domain and port. POST /storefront/gameproxy/name-suggestions suggests free names (count up to 20), and POST /storefront/gameproxy/availability?subdomain=... checks one. See Customise the game proxy messages.
For an external server, GET /storefront/external-servers/regions lists the regions it can be served from. POST /storefront/external-servers/probe checks that the customer's own server answers, from upstream_host, upstream_port and mode. It needs the customer's token and is rate limited.
Take an order
Section titled Take an orderOptionally, check a coupon code for the plan:
Shellcurl "https://api.coritan.com/api/v1/orgs/acme/storefront/coupons/preview?code=SPRING&org_product_id=12&org_pricing_id=31" \ -H "Authorization: Bearer $CUSTOMER_TOKEN"The answer has
valid. When it istrue,amount_beforeandamount_aftershow the first invoice's total. When it isfalse,messagesays why. To check the code over a server order that carries add-ons and extras, see Preview a coupon over the whole order.Place the order as the customer:
Shellcurl -X POST "https://api.coritan.com/api/v1/orgs/acme/storefront/order" \ -H "Authorization: Bearer $CUSTOMER_TOKEN" \ -H "Content-Type: application/json" \ -H "Idempotency-Key: 3f6c2a9e-order-1" \ -d '{"org_product_id": 12, "org_pricing_id": 31, "hostname": "survival-smp", "accepted_terms": true, "config": {}}'Field What it holds org_product_id,org_pricing_idThe product and the plan. Both are required. hostnameA name for the service, up to 100 characters. configThe options the product's config_schemaasks for, such as a location.accepted_termsMust be truefor a server, an instance or an external server. We record the acceptance with the invoice.terms_urlThe terms the customer accepted. Defaults to the Terms of service URL in your settings. turnstile_tokenThe Turnstile answer. A free order needs it when turnstile.enabledistrue.coupon_codeA coupon, taken off the first invoice. A code that does not apply refuses the order. addonsA server plan only. The add-ons the customer picked, each as {"kind": "container_cpu", "quantity": 2, "billing_cycle": "monthly"}.kindandquantityfollowGET /storefront/server-addons, andbilling_cycleis optional.extrasA server plan only. The extras the customer picked, each as {"org_product_id": 52, "org_pricing_id": 140}from theextraslist.attribution,trackingWhere the buyer came from, for a site that cannot forward the browser's cookies. See Record where the order came from. For a Cloud Compute instance,
configtakeslocation(a location code) orlocation_id,template_id(required, one ofGET /storefront/vps-templates) andssh_keys(optional, one public key per line). The plan sets the instance's vCPU, memory, disk and traffic. A size sent inconfigis ignored, so the instance is always the size the invoice is for.For Object Storage,
configtakeslocation_id, which is required and comes fromGET /portal/object-storage/regions, andbucket_name, which is optional (Order Object Storage for a customer).The answer has
service_id,invoice_id,invoice_number,total,currency,due_date,requires_paymentandservices, the list of services the order created.statusispending_paymentuntil the invoice is paid, orprovisioningwhen there is nothing to pay. Send the sameIdempotency-Keywhen you retry, and the same order comes back instead of a second one.Send the customer to pay, as the next section shows. We create the resource once the invoice is paid.
Order add-ons and extras with a server
Section titled Order add-ons and extras with a serverA server order can carry the add-ons and extras that GET /storefront/server-addons lists, so the customer buys them in the same checkout.
curl -X POST "https://api.coritan.com/api/v1/orgs/acme/storefront/order" \
-H "Authorization: Bearer $CUSTOMER_TOKEN" \
-H "Content-Type: application/json" \
-H "Idempotency-Key: 3f6c2a9e-order-2" \
-d '{"org_product_id": 12, "org_pricing_id": 31, "hostname": "survival-smp", "accepted_terms": true,
"config": {}, "addons": [{"kind": "container_cpu", "quantity": 2}],
"extras": [{"org_product_id": 52, "org_pricing_id": 140}]}'
- On a paid plan, the server, its add-ons and the extras are on one invoice.
service_idis the server, andinvoice_idandrequires_paymentdescribe that invoice. - On a free plan, the free server keeps its own invoice, which we settle at once, so the server starts without a payment. The add-ons and extras go on a second invoice.
invoice_idandrequires_paymentdescribe the second invoice,service_idis still the server, andserver_invoice_idandserver_invoice_numberare the first invoice. An add-on on a free server is billed on its own, as Add-ons on a free server explains. server_invoice_idandserver_invoice_numbername the server's own invoice on both plans. On a paid plan they equalinvoice_idandinvoice_number. Use the number when you record a free order, because on a free planinvoice_numberis the second invoice's.- A coupon discounts the plan only, and an add-on or an extra only when the coupon names that product.
- The customer can cancel one item that is still unpaid, and the invoice keeps the others: we take that item's lines off, keep the coupon's share of what is left and work out the tax again. When the customer cancels the server before paying, the add-ons and extras that were ordered with it end too, so no invoice is left for one of them alone. An extra the customer has paid for stays. Cancel a service has the rest.
- Every answer lists
services, one entry for each service the order created:service_id,kind(server,addonorextra),org_product_id,name,amountandbilling_cycle.
Each add-on kind and each extra product can be in the order once, so a customer who wants more Extra CPU sets quantity. On a paid plan, an add-on takes the plan's billing period and ignores billing_cycle. On a free plan, billing_cycle picks one of the add-on's prices and defaults to monthly, or to the add-on's first price when you have no monthly one. A billing_cycle that you name and have no price for is refused. An extra is one of your active Standalone products with one of its active plans, priced in the same currency as the server. A plan with a price of 0 is refused, because a free extra would skip the checks a free order goes through. Priority Support is the extra that GET /storefront/server-addons lists.
An extra the customer already has answers 409 with the same shape as the other refusals: detail.errors holds the sentence to show, detail.reason is already_held, and detail.kind and detail.org_product_id name the extra. An order of Priority Support on its own, not as an extra, answers 409 with detail set to the sentence alone, as it always did.
An order that carries addons or extras for a product that is not a server answers 422 with reason set to not_a_server_order. The server's own checks apply to each add-on as they do on the server's page, so GET /storefront/server-addons is the list to follow. quantity must be a whole number from 1 to 20. Any other value, such as 0, 21 or 1.5, is refused first with a plain validation 422 whose detail is a list of what is wrong and has no reason. A refused add-on or extra that passes that check answers 422 with the message to show in detail.errors, a detail.reason and, for an add-on, its detail.kind, and we create nothing:
detail.reason |
Meaning |
|---|---|
unknown_kind |
kind is not an add-on, or your store does not sell it for servers. |
duplicate_kind |
The order names the same add-on kind, or the same extra, twice. |
max_reached |
quantity is from 2 to 20 and more than a server takes, or an add-on sold one at a time has a quantity above 1. |
no_price_for_cycle |
You have no price for the add-on's billing period. |
not_available |
The plan has no CPU limit to add to, or the add-on is not sold for the plan's game. |
snapshots_disabled |
Automatic backups need snapshots, and they are off. |
extra_not_available |
The extra's product or plan is not on sale, or its plan has no price above 0. |
already_held |
The customer already has the extra, and your store sells it once for each customer. The answer is 409, not 422, and detail also holds kind (priority_support for Priority Support, extra for another extra) and the extra's org_product_id. |
currency_mismatch |
The add-on or extra is priced in another currency than the server. |
Preview a coupon over the whole order
Section titled Preview a coupon over the whole orderGET /storefront/coupons/preview quotes the plan alone. Add addons or extras, or both, and it quotes the coupon over the plan, the add-ons and the extras, the way POST /storefront/order will for the same selection.
curl "https://api.coritan.com/api/v1/orgs/acme/storefront/coupons/preview?code=SPRING&org_product_id=12&org_pricing_id=31&addons=container_cpu:2,container_auto_backups:1&extras=140" \
-H "Authorization: Bearer $CUSTOMER_TOKEN"
| Query | What it holds |
|---|---|
addons |
A comma list of kind:quantity, such as container_cpu:2,container_auto_backups:1. The quantity is optional and defaults to 1, and it is a whole number from 1 to 20. |
extras |
A comma list of the org_pricing_id of each extra's price, such as Priority Support's price for the plan's billing period. |
When the selection is valid, the answer has the same keys as a preview of the plan alone, with these changed or added:
| Key | What it holds |
|---|---|
discount |
What the coupon takes off the whole order, in the currency, with two decimals. |
amount_before |
The plan's first period plus every selected add-on and extra, before the discount. A string with two decimals. |
amount_after |
amount_before less discount. |
covered_product_ids |
The org_product_id of each line the coupon discounts. It holds the plan's own org_product_id when the coupon covers the plan. |
A coupon that names no product covers the plan only. A coupon that names products covers each line of those products, an add-on or an extra among them, so a coupon that names only Extra CPU is valid here. A fixed coupon never takes off more than the lines it covers cost. On a free plan the coupon applies to the second invoice that holds the add-ons and extras. A selection the order would refuse answers 200 with {"valid": false, "reason", "message"}, using the reasons in the table above, and so does a coupon the order would refuse. An addons or extras value that is not in the form above answers 422 with a sentence in detail.errors. Without addons and extras, the answer is the plan-alone answer.
Take a basket of store items and servers on one invoice
Section titled Take a basket of store items and servers on one invoiceA basket lets a customer buy several of your Standalone products and servers at once and pay one invoice for all of them, such as a rank for one player, credit packs for another and a new server. A server is a line of the basket as long as its product is a Platform product on a container plan. A VPS, an IP address or another platform product is ordered on its own, as Take an order shows.
Check out the basket as the customer:
curl -X POST "https://api.coritan.com/api/v1/orgs/acme/storefront/basket/checkout" \
-H "Authorization: Bearer $CUSTOMER_TOKEN" \
-H "Content-Type: application/json" \
-H "Idempotency-Key: 7d1e4b20-basket-1" \
-d '{"accepted_terms": true, "items": [
{"org_product_id": 14, "org_pricing_id": 40, "config": {"minecraft_name": "Steve", "minecraft_edition": "java"}},
{"org_product_id": 15, "org_pricing_id": 42, "quantity": 3, "config": {"minecraft_name": "Alex", "minecraft_edition": "java", "gift_from": "Sam"}},
{"org_product_id": 21, "org_pricing_id": 77, "hostname": "Survival SMP",
"config": {"subdomain": "survival-smp", "location_id": 3, "specialization_slug": "minecraft-paper", "software_version": "latest"}}
]}'
| Field | What it holds |
|---|---|
items |
From 1 to 20 lines. Each line names a product (org_product_id) and one of its plans (org_pricing_id), with a quantity from 1 to 10 (default 1) and the answers to the product's order form in config. A server line also has a hostname. |
accepted_terms |
true once the customer has ticked your terms. We record one acceptance, with the invoice. A basket with a server needs it. |
terms_url |
The terms the customer accepted. Defaults to the Terms of service URL in your settings. |
coupon_code |
A coupon of up to 40 characters, taken off the invoice. A code that does not apply refuses the basket. |
turnstile_token |
The Turnstile answer. A basket with a free server needs it when turnstile.enabled is true. |
A server line also takes addons, as a single order does (Order add-ons and extras with a server). To sell Priority Support in a basket, add it as a line of its own.
Every line must follow these rules:
- It is one of your Standalone products, or a Platform product on a container plan (a server), with a plan of that product that is on sale.
- A plan that renews, such as a monthly rank, takes a quantity of 1, and a basket holds at most one for each player. The player is the
minecraft_namein the line'sconfig. - It is priced in the same currency as the other lines.
Servers in the basket
Section titled Servers in the basketA server line holds exactly one server: quantity is 1. Its hostname is the server's name, and its config takes the same answers as POST /storefront/order:
| Key | What it holds |
|---|---|
subdomain |
The name players join at, such as survival-smp for survival-smp.example.gg. It must be free. We pick one when it is left out. |
location_id |
Where the server runs, from GET /storefront/locations. |
specialization_slug |
The software, from GET /storefront/specializations. We pick your featured software when it is left out. |
software_version |
A version of that software, or latest. |
free_tier |
true for the free server plan. |
We check each server line the way a single server order is checked, so a basket never accepts what an order would refuse: the plan and the location are in stock, the name follows the server name rules, the answers fit the plan, and the basket stays within your service limit. A server for a free plan also passes the free plan's rules: the Turnstile answer, one free server for each customer and the limit for each network every day. The basket adds three rules of its own. It holds at most one free server, every server has a name and an address of its own, and the address is not taken by another server.
We create each server's service and its pending platform service at checkout, and its invoice lines are on the basket's one invoice, such as Standard 4 GB, Monthly. The server starts when the invoice is paid, together with the other services on it. A basket that holds only a free server costs nothing, so we settle it at once and the server starts without a payment.
The answer:
{
"invoice_id": 5022,
"invoice_number": "INV-1043",
"subtotal": 69.96,
"discount": 0.0,
"tax": 13.992,
"total": 83.952,
"currency": "USD",
"requires_payment": true,
"service_ids": [7101, 7102, 7103, 7104],
"status": "pending_payment",
"due_date": "2026-10-15T14:03:00Z",
"coupon": null,
"replayed": false
}
We create one service for each item or server bought, so the three packs above are three services, and one invoice holds them all. service_ids lists them in the order of the lines. Each item has a line on the invoice that names the product, its plan and the player, such as VIP rank, Monthly, for Steve, and a setup fee has a line of its own, such as VIP rank setup fee, for Steve. subtotal is the price of the items with their setup fees, before the coupon. tax is your tax rate on the subtotal less the discount, and total is what the customer pays. Your webhooks receive order.created for each service, and we email the customer the invoice.
Then send the customer to pay, as Pay an invoice shows. Paying the invoice starts every service on it and sets up every server. Your game servers then deliver each store item and record how it went, as Record a delivery describes, and the customer follows the items and the servers as Follow the delivery of store items and servers shows.
When the basket costs nothing, we settle the invoice at once and start its services, and send no invoice email: requires_payment is false and status is provisioning.
Each customer can check out 20 baskets in 10 minutes. Baskets that cost nothing also share the limit on free orders, 12 for each customer in 10 minutes. Past either limit we answer 429.
We work out a coupon once for the whole basket, on the lines whose product it applies to, or on every line when it names no product. A percentage coupon takes its share of those lines, and a fixed coupon comes off once. As with an order, the coupon comes off this invoice only, and the services renew at their plan's price.
A refused line answers 422 with the message to show in detail.errors and the position of the line in detail.line, counting from 0. A refused coupon answers 422 with its reason in detail.coupon, such as expired or wrong_product. Either way, we create nothing:
{"detail": {"errors": ["Your basket already has a free server. Keep one of them."], "line": 1}}
The free plan's own refusals keep their status and their detail.error and detail.message, with detail.line added, so a page can show them on the line they came from: 409 with free_limit_reached, 503 with free_tier_paused, and 403 with turnstile_failed, free_network_not_allowed, free_network_blocked or email_domain_blocked, and 422 with server_name_blocked. A basket that is refused as a whole, such as one with a server and no accepted_terms, answers 422 with detail.errors and no line.
Send the same Idempotency-Key, up to 128 characters, when you retry, and the same answer comes back instead of a second invoice. When the same basket arrives again within two minutes without that key, we answer with the invoice the first one opened and replayed: true, unless the customer has paid it. Neither answer counts against the limits above.
Checking out adds the player each store line names to the customer's players (GET /portal/players), except on a gift: a line whose config has gift_from or gift_message is for somebody else's player.
To collect the customer's name and address at checkout, see Save billing details.
Record where the order came from
Section titled Record where the order came fromEvery order and basket checkout writes one attribution record on its invoice. A browser that calls the API on your storefront's own host sends its tracker cookies (_ct_attr, _ct_vid, _ct_sid and _ct_consent) and the ad cookies (_fbp, _fbc, _ga and _ga_<stream>) with the order, and we read them. A site that serves its own pages from another host forwards those cookies, or sends the same facts in the order's body:
| Field | What it holds |
|---|---|
attribution |
The _ct_attr cookie's value as JSON: {"first": touch, "last": touch}. Each touch has type, source, medium, campaign, term, content, utm_id, referrer, landing, click (a map of click IDs) and at (seconds since the epoch). |
tracking |
fbp, fbc, ga_client_id and ga_session_id. |
A cookie always wins over the same fact in the body. The body never sets the consent choice. Each of the two objects over 4 KB is ignored, and a field with the wrong shape is dropped, so a bad value never refuses the order. A request with no cookies and no body still gets a record, with the direct channel. We read the page's own address from the Referer header as a last resort, so a utm_ parameter on your checkout page still counts.
Ask how the customer heard about you
Section titled Ask how the customer heard about youAfter an order, ask "How did you first hear about us?" once. Both routes need the customer's token, and the invoice must be one of that customer's own storefront orders. Any other invoice, an invoice your team made, and an invoice that does not exist all answer 404.
curl "https://api.coritan.com/api/v1/orgs/acme/storefront/orders/5021/heard-about" \
-H "Authorization: Bearer $CUSTOMER_TOKEN"
GET answers answered, which is true once the order has an answer, and the options to offer, each with a value and a label, in the order to show them. POST saves the answer:
curl -X POST "https://api.coritan.com/api/v1/orgs/acme/storefront/orders/5021/heard-about" \
-H "Authorization: Bearer $CUSTOMER_TOKEN" \
-H "Content-Type: application/json" \
-d '{"source": "forum", "detail": "The Admincraft forum"}'
source is one of google_search, google_ads, bing_search, reddit, forum, youtube, discord, instagram, facebook, tiktok, x, friend or other, in any letter case. Any other word answers 422 and lists the words that work. detail is optional and takes up to 200 characters. We fold its spaces and take out control characters.
The answer is {"saved": true}. The first answer stays: asking again changes nothing and answers {"saved": false}. A support session cannot answer for the customer and gets 403. One order takes 20 answers a day, and one address 120 an hour, before the route answers 429.
Pay an invoice
Section titled Pay an invoicePOST /storefront/checkout/{invoice_id} starts a payment for the customer's unpaid or overdue invoice and returns a checkout_url to send them to.
curl -X POST "https://api.coritan.com/api/v1/orgs/acme/storefront/checkout/5021?currency=EUR" \
-H "Authorization: Bearer $CUSTOMER_TOKEN" \
-H "Origin: https://shop.example.net"
currency defaults to the customer's own, and gateway_name to the gateway we pick for that currency and the customer's country. return_url and cancel_url must be absolute http or https addresses. Leave them out and we build them from the request's Origin: /billing/invoices/{invoice_id}?paid=1 and ?cancelled=1 on your site. The answer also lists the available_gateways for that currency.
Show status, notices and a contact form
Section titled Show status, notices and a contact formGET /storefront/statusreturns the health of each location, the incidents and maintenance your staff post, and the state of the services your storefront relies on. Resolved incidents stay for 24 hours. Each host innodeshasqueued_starts: the number of servers waiting to start on that host when more than three are waiting, andnullotherwise.GET /storefront/noticesreturns the notices for visitors who are not signed in. Signed-in customers get theirs fromGET /portal/notices, which can target paying customers.POST /storefront/contactsends a visitor's message to your Support email, or to your SMTP from address when there is none, and emails the visitor an acknowledgement. The message to you has the visitor's address as its Reply-To, so replying to it answers the visitor. With neither address set, nobody is emailed. It takesname,emailandmessage, withkindset togeneralorbare_metal_quoteand optionalcompany,location,cpu,ram,storage,quantityandtimeline. Leave thewebsitefield empty: we treat a filled one as a bot. When the visitor sends their customer token, the message also opens a sales conversation in your support inbox.
Result
Section titled ResultYour site shows your brand and catalogue to every visitor, and a signed-in customer can order and pay. The order appears on the Services tab and in the customer's invoices, and your webhooks receive order.created. A basket appears as one invoice with a service for each item or server, and order.created arrives for each service.
Troubleshooting
Section titled TroubleshootingProduct not foundorPricing plan not found- The product is not available, or the plan is turned off or belongs to another product.
Accept the Terms of Service to place this order- Send
accepted_terms: trueonce the customer has ticked your terms. A basket with a server needs it too, and answers422with this message indetail.errors. Order already in progress- The same order with the same
Idempotency-Keyis still being placed. Wait for the first request to finish. 409withfree_limit_reached, or503withfree_tier_paused- The customer holds as many free servers as allowed, or free servers are paused for now. Offer a paid plan.
403 Customer account is not activeor403 Service limit reached (500/500)- The customer is suspended or closed, or your organization holds as many services as it may. Ask support to raise the limit.
422witherrors- The options in
configcannot be ordered, for example because the location has no room, or the coupon does not apply. The messages say why. This plan cannot be bought in a basket. Order it on its own.- A basket line names a platform product that is not a server on a container plan, such as a VPS or an IP address. Order it on its own with
POST /storefront/order.detail.linesays which line. A server line holds exactly one server. Add a line for each server.- A server line has a
quantityabove 1. Your basket already has a free server. Keep one of them.- A basket holds one free server. The second line, counting from 0, is the one refused.
Your basket already has a server named Hub. Give each server its own name.orYour basket already has a server at hub-one. Give each server its own address.- Two server lines share a
hostnameor asubdomain. survival-smp is already taken. Choose another name for this server.- The
subdomainis in use. Check a name first withPOST /storefront/gameproxy/availability. This item is no longer for sale. Remove it from your basket.orThis option is no longer for sale. Remove the item and add it again.- The product is hidden or belongs to another organization, or the plan is retired or belongs to another product.
VIP rank is a subscription, so this line can hold only one.orYour basket already has a subscription for Steve. Keep one of them.- A plan that renews takes a quantity of 1, and a basket holds at most one for each player.
Euro pack is priced in EUR and the rest of your basket in USD. Buy it on its own.- Every line of a basket must be priced in the same currency.
This Idempotency-Key was sent with a different basket. Send a new key with a new basket.- Send a new key whenever the basket changes.
422withcoupon- The coupon does not apply to the basket, and
detail.errorssays why. The customer can check out without it. 429withrate_limited- The customer tried to check out 20 baskets, or placed 12 free orders or baskets that cost nothing, in 10 minutes. Wait for the seconds in
Retry-Afterand try again. Invoice not found or not payableorNo balance remaining- The invoice belongs to another customer, or it is already paid.
return_url and cancel_url are required and must be absolute http(s) URLs ...- Send both, or send an
Originheader we can build them from. 429 Too many inquiries. Try again shortly.- One address sent too many contact messages in a short time.
Related
Section titled RelatedAPI operations on this page
| Method | Path | What it does |
|---|---|---|
POST | /api/v1/orgs/{org_slug}/storefront/basket/checkout | Buy a basket of store items and servers on one invoice |
GET | /api/v1/orgs/{org_slug}/storefront/branding | Public branding + legal links for the org storefront |
POST | /api/v1/orgs/{org_slug}/storefront/checkout/{invoice_id} | Create a payment checkout session for a specific invoice |
POST | /api/v1/orgs/{org_slug}/storefront/contact | Public contact / bare-metal quote intake |
GET | /api/v1/orgs/{org_slug}/storefront/coupons/preview | What a code would take off this plan's first invoice, before ordering |
GET | /api/v1/orgs/{org_slug}/storefront/currencies | The currencies customers can pay in, with the rate to show prices in each |
POST | /api/v1/orgs/{org_slug}/storefront/external-servers/probe | Customer: check a backend before ordering ("Test connection") |
GET | /api/v1/orgs/{org_slug}/storefront/external-servers/regions | Public: edge regions an external server can be served from, and the default |
POST | /api/v1/orgs/{org_slug}/storefront/gameproxy/availability | Storefront availability |
POST | /api/v1/orgs/{org_slug}/storefront/gameproxy/name-suggestions | Storefront name suggestions |
GET | /api/v1/orgs/{org_slug}/storefront/gameproxy/policy | Public: whether Minecraft orders auto-attach gameproxy + base domain |
GET | /api/v1/orgs/{org_slug}/storefront/locations | The locations this storefront can sell a server in, optionally with capacity for a module |
GET | /api/v1/orgs/{org_slug}/storefront/notices | Live notices for everyone, for the dashboard bar when nobody is signed in |
POST | /api/v1/orgs/{org_slug}/storefront/order | Authenticated customer places an order for a product |
GET | /api/v1/orgs/{org_slug}/storefront/orders/{invoice_id}/heard-about | Whether to ask "How did you first hear about us?" for an order, and the options to offer |
POST | /api/v1/orgs/{org_slug}/storefront/orders/{invoice_id}/heard-about | Save the customer's answer to "How did you first hear about us?" for an order |
GET | /api/v1/orgs/{org_slug}/storefront/products | Public endpoint: browse the organization's product catalog |
GET | /api/v1/orgs/{org_slug}/storefront/products/{product_slug} | Public endpoint: view a single product with all pricing tiers |
GET | /api/v1/orgs/{org_slug}/storefront/server-addons | Public endpoint: the add-ons and extras a checkout can offer with a new server |
GET | /api/v1/orgs/{org_slug}/storefront/specializations | Public game/app specialization catalog for order UX |
GET | /api/v1/orgs/{org_slug}/storefront/specializations/{slug}/compose | Storefront compose specialization |
GET | /api/v1/orgs/{org_slug}/storefront/timezones | Zones a schedule may be set to, and the old names for them |
GET | /api/v1/orgs/{org_slug}/storefront/status | Per-location and per-node platform health, for the public status page |
GET | /api/v1/orgs/{org_slug}/storefront/vps-templates | Ready OS templates for Cloud / VPS storefront orders |