Getting started
API conventions
The rules every Serverside.com API endpoint shares: base URL, naming, 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
| Where | Style | Example |
|---|---|---|
| Query parameters | camelCase | ?pageSize=50&sortColumn=name |
| JSON request and response fields | camelCase | "offeringId", "primaryIpv4" |
| Allowed values | UPPER_SNAKE_CASE strings | "GRACEFUL_RESTART", "RAID10" |
A field with a fixed set of values lists them under Allowed values on its endpoint page. The API matches them without regard to case, so per_hour reads as PER_HOUR. A value outside the list, 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.
IDs are UUIDs. IP addresses and networks travel as strings, such as "192.0.2.10" and "192.0.2.0/28". Timestamps are ISO 8601 strings, and 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. A JSON property the endpoint does not know is ignored, so a misspelled optional field is dropped without an error.
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,
"currentPage": 1,
"pageSize": 25,
"totalPages": 2
}
}
| Field | Meaning |
|---|---|
success | true. |
data | The payload: an object, or an array on list endpoints. Endpoints that only confirm an action send {"success": true} with no data. |
pagination | On lists that take page and pageSize (Pagination). Every other response leaves it out. |
204 No Content answers have no body at all. A 202 Accepted answer comes from a call whose change is still being applied, such as attaching a virtual network or switching boot mode, and its data holds the change's progress: a status of APPLYING and since, the time the change was accepted. 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"
}
]
}
| Field | Meaning |
|---|---|
success | false. |
data | null, except when an invoice payment is refused: then it holds the payment result (Billing). |
code | What 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. |
message | A sentence for people. Its wording can change, so don't parse it. |
traceId | The request's id, equal to the X-Request-Id header. Quote it when you contact support. |
details | One 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
A list whose endpoint page names page among its query parameters is paged, and all of them take the same set:
| Parameter | Default | Notes |
|---|---|---|
page | 1 | 1-based. A page past the last returns the last page. |
pageSize | 25 | 1 to 1000. A value outside that range is a 400. |
sortColumn | none | The field to sort by. |
ascending | true | false reverses the sort. |
search | none | Free text; leading and trailing spaces are dropped. Which fields it searches depends on the endpoint. |
filter[<field>] | none | One parameter per filtered field. |
The other lists, such as List OS images and List available datacenters, return every row in data at once and carry no pagination.
To read everything from a paged list, request pages until currentPage reaches totalPages. An empty list answers with totalPages 0 and currentPage 1, so check for 0 first. A page past the end returns the last page again, so a loop that waits for an empty page never stops on a list with rows in it.
curl --request GET \
--url 'https://api.serverside.com/v1/services/baremetal?page=2&pageSize=100&sortColumn=name' \
--header 'X-API-KEY: <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 a value the field does not have is a 400 whose detail names filter[status] as the field. A filter on a true-or-false field takes true or false; any other value is ignored, so the list comes back unfiltered on that field. Fields of other types cannot be filtered.
sortColumn and the names inside filter[...] are the stored property, which is not always spelled like the JSON field, and they are not part of the stable contract. A name the endpoint does not know is ignored without an error: a mistyped filter returns the list unfiltered, and a mistyped sortColumn returns it unsorted. Without sortColumn the order is not fixed either, since invoices come back sorted by currency and subscriptions in no set order, so pages can repeat or skip rows. Send sortColumn on every list you page through.
Errors
Statuses
| Status | Meaning | What to do |
|---|---|---|
400 | The request could not be read: malformed JSON, a wrong type, a value outside the declared range, an unknown allowed value, a timestamp without an offset. | Fix the request; the same request fails again. |
401 | The key is missing, wrong, disabled or expired, or the endpoint takes a console session (Authentication). | Send a valid key. |
403 | The key is valid but lacks the permission the endpoint checks. | Use a key that holds it. |
404 | Nothing 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, 415 | The method, Accept header, body timing, body size or content type is wrong. | Fix the request. |
409 | Something 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. |
422 | The request was read, and a rule of the resource or its current state refused it. | Change what you asked for. |
500 | A fault on our side. | Retry later, and send the traceId to support if it persists. |
502 | A system behind the API answered with an error. | Retry later. |
503 | A system behind the API cannot be reached, or a feature is paused. Always has Retry-After. | Retry after Retry-After. |
504 | A 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 create something, only the five listed under Idempotency take a key that makes a retry safe; before retrying any other, list 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'scodeisINVALID_BODYwhen the JSON body itself cannot be read: bad syntax, a wrong type, a missing required member, a value outside a field's allowed values. It isINVALID_VALUEfor 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_RANGEmarks 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'scodenames the rule, such asBILLING_CYCLE_UNAVAILABLEon an order, and itslocationisnull.
A body field is named by its JSON path, with indexes for arrays (services[0].offeringId, 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 or network segment id is a 422 with a detail on that field, such as OFFERING_NOT_FOUND. 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. An unknown datacenterId is 422 OFFERING_NOT_AVAILABLE.
Codes
Codes that any endpoint can answer:
| Code | Status | Meaning |
|---|---|---|
VALIDATION_ERROR | 400 | The request could not be read; see details. |
BAD_REQUEST | 400 | The HTTP request itself could not be read. |
UNAUTHENTICATED | 401 | See Authentication errors. |
NO_PERMISSION | 403 | The key lacks the permission the endpoint names. |
ENTITY_NOT_FOUND | 404 | No resource with that id in your organization. |
ROUTE_NOT_FOUND | 404 | No endpoint has that path. |
ENTITY_ALREADY_EXISTS | 409 | A resource with the same identity exists. |
ENTITY_IN_USE | 409 | The resource is in use, so it cannot be deleted or moved. |
ENTITY_STATE_INVALID | 422 | The resource's state does not allow the operation, where no more specific code exists. |
VALIDATION_FAILED | 422 | A rule refused the request; see details. |
INTERNAL_ERROR | 500 | A fault on our side. |
UPSTREAM_ERROR | 502 | A system behind the API answered with an error. |
UPSTREAM_UNAVAILABLE | 503 | A system behind the API cannot be reached. Retry-After: 30. |
UPSTREAM_TIMEOUT | 504 | A system behind the API timed out. |
Codes for bare metal servers:
| Code | Status | Meaning |
|---|---|---|
SERVICE_SUSPENDED | 422 | The service is suspended; nothing can change until it is reinstated. |
SERVICE_LOCKED | 422 | The service is locked. |
OPERATION_IN_PROGRESS | 409 | Another operation is running on the server. |
BMC_RESET_IN_PROGRESS | 409 | The management controller is restarting. Retry-After: 30. |
BMC_UNREACHABLE | 503 | The management controller is not responding. Retry-After: 30. |
BMC_REQUEST_FAILED | 502 | The management controller returned an error. |
KVM_SESSION_REQUEST_IN_PROGRESS | 409 | A console session request for the server is already running. Retry-After: 10. |
OPERATION_NOT_SUPPORTED_PLATFORM | 422 | The server's hardware does not support the operation. |
PRIMARY_IPV4_REQUIRED | 422 | A deployment needs a primary IPv4 address on the server. |
ISO_LIMIT_REACHED | 422 | The organization holds as many ISOs as it may. |
ISO_UPLOAD_INCOMPLETE | 422 | The ISO's upload has not finished or could not be verified. |
VIRTUAL_MEDIA_EJECT_FAILED | 502 | The management controller could not eject the media. |
VIRTUAL_MEDIA_ATTACH_TIMEOUT | 504 | Attaching virtual media timed out. |
NETWORK_METRICS_UNAVAILABLE | 503 | Traffic metrics cannot be read for now. Retry-After: 30. |
Codes for orders and billing:
| Code | Status | Meaning |
|---|---|---|
OFFERING_NOT_AVAILABLE | 422 | The plan cannot be ordered as asked: out of stock in that datacenter, or an unknown datacenterId. |
USAGE_BILLING_DISABLED | 422 | Hourly billing is not enabled for the organization (Quickstart). |
BILLING_ADDRESS_REQUIRED | 422 | The organization has no billing address. |
PAYMENT_FAILED | 422 | The payment could not be taken: the card was declined, or the organization has no account credit; data holds the payment result. |
INVOICE_CANNOT_ACCEPT_PAYMENT | 422 | The invoice is already paid, cancelled or refunded, or a payment is awaiting confirmation. |
SUBSCRIPTION_NOT_ACTIVE | 422 | The subscription has already been cancelled. |
TERMINATION_BLOCKED | 422 | An item on the subscription cannot be cancelled now; nothing changed. |
BUNDLED_ITEM_CANCELLATION_BLOCKED | 422 | The IP block came with a server; cancelling the server releases it. |
SUBSCRIPTION_CANCELLATION_INCOMPLETE | 422 | Some items were cancelled before one failed. Sending the request again is safe. |
IDEMPOTENCY_KEY_REUSED | 409 | The Idempotency-Key was already used for a different request. |
ORDER_CREATION_IN_PROGRESS | 409 | A request with the same Idempotency-Key is still creating the order. Retry-After: 5. |
ORDER_PROVISIONING_IN_PROGRESS | 409 | A create under the same Idempotency-Key is still running. Retry-After: 5. |
PROVISIONING_FAILED | 409 | Nothing in an hourly order or an IP block request could be provisioned. |
VAT_SERVICE_UNAVAILABLE | 503 | The EU VAT number check could not be completed. Retry-After: 30. |
STRIPE_PROVIDER_ERROR | 502 | The 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 a bare metal service, creating an IP block, creating an order, and assigning or unassigning a service 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 with what the first request created; Create order then answers 200 in place of 201, with data.replayed set to true. 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 with Retry-After: 5; send it again after that many seconds.
On the virtual network calls a key belongs to the service in the body, 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:
| Requests | Limit |
|---|---|
Reads (GET) | 1,000 per minute |
Writes (POST, PUT, PATCH, DELETE) | 100 per minute |
Polling a deployment every 20 seconds, as the guides do, uses 3 reads a minute.