Skip to content
qibdo qibdo
Theme
Book a demo

API standards

qibdo Cloud’s API follows the API Improvement Proposals (AIP). Every service exposes the same resource-oriented shape, so once you learn how one service works, you know how they all work.

The API Improvement Proposals are a well-documented, openly standardised set of rules, best practices, and patterns for API design. We adopted them deliberately: rather than invent platform-specific conventions, every service inherits one mature, consistent design language, the same one that large public clouds already use. Resources, collections, and the verbs that act on them follow the same rules everywhere, so learning one service lets you predict the rest, and an engineer who has used a public cloud is productive on qibdo from day one.

That choice is reinforced by how the API is built. Each service is defined once in gRPC and Protocol Buffers, which gives us a single, strongly-typed, well-defined contract. A gateway then transcodes between REST/JSON and gRPC in both directions, following AIP-127 (HTTP and gRPC transcoding): REST clients call ordinary HTTP/JSON endpoints, gRPC clients use the typed interface, and both reach the exact same API. There is no separate, hand-written REST layer that can drift from the contract.

Because that one contract is the source of truth, the OpenAPI specification, the generated SDKs, and these reference pages are all derived from it. What you read here is what the API actually does.

Resource-oriented design is the RESTful model: the API is organized around resources (a VM, a bucket, a role) grouped into collections, each addressed by a URL and manipulated with standard HTTP methods. qibdo adopts that model through AIP’s resource conventions (AIP-121) and exposes it over REST by transcoding the gRPC contract. URLs are hierarchical and name each parent in the path; the target resource is identified by {id}, and parents are named by their resource type.

# A workspace in Taxonomy
/taxonomy/v1/organisations/{organisation}/workspaces/{id}
# A virtual machine in Compute
/compute/v1/workspaces/{workspace}/zones/{zone}/engines/qibdo/vms/{id}

Most resources are scoped to a workspace, the boundary that every service uses to isolate your teams, environments, and projects (see Taxonomy).

Five standard methods cover the lifecycle of every resource. The verb, URL, and request body are consistent across services:

MethodHTTPPathBody
GetGET…/<collection>/{id}(none)
ListGET…/<collection>(none)
CreatePOST…/<collection>the resource
UpdatePATCH…/<collection>/{id}the resource + an update mask
DeleteDELETE…/<collection>/{id}(none)

For example, to create and then fetch a VM:

Terminal window
# Create (returns an operation, see Standard responses)
curl --request POST \
--url https://api.qibdo.example.com/compute/v1/workspaces/$WS/zones/$ZONE/engines/qibdo/vms \
--header "Authorization: Bearer $QIBDO_API_TOKEN" \
--header 'Content-Type: application/json' \
--data '{ "name": "web-1", "cpu": { "v_cpus_boot": 2 } }'
# Get
curl --url https://api.qibdo.example.com/compute/v1/workspaces/$WS/zones/$ZONE/engines/qibdo/vms/$ID \
--header "Authorization: Bearer $QIBDO_API_TOKEN"

Actions that don’t map cleanly to create/read/update/delete use a custom method: a verb appended to the resource URL after a colon. Lifecycle actions on a VM, for instance:

POST …/vms/{id}:start
POST …/vms/{id}:stop
POST …/vms/{id}:restart
POST …/vms/{id}:suspend
POST …/vms/{id}:resume
POST …/vms/{id}:reset

Other examples include …/workspaces/{id}:move and /iam/v1/tokens:revoke. Like the standard mutations, these custom methods return an operation.

Updates use PATCH with a field mask so you change only the fields you intend to:

  • Send the resource with the fields you want to set, plus an update_mask listing those field paths. Only the masked fields are written; everything else is left untouched.
  • Omit the mask to update every field present in your request body.
  • Use * as the mask for a full replacement (PUT-style semantics).

This makes partial updates safe and explicit: you never accidentally clear a field you didn’t send.

You can send a resource straight back the way you received it. Fetch it with GET, change the fields you care about, and PATCH the whole thing — server-managed fields like create_time and id come along unchanged and are accepted, because their values still match what the server holds.

Terminal window
# Fetch, change one field, send the whole thing back. This is safe.
curl --url https://api.qibdo.example.com/vault/v1/workspaces/$WS/engines/qibdo/secrets/$ID \
--header "Authorization: Bearer $QIBDO_API_TOKEN" \
| jq '.name = "billing-api-key"' \
| curl --request PATCH \
--url https://api.qibdo.example.com/vault/v1/workspaces/$WS/engines/qibdo/secrets/$ID \
--header "Authorization: Bearer $QIBDO_API_TOKEN" \
--header 'Content-Type: application/json' \
--data @-

