Overview

Most mutations are asynchronous. A 200 means accepted, not finished — the response carries a jobId, and the real work continues behind it.
Treating a 200 as “done” is the single most common integration bug against this API. An instance can exist while its network interface, volumes and public address do not, so a script that deploys and immediately reads the address gets nothing back and concludes the deploy failed.Poll the job.

The pattern

1

Send the mutation and keep the jobId

Also keep any resource id in the response. You will want it once the job completes.
2

Poll GET /api/v1/jobs/{jobId}

Every 2–3 seconds is plenty. Use the same region header as the original request.
3

Stop on success or error

Those two are terminal. Anything else will change on its own.
4

On error, read detail before doing anything else

A failed job carries its reason. Usually a quota ceiling or no capacity in the chosen zone — neither is fixed by retrying the same request.

Job status

GET /api/v1/jobs/{jobId} returns a normalised status:
The underlying platform has more states than this — PENDING, PROCESSING, WEBHOOK_COMPLETED and others — and they are collapsed into these three. When a job is read live from the platform you also get statusCode, the platform’s raw numeric status. Branch on status; treat statusCode as diagnostic only.

Which endpoint to poll

GET /api/v1/jobs and the recent/running variants default to a 6-hour window via hoursBack. A job older than that is absent from the results, which looks identical to a job that never existed. Widen hoursBack before concluding anything.

Retrying safely

There are no idempotency keys. Nothing deduplicates a repeated POST, so retrying a deploy that actually succeeded gives you two instances — and you pay for both.
Before retrying any mutation:
1

Look for the job

If you captured a jobId, read it. running means wait — the work is progressing. success means the original call worked and a retry would duplicate it.
2

If you never got a jobId, search by window

GET /api/v1/jobs?hoursBack=1 and look for the command. A job present means the request landed.
3

Only retry when no job exists at all

No job means the request never reached the platform, so retrying is safe. A network timeout on your side is exactly this case — the connection dropped, but you cannot tell from your end whether the work started.
This is the same reasoning the console’s Jobs screen supports, described in Jobs. A timeout in a client means “we stopped watching”, never “it failed”.

A polling helper

Which operations are asynchronous

Anything that touches the compute platform: deploying, starting, stopping, rebooting, scaling, migrating and deleting instances; creating, attaching, detaching and resizing volumes; snapshots and backups; creating VPCs, networks and gateways; acquiring addresses and enabling static NAT. Reads are synchronous. So are operations on data Simnet holds itself — tags, comments, preferences, projects and organization membership.
A mutation with no jobId in the response completed synchronously. Absence of the field is the signal; there is no separate flag.

Jobs

The same model in the console.

Errors

Reading a failure reason.

Conventions

What this API deliberately lacks.

Jobs and events

Jobs versus events.