Getting started

API conventions

The rules every Serverside.com API endpoint shares: base URL, naming, money units, what a success and a failure look like, pagination, error codes, retries and idempotency.

Base URL and versions

https://api.serverside.com

Every path starts with a version segment, /v1. Requests and responses are JSON over HTTPS; send Content-Type: application/json with any request that has a body. Every response carries an X-Request-Id header.

Names and values

WhereStyleExample
Query parameterscamelCase?pageSize=50&sortColumn=name
JSON request and response fieldscamelCase"plan", "primaryIpv4"
Allowed valuesUPPER_SNAKE_CASE strings"GRACEFUL_RESTART", "RAID10"

A field with a fixed set of values lists them under Allowed values on its endpoint page. Every set is upper case except an error detail's location, which only appears in error responses. The API matches them without regard to case, so semi_annually reads as SEMI_ANNUALLY. Another spelling (SemiAnnually), a number in place of the name or a comma-separated list is refused with 400, and the error names the values the field takes. The one exception is the type of an order item, which must be written exactly: BAREMETAL or IP_BLOCK.

IDs are UUIDs, and a field with no id to give is null, never an empty string. A few fields that take an id also take a name that never changes: a bare metal plan's slug (epyc-9354p) and a datacenter's code, three letters for the metro and a site number (ams1). True-or-false fields are named as questions starting with is, such as isLocked, and a field that carries a unit names it: speedMbps, memoryGb, sizeBytes. A duration you send is in seconds.

IP addresses and networks travel as strings, such as "192.0.2.10" and "192.0.2.0/28". An IPv4 address must be written in its usual dotted form, so 10.1 and 010.0.0.1 are refused; an IPv6 address may be written in any form but without brackets or a zone. Timestamps are ISO 8601 strings. One you send has to carry its offset: 2026-09-20T00:00:00Z and 2026-09-20T02:00:00+02:00 are read as the same instant, and 2026-09-20T00:00:00 is refused. Every timestamp the API sends is in UTC and ends in Z.

Request bodies

A JSON property the endpoint does not declare is refused with 400 VALIDATION_ERROR, and the INVALID_BODY detail names the property, so a misspelled optional field is caught rather than dropped.

PATCH changes only what the body names: a field you leave out keeps its value, and the answer is 200 with the resource as its GET returns it. The three PATCH operations, on a server, a virtual network and an SSH key, take one field, name, and a null name leaves the name as it was. PUT replaces one setting whole, such as a server's boot mode or its iPXE script; lock and unlock are a PUT pair as well.

Money

Amounts are whole numbers in the currency of their record, which carries it as currency (pricing.currency on a plan). Whole numbers keep every amount exact. Most JSON parsers read a decimal such as 0.10 as a binary floating-point number, which cannot hold it exactly: in JavaScript, 0.1 + 0.2 is 0.30000000000000004. The field name gives the scale:

  • A field whose name ends in Millicents counts thousandths of a cent: 150000 is 1.50. Prices and rates are written this way, on plans, orders and subscriptions, so an hourly rate keeps its fraction of a cent.
  • Every other money field counts cents: 12999 is 129.99. Invoices, payments, usage charges and account credit are written this way.

An order carries only its price. Tax, discounts and the amount due are on the invoice (Billing).

Success and error responses

The HTTP status tells the two shapes apart: a 2xx answer is a success, anything else is an error, and success repeats that in the body.

Success

{
  "success": true,
  "data": { },
  "pagination": {
    "totalRecords": 42,
    "page": 1,
    "pageSize": 25,
    "totalPages": 2
  }
}
FieldMeaning
successtrue.
dataThe payload: an object, or an array on list endpoints.
paginationOn paged lists (Pagination). Every other response leaves it out.

A call that creates something answers 201 with what it created, in the shape a read of it returns. 202 Accepted means the change is still being applied. Attaching a server to a virtual network, detaching it and switching its boot mode put the change's progress in data: a status of APPLYING and since, the time the change was accepted. Deleting a server answers 202 with no body while the server is torn down. 204 No Content answers have no body at all. Download invoice PDF is the one endpoint that returns a file instead of JSON.