You do not have to strip a response down to the fields you changed before sending it.

Some fields are fixed once the resource exists — a resource’s location_id, for instance. How the API answers depends on what your request said, and the difference is deliberate:

Your requestResult
Names the field in update_maskRejected. Naming a field in a mask says you intend to change it
Includes the field, unchanged, with no maskAccepted. It is the value you were given back; nothing is being asked of it
Includes the field with a different value, no maskRejected, naming the field

Output-only fields such as create_time are ignored if you send them, changed or not, per AIP-203.

Listing: pagination, filtering, and ordering

Section titled “Listing: pagination, filtering, and ordering”

A List endpoint accepts these four standard query parameters for narrowing results:

ParameterPurpose
page_sizeMaximum items per page (default 20, capped at 100).
page_tokenOpaque cursor for the next page (from a prior response’s next_page_token).
filterAn AIP-160 filter expression to narrow results.
order_byA comma-separated list of fields to sort by (append desc to reverse).

There are no per-field filter parameters; all filtering goes through the single filter string. See Filtering for the language, and Standard responses for the list response shape.

A small number of listings depart from this shape, because the data behind them is not tabular and cannot honour tabular query semantics. A field predicate assumes a set of records with addressable, indexed attributes; where that assumption does not hold, offering one would be misleading at best and ruinously expensive at worst.

The rule, rather than a census: a listing declares the parameters it accepts, and its reference page plus the discovery endpoint are authoritative. A listing may narrow by something other than filter (object storage takes prefix and delimiter, because a bucket is a flat keyspace rather than a table), be enumerated by the store that owns it (time-series and log data are queried by their own languages), or accept a restricted dialect of the filter language rather than all of it. Each such listing states what it accepts on the service page that owns it.

Some Get endpoints let you fetch a resource by its id without knowing which parent it lives under, by using - as that parent path segment (AIP-159). For example, to read a resource pool by id across every region and zone:

GET /topology/v1/regions/-/zones/-/resource-pools/{id}

You can substitute - for any parent the endpoint marks as supporting it, and you need permission at a scope that covers the resolved resource. Each resource’s reference notes which parents accept -; List endpoints do not (listing resource pools, for instance, is scoped to a single region and zone) and require every parent to be a concrete id.

The major version is part of the path (/compute/v1/..., /iam/v1/...). A new major version is introduced only for breaking changes and is served alongside the previous one; within a major version, changes are additive and backward-compatible.

  • snake_case fields. JSON field names mirror the Protobuf fields, in snake_case.
  • Server-managed fields are read-only. Fields such as id, created, and updated are output-only; if you send them on create or update, they’re ignored.
  • Timestamps are RFC 3339. For example 2026-06-04T12:30:00Z.
  • Monetary values are an exact decimal string plus an ISO 4217 currency code. For example {"amount": "12.34", "currency_code": "USD"}.

Every monetary value the API publishes has the same two-field shape: an amount and a currency_code. Hand both to a locale-aware currency formatter and you are done — there is no arithmetic to perform, no unit to convert, and no currency exponent to look up.

{ "amount": "12.34", "currency_code": "USD" }

The amount is a string, and deliberately so. A JSON number is a binary floating-point value, which cannot represent a figure as ordinary as 0.10 exactly; sending money that way would mean publishing a value that is close to the one we hold rather than equal to it. A decimal in text has no such loss, and every language can parse one.

Four guarantees come with it, and they are worth knowing because they let you compare and store these values without defensive handling:

  • The amount is exact. It is the value the platform holds, not a rounding of it.
  • It is normalised. No trailing zeros, no exponent notation, and no negative zero — so two numerically equal amounts are always character-for-character identical, and comparing or de-duplicating them never reports a difference that is not there. A figure stored as 12.3400 is published as 12.34, and a whole hundred as 100.
  • It carries at most eight fraction digits, which is the finest granularity the platform stores. Nothing published will outrun what you can persist alongside it.
  • The sign is on the amount itself, so a negative value is unambiguous.

The currency_code is an ISO 4217 alphabetic code, such as USD. It is a plain string rather than a fixed list because ISO 4217 is an external standard that changes without reference to this platform.

Values are never pre-rendered for display. Presentation depends on the reader’s locale, which the platform does not know, so it publishes the value and leaves the formatting to you.