Skip to main content

Ordering

This guide explains the relationship between the catalog, orders, invoices, and services. If you just want the end-to-end happy path, read Provisioning a VPS first; come back here when you need to understand the order lifecycle in depth.

Mental model​

catalog → preview → order → invoice → service → server
  1. Catalog — read-only list of what you can buy.
  2. Preview — compute the total without committing. Returns a quote_token.
  3. Order — created with the preview's quote_token; charges the invoice.
  4. Invoice — generated automatically by the order. Pay it (or it auto-pays).
  5. Service — the billing record for a recurring product. One per line item.
  6. Server — the actual provisioned VPS (server-type products only).

Each of these is a separate resource with its own endpoint. The order is the entry point; the others get created as side effects.

The order state machine​

┌───────────────────────────────────────┐
│ ▼
createOrder ─► pending ─► (invoice paid) ─► active ─► (service ids present)
│ │
│ └── (fraud check fails) ─► fraud (manual review)
│ │
│ └── (timeout, customer never paid) ─► cancelled
│
└── (you call cancelPendingOrder) ─► cancelled

Terminal states are active and cancelled. fraud means we're looking into it — we don't expose which heuristic triggered, since that would be useful to bad actors. Open a ticket if you believe a fraud flag is wrong.

quote_token — locking pricing​

Catalog prices can change between when you call preview and when you call create. To protect against this, previewOrder returns a quote_token valid for 5 minutes. Pass it to createOrder and the order uses the quoted total even if catalog pricing changed in the window.

If you skip the token, createOrder recomputes pricing at order time. That's fine for one-shot scripts; for cart UIs always preview-then-create.

Idempotency​

Every POST order endpoint accepts an Idempotency-Key header. Same key + same body within 24h replays the cached response; same key + different body returns 409 idempotency_conflict.

Use a fresh UUID per logical order — not per HTTP retry. The point of idempotency is "I clicked the button and got a network error — was the order placed?" not "I want to deduplicate identical orders."

Cancelling​

  • cancelPendingOrder — for orders that haven't been paid + provisioned. Voids the invoice. Free.
  • cancelService — for already-active services. Different rules: refunds, end-of-cycle vs immediate, fees. See the services guide.

If the order is active, cancelPendingOrder returns 409. You want cancelService on the service id in order.service_ids.

What auto_accept does​

Our billing engine normally runs provisioning on a periodic cron, even after you pay the invoice. That's the customer-portal behaviour. The API offers auto_accept: true on createOrder which forces synchronous provisioning after the invoice auto-pays — you get back an operation to poll immediately instead of waiting for the next cron tick.

Use it for paid-up-front flows. Skip it if you want the customer to pay manually (the operation will still run automatically once InvoicePaid fires).

POST /servers vs POST /orders​

POST /servers is a shortcut over POST /orders for the one-VPS-one-plan case. Internally it builds an order with auto_accept set and returns the operation directly. Use /orders when:

  • You're ordering multiple products at once
  • You need addons or a promo code
  • You want to defer payment (no auto_accept)
  • You need to preview-then-create against a quote_token

For everything else, /servers is simpler.

Errors specific to ordering​

HTTPcodeMeaningAction
402payment_requiredAuto-pay failed (insufficient credit, declined card)Update payment method, retry, or pay the invoice manually
403step_up_requiredDestructive verb without X-Confirm-TokenRun vpsctl auth step-up, retry
403kyc_requiredKYC not complete for high-value ordersComplete KYC at my.vps-server.host
409idempotency_conflictSame Idempotency-Key, different bodyUse a fresh key
422validation_failedBad product_id, billing_cycle, etc.Check the response's errors[] array
429rate_limitedToo many previewOrder calls (more aggressive limit)Back off

See errors for the full envelope.

What's next​