Error

{
  "success": false,
  "data": null,
  "code": "VALIDATION_ERROR",
  "message": "pageSize: Must be between 1 and 1000.",
  "traceId": "SRV-7F3A-0C21-9B4E-51D2",
  "details": [
    {
      "code": "INVALID_VALUE",
      "message": "Must be between 1 and 1000.",
      "field": "pageSize",
      "location": "query"
    }
  ]
}
FieldMeaning
successfalse.
datanull, except when an invoice payment is refused: then it holds the payment result (Billing).
codeWhat went wrong, as a stable UPPER_SNAKE_CASE code. Branch on it together with the status. A code your client does not know yet is handled by its status.
messageA sentence for people. Its wording can change, so don't parse it.
traceIdThe request's id, equal to the X-Request-Id header. Quote it when you contact support.
detailsOne entry per problem, on validation errors and some others. Each has its own code and message, the field it is about (null when the problem concerns the whole request) and that field's location: body, query, route or header.

The error body shares success and data with a success, so a client can read both with one type.

Pagination

Eight lists return every row at once and carry no pagination: List regions, List OS images, List applications, Get PSU health, List console sessions, List virtual media, Get security scan and List scan plugins. Every other list is paged, and all of them take the same parameters:

ParameterDefaultNotes
page11-based. A page past the last answers an empty data.
pageSize251 to 1000. A value outside that range is a 400.
sortColumnnoneThe field to sort by.
ascendingtruefalse reverses the sort.
searchnoneFree text; leading and trailing spaces are dropped. Which fields it searches depends on the endpoint.
filter[<field>]noneOne parameter per filtered field.

The answer's pagination repeats page and pageSize and adds totalRecords and totalPages. To read a whole list, request pages until page reaches totalPages; an empty list answers totalPages 0, so check for that first. Stopping at the first empty page works as well. Every paged list ends its sort on each row's id, so rows that tie on sortColumn keep their order from one page to the next.

curl --request GET \
  --url 'https://api.serverside.com/v1/baremetal?page=2&pageSize=100&sortColumn=name' \
  --header 'Authorization: Bearer <api-key>'

A filter on a text field matches any value that contains it, ignoring case. A filter on a field with allowed values takes the value as the API writes it, filter[status]=CANCELLATION_REQUESTED, and one on a true-or-false field takes true or false. A value the field cannot hold, a name the list cannot filter on, and any filter at all on a list that takes none are refused with 400 VALIDATION_ERROR, on an INVALID_VALUE detail whose field is the key as you sent it (filter[status]). A blank value is no filter.

sortColumn and the names inside filter[...] are not part of the stable contract. A sortColumn the endpoint does not know is ignored, and the list comes back in its default order.

Errors

Statuses

StatusMeaningWhat to do
400The request could not be read: malformed JSON, a wrong type, a property the endpoint does not declare, a value outside the declared range, an unknown allowed value, a timestamp without an offset.Fix the request; the same request fails again.
401The key is missing, wrong, disabled or expired, or the endpoint takes a console session (Authentication).Send a valid key.
403The key is valid but lacks the permission the endpoint checks.Use a key that holds it.
404Nothing with that id exists in your organization, or no endpoint has that path. A resource of another organization gets the same answer.Check the id and the path.
405, 406, 408, 413, 415The method (the Allow header lists the ones the path takes), Accept header, body timing, body size or content type is wrong.Fix the request.
409Something is in the way: a duplicate, a resource in use, an operation already running.Retry after Retry-After when the response has one; otherwise resolve the conflict.
422The request was read, and a rule of the resource or its current state refused it.Change what you asked for.
500A fault on our side.Retry later, and send the traceId to support if it persists.
502A system behind the API answered with an error.Retry later.
503A system behind the API cannot be reached, or a feature is paused. Always has Retry-After.Retry after Retry-After.
504A system behind the API did not answer in time.Retry later.

