Ordering guide
Order an instant server end to end: get ready, pick an offer, check the total with a dry run, order, and wait until the server is ready.
Before your first order
Ordering is off until the account owner sets it up. In the client area, on Settings → API:
- Make a key with the order permission. It needs a monthly spend limit for that key: a whole amount in the account's currency, 1 to 100000. Orders that would take the key over it in 30 days are refused. The limit cannot be raised later; make a new key instead. An IP list on ordering keys is strongly suggested.
- Turn on Ordering through the API (a recent sign-in and an emailed code, as for keys).
- Wait 24 hours. Ordering opens 24 hours after it was turned on and after a new ordering key was made. Until then orders answer
403 cooling_off, withdetails.retry_afterin seconds. - Add funds. Orders through the API are paid from account credit only. With
read_billing,GET /accountshows the balance.
Account limits
On top of each key's own monthly limit, the account has limits counted across all its keys over rolling windows:
| Limit | Default | details.cap when reached |
|---|---|---|
| Servers ordered in a day | 3 | servers_day |
| Servers ordered in 30 days | 10 | servers_month |
| Orders paid but not delivered yet, at once | 2 | open |
| Money spent in 30 days | the equivalent of €2,000 | money_month |
| Monthly renewals added in 30 days | the equivalent of €2,000 | renewal_month |
| The key's own monthly limit | set by the owner | key_month |
The account limits are set by RedSwitches for each account; the defaults are above. Contact support if you need a higher one. Raising a limit does not pause ordering. The key's own limit is set when the key is made and cannot be raised: make a new key instead.
Over a limit the order is refused with 403 cap_reached and nothing is ordered. The details say which limit, its value and its window:
{
"error": {
"code": "cap_reached",
"message": "This order would go over a limit of this key or of the account. Nothing was ordered.",
"request_id": "01JCX7Q2M8ZK4T6V9B3N5R1W2Y",
"details": { "cap": "servers_day", "limit": 3, "window": "day" }
}
}
For a money limit, limit is an amount in your account's currency, in minor units: {"cap": "money_month", "limit": {"amount": "200000", "currency": "EUR"}, "window": "30_days"} is 2,000.00 EUR in 30 days. window is day, 30_days, or open for orders paid but not delivered yet.
1. Pick an offer
curl -sS https://api.redswitches.com/v1/offers \
-H "Authorization: Bearer $REDSWITCHES_API_KEY"const { data: offers } = await rs.offers.list();
const offer = offers.find((o) => o.location?.city === "Amsterdam" && o.stock !== 0)!;
const os = offer.os.find((x) => x.name.startsWith("Ubuntu"))!;offers = client.offers.list().data
offer = next(o for o in offers if o.location and o.location.city == "Amsterdam")
os_choice = next(x for x in offer.os if x.name.startswith("Ubuntu"))offers, err := client.Offers.List(ctx)
if err != nil {
log.Fatal(err)
}
offer := offers.Data[0]
osID := offer.OS[0].ID{
"data": [
{
"id": "ofr_12",
"name": "Instant Dedicated Server E-2388G",
"location": { "city": "Amsterdam", "country": "NL" },
"cpu": "Intel Xeon E-2388G (8 cores, 3.2 GHz)",
"ram": "64 GB DDR4",
"disks": "2 x 960 GB NVMe SSD",
"port_speed": "1 Gbps",
"traffic": "50 TB",
"currency": "USD",
"prices": [{ "cycle": "monthly", "price": "129.00", "setup": "0.00" }],
"os": [{ "id": "os_31", "name": "Ubuntu 24.04" }],
"stock": "5+",
"as_of": "2026-11-15T09:30:00Z"
}
]
}
Offer prices are before tax. The amount you pay comes from the dry run.
2. Dry run
A dry run checks everything a real order checks (offer, OS, SSH key, stock, limits, credit) and returns the exact total. Nothing is ordered or held.
curl -sS -X POST "https://api.redswitches.com/v1/orders?dry_run=true" \
-H "Authorization: Bearer $REDSWITCHES_API_KEY" \
-H "Content-Type: application/json" \
-d '{"offer_id":"ofr_12","billing_cycle":"monthly","os_id":"os_31","ssh_key_id":"sshkey_7"}'const body = { offer_id: offer.id, billing_cycle: "monthly", os_id: os.id, ssh_key_id: "sshkey_7" } as const;
const quote = await rs.orders.create(body, { dryRun: true });
console.log("Will pay", quote.total.amount, quote.total.currency, "(minor units)");body = dict(offer_id=offer.id, billing_cycle="monthly", os_id=os_choice.id, ssh_key_id="sshkey_7")
quote = client.orders.create(**body, dry_run=True)
print("Will pay", quote.total.amount, quote.total.currency, "(minor units)")req := &redswitches.OrderCreateRequest{OfferID: offer.ID, BillingCycle: "monthly", OSID: osID, SSHKeyID: "sshkey_7"}
quote, err := client.Orders.Create(ctx, req, &redswitches.OrderOptions{DryRun: true}){
"dry_run": true,
"offer_id": "ofr_12",
"billing_cycle": "monthly",
"total": { "amount": "15480", "currency": "USD" },
"recurring": { "amount": "12900", "currency": "USD" },
"paid_from_credit": { "amount": "15480", "currency": "USD" }
}
"15480" is 154.80 USD. Show it to whoever approves the spend.
3. Order
Send the same body with expected_total set to the dry run's total, and a new Idempotency-Key that you keep until you have the answer. If the price moved in between, the order is refused with 409 price_changed and the new total in details.total; nothing is charged.
curl -sS -X POST https://api.redswitches.com/v1/orders \
-H "Authorization: Bearer $REDSWITCHES_API_KEY" \
-H "Content-Type: application/json" \
-H "Idempotency-Key: 7d0e4b1c-2f3a-4c5d-8e9f-0a1b2c3d4e5f" \
-d '{"offer_id":"ofr_12","billing_cycle":"monthly","os_id":"os_31","ssh_key_id":"sshkey_7","expected_total":{"amount":"15480","currency":"USD"}}'import { randomUUID } from "node:crypto";
const idempotencyKey = randomUUID(); // store it with your job until the answer arrives
const order = await rs.orders.create({ ...body, expected_total: quote.total }, { idempotencyKey });import uuid
key = str(uuid.uuid4()) # store it with your job until the answer arrives
order = client.orders.create(**body, expected_total=quote.total, idempotency_key=key)req.ExpectedTotal = quote.Total
order, err := client.Orders.Create(ctx, req, &redswitches.OrderOptions{IdempotencyKey: uuid.NewString()}){
"id": "ord_456",
"invoice_id": "inv_789",
"server_ids": ["srv_123"],
"status": "paid",
"delivery": "queued",
"billing_cycle": "monthly",
"total": { "amount": "15480", "currency": "USD" },
"recurring": { "amount": "12900", "currency": "USD" },
"paid_from_credit": { "amount": "15480", "currency": "USD" },
"poll_after": 30
}
201 means the order exists and is paid from credit. The invoice is already settled.
If the answer is lost
Send exactly the same request again, with the same Idempotency-Key and the same body. You get:
201with the same order, if it went through;- the same error, if it was refused;
409 in_progresswhile it is still running: waitdetails.retry_afterseconds and ask again.
You never get a second server. Never change the body under the same key: that is 422 idempotency_conflict.
4. Wait for delivery
Poll the order, waiting poll_after seconds between requests, until every server's delivery is ready.
while true; do
r=$(curl -sS https://api.redswitches.com/v1/orders/ord_456 \
-H "Authorization: Bearer $REDSWITCHES_API_KEY")
echo "$r" | jq -c '.services'
wait=$(echo "$r" | jq -r '.poll_after // empty')
[ -z "$wait" ] && break
sleep "$wait"
doneconst ready = await rs.orders.waitUntilReady(order.id);
const server = await rs.servers.get(ready.services[0].id);
console.log(server.primary_ip);ready = client.orders.wait_until_ready(order.id)
server = client.servers.get(ready.services[0].id)
print(server.primary_ip)for {
o, err := client.Orders.Get(ctx, order.ID)
if err != nil {
log.Fatal(err)
}
if o.PollAfter == nil {
break
}
time.Sleep(time.Duration(*o.PollAfter) * time.Second)
}{
"id": "ord_456",
"date": "2026-11-15T09:30:00Z",
"status": "active",
"payment": "paid",
"invoice": "inv_789",
"services": [{ "id": "srv_123", "delivery": "ready" }],
"poll_after": null
}
Delivery goes assigning → installing → ready. poll_after becomes null when nothing is still being delivered. If a server shows attention, our staff are already looking at it; do not order again.
When it is ready, the welcome email with the login details is in the account owner's mailbox. The root password never appears in the API. If you ordered with ssh_key_id, you can log in with that key straight away.
When an order is refused
| Answer | What happened | What to do |
|---|---|---|
402 insufficient_credit | Not enough credit. details.needed is the shortfall in minor units. | Add funds, then order with a new Idempotency-Key. |
409 price_changed | The total moved. details.total is the new one. | Confirm it, then order with a new key and the new expected_total. |
409 sold_out | The last unit went. | Pick another offer. |
403 cap_reached | A limit would be passed. details.cap, details.limit and details.window say which. | Wait for the window to pass, or ask support to raise an account limit. |
403 cooling_off | Ordering is not open yet. | Wait details.retry_after seconds. |
403 ordering_not_enabled | Ordering is off for the account. | The owner turns it on. |
403 not_eligible | The account may not order now. | The owner sees why on the API page. |
403 ordering_paused | RedSwitches paused API ordering. | Try later. |
409 order_not_placed | Nothing was created or charged. | You may try again with a new key. |
409 order_under_review | The order exists but staff must check it before it is paid or delivered. details.order is its id. | Do not order again. We will contact you. |
422 not_orderable | Offer, OS, SSH key or cycle not allowed. | Check the ids against GET /offers and GET /ssh-keys. |