Skip to content
Coritan Docs

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.

View as Markdown

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.

GET /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.
  1. List what you sell:

    Shell
    curl "https://api.coritan.com/api/v1/orgs/acme/storefront/products?category=minecraft"
    

    The answer holds products and org_name. Filter with category (minecraft, games, cloud or addons) or game, and page with page and per_page (up to 200, default 100). A product backed by a server or an instance also says whether it can be ordered right now, in is_orderable_now and stock_status (in_stock or out_of_stock), with the reason in availability_reason.

  2. Open one product by its slug to get its plans:

    Shell
    curl "https://api.coritan.com/api/v1/orgs/acme/storefront/products/survival-smp"
    

    Each plan in pricing has the id you order with, its name, billing_cycle, price, setup_fee and currency. config_schema describes the options the product asks for.

A 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.

Shell
curl "https://api.coritan.com/api/v1/orgs/acme/storefront/server-addons?org_product_id=12"
JSON
{
  "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.

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.

  1. Optionally, check a coupon code for the plan:

    Shell
    curl "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 is true, amount_before and amount_after show the first invoice's total. When it is false, message says why. To check the code over a server order that carries add-ons and extras, see Preview a coupon over the whole order.

  2. Place the order as the customer:

    Shell
    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-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_id The product and the plan. Both are required.
    hostname A name for the service, up to 100 characters.
    config The options the product's config_schema asks for, such as a location.
    accepted_terms Must be true for a server, an instance or an external server. We record the acceptance with the invoice.
    terms_url The terms the customer accepted. Defaults to the Terms of service URL in your settings.
    turnstile_token The Turnstile answer. A free order needs it when turnstile.enabled is true.
    coupon_code A coupon, taken off the first invoice. A code that does not apply refuses the order.
    addons A server plan only. The add-ons the customer picked, each as {"kind": "container_cpu", "quantity": 2, "billing_cycle": "monthly"}. kind and quantity follow GET /storefront/server-addons, and billing_cycle is optional.
    extras A server plan only. The extras the customer picked, each as {"org_product_id": 52, "org_pricing_id": 140} from the extras list.
    attribution, tracking Where 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, config takes location (a location code) or location_id, template_id (required, one of GET /storefront/vps-templates) and ssh_keys (optional, one public key per line). The plan sets the instance's vCPU, memory, disk and traffic. A size sent in config is ignored, so the instance is always the size the invoice is for.

    For Object Storage, config takes location_id, which is required and comes from GET /portal/object-storage/regions, and bucket_name, which is optional (Order Object Storage for a customer).

    The answer has service_id, invoice_id, invoice_number, total, currency, due_date, requires_payment and services, the list of services the order created. status is pending_payment until the invoice is paid, or provisioning when there is nothing to pay. Send the same Idempotency-Key when you retry, and the same order comes back instead of a second one.

  3. 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 server

A server order can carry the add-ons and extras that GET /storefront/server-addons lists, so the customer buys them in the same checkout.

Shell
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_id is the server, and invoice_id and requires_payment describe 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_id and requires_payment describe the second invoice, service_id is still the server, and server_invoice_id and server_invoice_number are 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_id and server_invoice_number name the server's own invoice on both plans. On a paid plan they equal invoice_id and invoice_number. Use the number when you record a free order, because on a free plan invoice_number is 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, addon or extra), org_product_id, name, amount and billing_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 order

GET /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.

Shell
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 invoice

A 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:

Shell
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_name in the line's config.
  • It is priced in the same currency as the other lines.

A 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:

JSON
{
  "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:

JSON
{"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 from

Every 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 you

After 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.

Shell
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:

Shell
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.

POST /storefront/checkout/{invoice_id} starts a payment for the customer's unpaid or overdue invoice and returns a checkout_url to send them to.

Shell
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 form
  • GET /storefront/status returns 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 in nodes has queued_starts: the number of servers waiting to start on that host when more than three are waiting, and null otherwise.
  • GET /storefront/notices returns the notices for visitors who are not signed in. Signed-in customers get theirs from GET /portal/notices, which can target paying customers.
  • POST /storefront/contact sends 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 takes name, email and message, with kind set to general or bare_metal_quote and optional company, location, cpu, ram, storage, quantity and timeline. Leave the website field 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.

Your 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.

Product not found or Pricing 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: true once the customer has ticked your terms. A basket with a server needs it too, and answers 422 with this message in detail.errors.
Order already in progress
The same order with the same Idempotency-Key is still being placed. Wait for the first request to finish.
409 with free_limit_reached, or 503 with free_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 active or 403 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.
422 with errors
The options in config cannot 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.line says which line.
A server line holds exactly one server. Add a line for each server.
A server line has a quantity above 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. or Your basket already has a server at hub-one. Give each server its own address.
Two server lines share a hostname or a subdomain.
survival-smp is already taken. Choose another name for this server.
The subdomain is in use. Check a name first with POST /storefront/gameproxy/availability.
This item is no longer for sale. Remove it from your basket. or This 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. or Your 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.
422 with coupon
The coupon does not apply to the basket, and detail.errors says why. The customer can check out without it.
429 with rate_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-After and try again.
Invoice not found or not payable or No 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 Origin header we can build them from.
429 Too many inquiries. Try again shortly.
One address sent too many contact messages in a short time.

API operations on this page

MethodPathWhat it does
POST/api/v1/orgs/{org_slug}/storefront/basket/checkoutBuy a basket of store items and servers on one invoice
GET/api/v1/orgs/{org_slug}/storefront/brandingPublic 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/contactPublic contact / bare-metal quote intake
GET/api/v1/orgs/{org_slug}/storefront/coupons/previewWhat a code would take off this plan's first invoice, before ordering
GET/api/v1/orgs/{org_slug}/storefront/currenciesThe currencies customers can pay in, with the rate to show prices in each
POST/api/v1/orgs/{org_slug}/storefront/external-servers/probeCustomer: check a backend before ordering ("Test connection")
GET/api/v1/orgs/{org_slug}/storefront/external-servers/regionsPublic: edge regions an external server can be served from, and the default
POST/api/v1/orgs/{org_slug}/storefront/gameproxy/availabilityStorefront availability
POST/api/v1/orgs/{org_slug}/storefront/gameproxy/name-suggestionsStorefront name suggestions
GET/api/v1/orgs/{org_slug}/storefront/gameproxy/policyPublic: whether Minecraft orders auto-attach gameproxy + base domain
GET/api/v1/orgs/{org_slug}/storefront/locationsThe locations this storefront can sell a server in, optionally with capacity for a module
GET/api/v1/orgs/{org_slug}/storefront/noticesLive notices for everyone, for the dashboard bar when nobody is signed in
POST/api/v1/orgs/{org_slug}/storefront/orderAuthenticated customer places an order for a product
GET/api/v1/orgs/{org_slug}/storefront/orders/{invoice_id}/heard-aboutWhether 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-aboutSave the customer's answer to "How did you first hear about us?" for an order
GET/api/v1/orgs/{org_slug}/storefront/productsPublic 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-addonsPublic endpoint: the add-ons and extras a checkout can offer with a new server
GET/api/v1/orgs/{org_slug}/storefront/specializationsPublic game/app specialization catalog for order UX
GET/api/v1/orgs/{org_slug}/storefront/specializations/{slug}/composeStorefront compose specialization
GET/api/v1/orgs/{org_slug}/storefront/timezonesZones a schedule may be set to, and the old names for them
GET/api/v1/orgs/{org_slug}/storefront/statusPer-location and per-node platform health, for the public status page
GET/api/v1/orgs/{org_slug}/storefront/vps-templatesReady OS templates for Cloud / VPS storefront orders