Retry-After is a number of seconds. Every 503 has it, and so does each 409 that clears by itself, such as a management controller that is restarting. Retrying a 500, 502 or 504 is safe for reads. Of the calls that change something, only the five listed under Idempotency take a key that makes a retry safe; before retrying any other, read what exists, since the first request may have gone through.

Validation

Two codes carry a list of details:

  • VALIDATION_ERROR, 400: the request could not be read. A detail's code is INVALID_BODY when the JSON body itself cannot be read: bad syntax, a wrong type, a missing required member, a member the endpoint does not declare, a value outside a field's allowed values. It is INVALID_VALUE for a query, route or header value, and for a body field that was read but breaks a declared limit such as a length or a range. VALUE_OUT_OF_RANGE marks a number the operation checks in its own code.
  • VALIDATION_FAILED, 422: the request was read, and one or more rules refused it. Each detail's code names the rule, such as BILLING_CYCLE_UNAVAILABLE on an order, and its location is null.

A body field is named by its JSON path, with indexes for arrays (items[0].plan, billingAddress.countryCode). A query field is named as you sent it, a route field by its parameter name and a header by its name. With one detail, message reads field: problem; with several, The request has N problems: followed by each of them.

An order on a billing cycle its plan is not sold on:

{
  "success": false,
  "data": null,
  "code": "VALIDATION_FAILED",
  "message": "cycle: This billing cycle is not offered.",
  "traceId": "SRV-1A2B-3C4D-5E6F-7A8B",
  "details": [
    {
      "code": "BILLING_CYCLE_UNAVAILABLE",
      "message": "This billing cycle is not offered.",
      "field": "cycle",
      "location": null
    }
  ]
}

An id in the body that matches nothing is treated by what it names. A plan, image, application, segment or interface is a catalog entry, and an unknown one is a 422 with a detail on that field, such as OFFERING_NOT_FOUND on items[0].plan. An id of something your organization owns (a server, a network, an SSH key) is a 404 ENTITY_NOT_FOUND with no field, so another organization's id looks the same as one that does not exist. A datacenter is the exception: an unknown code is a DATACENTER_NOT_FOUND detail, and an unknown UUID is 422 OFFERING_NOT_AVAILABLE.

Codes

Codes that any endpoint can answer:

CodeStatusMeaning
VALIDATION_ERROR400The request could not be read; see details.
BAD_REQUEST400The HTTP request itself could not be read.
UNAUTHENTICATED401See Authentication errors.
NO_PERMISSION403The key lacks the permission the endpoint names.
ENTITY_NOT_FOUND404No resource with that id in your organization.
ROUTE_NOT_FOUND404No endpoint has that path.
METHOD_NOT_ALLOWED405The path exists, the method does not; Allow lists the methods it takes.
ENTITY_ALREADY_EXISTS409A resource with the same identity exists.
ENTITY_IN_USE409The resource is in use, so it cannot be deleted or moved.
ENTITY_STATE_INVALID422The resource's state does not allow the operation, where no more specific code exists.
VALIDATION_FAILED422A rule refused the request; see details.
INTERNAL_ERROR500A fault on our side.
UPSTREAM_ERROR502A system behind the API answered with an error.
UPSTREAM_UNAVAILABLE503A system behind the API cannot be reached. Retry-After: 30.
UPSTREAM_TIMEOUT504A system behind the API timed out.

Codes for bare metal servers:

CodeStatusMeaning
SERVICE_SUSPENDED422The service is suspended; nothing can change until it is reinstated.
SERVICE_LOCKED422The service is locked.
SERVICE_DELETING422The service is being deleted, and refuses everything a suspended one does.
OPERATION_IN_PROGRESS409Another operation is running on the server.
BMC_RESET_IN_PROGRESS409The management controller is restarting. Retry-After: 30.
BMC_UNREACHABLE503The management controller is not responding. Retry-After: 30.
BMC_REQUEST_FAILED502The management controller returned an error.
KVM_SESSION_REQUEST_IN_PROGRESS409A console session request for the server is already running. Retry-After: 10.
OPERATION_NOT_SUPPORTED_PLATFORM422The server's hardware does not support the operation.
PRIMARY_IPV4_REQUIRED422A deployment needs a primary IPv4 address on the server.
ISO_LIMIT_REACHED422The organization holds as many ISOs as it may.
ISO_UPLOAD_INCOMPLETE422The ISO's upload has not finished or could not be verified.
VIRTUAL_MEDIA_EJECT_FAILED502The management controller could not eject the media.
VIRTUAL_MEDIA_ATTACH_TIMEOUT504Attaching virtual media timed out.
NETWORK_METRICS_UNAVAILABLE503Traffic metrics cannot be read for now. Retry-After: 30.

