Skip to content
Raster
Esc
navigateopen⌘Jpreview
On this page

API conventions

Auth, ids, idempotency, pagination, versioning and retries — the things every operation assumes.

The operation reference is generated from the same oRPC contract the API implements and both SDKs type themselves against, so a route cannot exist in one without existing in the others. This page is what that document assumes you already know.

Base URL and versioning

https://api.example.com/v1

The version is part of the path, not a header. RASTER_BASE_URL includes it, and so does every example here.

Within a version, fields are added and never repurposed. Enums are closed sets that may gain a member, so parse defensively: a client that throws on an unknown MachineEventType breaks on a release that adds one.

Authentication

Authorization: Bearer sk_...

An organization API key, or — for a browser — the httponly computer_session cookie. There is no third way in. See organizations and keys for scopes and how they intersect with the holder’s role.

A cookie-authenticated write additionally requires an Origin the API recognises.

Identifiers

Every id is prefixed, so a value is self-describing in a log or an error, and the prefix is part of the public contract:

Prefix Resource Prefix Resource
usr_ user snp_ snapshot
org_ organization tpl_ template
mem_ membership img_ image
inv_ invitation key_ api key
mch_ machine evt_ event
ses_ session prv_ published port
lea_ input lease sec_ machine secret

Ids are opaque past the prefix. Do not parse them for ordering or timestamps.

Requests and responses

JSON in, JSON out. Field names are snake_case on the wire; both SDKs present them in the host language’s own style at the wrapper layer and pass them through untouched on client.api.

Timestamps are RFC 3339 in UTC. Billing quantities are decimal strings, not numbers, because a byte-second over a month exceeds what a double holds exactly.

Errors

Every non-2xx response is the same envelope:

{
  "error": {
    "code": "quota_exceeded",
    "message": "your plan allows 3 running machines",
    "request_id": "req_...",
    "details": [{ "field": "size", "message": "must be one of small, standard, large" }],
    "quota": {
      "limit": "running_machines",
      "current": 3,
      "maximum": 3,
      "plan_id": "developer",
      "upgrade_url": "..."
    }
  }
}

Switch on code. It is a closed set with a fixed HTTP status per member. message is written for a person and may be reworded between releases. See errors.

Idempotency

Any unsafe request accepts a key:

Idempotency-Key: <your unique value>

That header is what makes a retry safe. A repeat replays the first response instead of creating a second machine.

  • The stored record holds a hash of the method, path and body, so reusing a key with a different body is a conflict rather than a wrong replay.
  • The insert is the lock: two concurrent requests with the same key race on a unique index and exactly one proceeds. The loser is told to retry rather than being given a half-finished answer.
  • Keys are retained for 24 hours.

Both SDKs take an idempotencyKey / idempotency_key argument on every unsafe call.

Retries

The SDKs retry a request only when repeating it can produce nothing but the effect it already had: a GET or HEAD, or an unsafe request carrying an idempotency key. Everything else is attempted once.

Retried codes are rate_limited, internal_error and compute_unavailable, with exponential backoff to a 2 second ceiling. compute_unavailable is on that list deliberately — a machine that is running but whose agent has not published its address yet answers with it on every boot, and that is a normal state to wait through rather than a failure to report.

Default timeout is 30 seconds per attempt. A machine that is still booting is refused, not waited on; waiting is waitForDesktop’s job, not the transport’s.

Pagination

Every list answers the same shape:

{ "data": [...], "next_cursor": "...", "has_more": true }

Pass next_cursor back as cursor, with limit to size the page. A cursor is an opaque encoding of the last row’s sort key — never construct one, and never substitute an offset: an offset skips or repeats rows on a list that changes while it is being read.

has_more is known without a second count query, because the query over-fetches by one.

Both SDKs iterate pages for you, so a cursor never appears in your code:

for await (const machine of client.machines.list()) {
  /* every page */
}

Rate limits

Signup and login are rate-limited — failed logins per email, signups per IP, within a window — and a successful login clears the counter so a good login does not count against the next. No other route is rate-limited by the API today.

The gateway limits input messages per second per socket separately, and a viewer that does not coalesce pointer moves is meant to hit it.

Request ids

Every response the API produces carries request_id, and so does every error. Quote it in a support request; it is the only thing that identifies one call among a day of them.

What the API never returns

No substrate identifier, host address, environment id or guest bearer token appears in any response, and there is no route that would return one. The SDKs cannot expose them because the API does not.

Was this page helpful?