Codes for orders and billing:

CodeStatusMeaning
OFFERING_NOT_AVAILABLE422The plan cannot be ordered as asked: out of stock in that datacenter, no address space there for it, or an unknown datacenter UUID.
USAGE_BILLING_DISABLED422Hourly billing is not enabled for the organization (Quickstart).
BILLING_ADDRESS_REQUIRED422The organization has no billing address.
PAYMENT_FAILED422The payment could not be taken: the card was declined, or the organization has no account credit; data holds the payment result.
INVOICE_CANNOT_ACCEPT_PAYMENT422The invoice is already paid, cancelled or refunded, has nothing due, or a payment is awaiting confirmation.
SUBSCRIPTION_NOT_ACTIVE422The subscription has already been cancelled.
SUBSCRIPTION_SERVER_TRANSFERRED422A server the subscription bills now belongs to another organization; nothing changed.
TERMINATION_BLOCKED422An item on the subscription cannot be cancelled now; nothing changed.
BUNDLED_ITEM_CANCELLATION_BLOCKED422The IP block came with a server; deleting the server releases it.
SUBSCRIPTION_CANCELLATION_INCOMPLETE422Some items were cancelled before one failed. Sending the request again is safe.
IDEMPOTENCY_KEY_REUSED409The Idempotency-Key was already used for a different request.
ORDER_CREATION_IN_PROGRESS409A request with the same Idempotency-Key is still creating the order. Retry-After: 5.
ORDER_PROVISIONING_IN_PROGRESS409A create under the same Idempotency-Key is still running. Retry-After: 5.
PROVISIONING_FAILED409Nothing in an hourly order, or in an hourly server or IP block request, could be provisioned.
VAT_SERVICE_UNAVAILABLE503The EU VAT number check could not be completed. Retry-After: 30.
STRIPE_PROVIDER_ERROR502The payment provider failed.

Networking codes are listed in Managing IP addresses and Setting up virtual networks, next to the calls that answer them. VALIDATION_FAILED detail codes are named in the guide for the operation that sends them.

Idempotency

Five operations accept an Idempotency-Key header of up to 200 characters: creating an order, creating a bare metal service, creating an IP block, and attaching or detaching a server on a virtual network. Send a fresh value for each new action and the same value when you retry that action.

The three create calls share one set of keys per organization, and a key is never forgotten. A retry with the same key and the same purchase (billing cycle, promo code and items) places nothing new and answers 200 in place of 201, with what the first request created; Create order also sets data.isReplayed to true. Naming the plan by its slug in one request and by its id in the other still counts as the same purchase, and so does a datacenter's code against its id. The same key with a different purchase is refused with 409 IDEMPOTENCY_KEY_REUSED. While the first request is still running, a retry gets 409 ORDER_CREATION_IN_PROGRESS or ORDER_PROVISIONING_IN_PROGRESS with Retry-After: 5; send it again after that many seconds.

On the virtual network calls a key belongs to the server, and it is remembered until 90 days after the change has been applied. A retry answers 202 again without queuing a second change, and the same key with another network or segment gets 409 IDEMPOTENCY_KEY_REUSED. A second change while the first is still being applied gets 409 NETWORK_CHANGE_IN_PROGRESS with Retry-After: 10.

Rate limits

Each API key has its own limits:

RequestsLimit
Reads (GET)1,000 per minute
Writes (POST, PUT, PATCH, DELETE)100 per minute

The limits are enforced in front of the API, and the API itself does not send 429.

Polling a deployment every 20 seconds, as the guides do, uses 3 reads a minute.