Ir para o conteúdo
NimbusNexus

Conventions

The API has one shape, repeated across every resource. Once you know the shape, you know every endpoint — the only thing that changes from one resource to the next is the field set on the resource itself.

Resource ids

Every resource has a stable opaque id, in the form <prefix>_<26-char-base32>:

ResourcePrefixExample
Virtual machinevm_vm_01H8FZQ4XW9KMP4XQNR3VT7DC2
Managed databasedb_db_01H8FZQ4XW9KMP4XQNR3VT7DC2
Block volumevol_vol_01H8FZQ4XW9KMP4XQNR3VT7DC2
Snapshotsnap_snap_01H8FZQ4XW9KMP4XQNR3VT7DC2
API keykey_key_01H8FZQ4XW9KMP4XQNR3VT7DC2

Ids are stable for the life of the resource — never reused, never rewritten on rename. Use them in URLs, foreign keys, and audit logs; use human-readable name fields only for display.

Pagination

List endpoints accept two query parameters:

  • limit — page size. Default 50, max 200. Past 200 the request fails validation: 422 validation_error (Errors).
  • cursor — opaque next-page pointer. The response body returns it as pagination.next_cursor when there are more pages; pass it back as cursor to advance.

Cursors are opaque — don't try to decode or construct them. They include sort + filter state from the original request, so changing filters mid-pagination produces undefined behavior. Start fresh (no cursor) when your filters change.

The pattern in code:

# Page 1
curl 'https://api.nimbusnexus.net/v1/vms?limit=50' \
  -H "Authorization: Bearer $NIMBUS_KEY"

# Page 2 (cursor from page 1's response.pagination.next_cursor)
curl 'https://api.nimbusnexus.net/v1/vms?limit=50&cursor=eyJpZCI6...' \
  -H "Authorization: Bearer $NIMBUS_KEY"

When pagination.next_cursor is absent or null, you're on the last page.

Errors

Every error response uses the same JSON shape:

{
  "error": {
    "code": "validation_error",
    "message": "request validation failed",
    "details": {
      "errors": [
        {
          "loc": ["body", "event_type"],
          "msg": "Field required",
          "type": "missing"
        }
      ]
    },
    "request_id": "7e4b1d9c2a6f4e8b9d3c5a1f0e2b4c68"
  }
}
  • code — stable machine-readable string. Switch on this, not on the status code, when the same status has multiple meanings (e.g. 402 can be quota_exceeded, not_entitled or workspace_frozen).
  • message — human-readable English for logs. Not localized; if you show an error to end users, map code to your own text.
  • details — present on some codes. On a validation error, details.errors lists the failing inputs; on many other errors it carries a reason that refines the code, with the values that go with it. Its shape depends on the code.
  • request_id — on every error body the API sends: the same id as the X-Request-ID header every response from the API carries. A response from the edge in front of it (a 502 or 504, for example, or a 413 for a body too large to reach the API) carries neither the API's error body nor the X-Request-ID header (Errors). Log it, and include it when contacting support.
  • next_code — sent only while a code is being renamed: the code this error will carry afterwards. When it's present, switch on it instead of code. No code is being renamed today, so the API doesn't send it.

Errors lists every code and reason, and which of the Webhooks and Inboxes APIs sends each. (Earlier versions of this page showed validation_failed as a 400, with fields and request_id in the body. The APIs have never sent the first two: a validation failure is a 422 validation_error, with details. request_id was not in the body then either; the API's error bodies carry it now.)

HTTP status codes

We use a small, predictable set:

StatusWhen
200Success with body.
201Resource created. Location header points at the new resource.
202Operation accepted; check operation.status to track progress.
204Success, no body (typical for DELETE).
400The request couldn't be read as sent. error.message says why.
401No usable credential. See Authentication.
402Your plan or billing state stops the request (a quota, a feature not in your plan, a frozen workspace). Retrying won't help.
403Authenticated but not authorized (scope missing, wrong project, etc.). On Webhooks, also a credential it doesn't accept.
404Resource doesn't exist, OR the caller can't see it. We deliberately don't distinguish — leaking existence is itself an access leak.
409State conflict (name already taken, an Idempotency-Key reused with a different body, etc.).
413The body is over the size limit.
422Validation failed. On a validation error (validation_error), error.details.errors lists the failing inputs; on the Webhooks API's other 422 codes, such as invalid_payload or unknown_event_type, it is an object naming the value or rule at fault.
429Rate limited. When Retry-After is set, it tells you when to retry.
500–504Server-side problem. Always safe to retry idempotent requests.

Idempotency

Mutating endpoints (POST, PUT, PATCH, DELETE) accept an optional Idempotency-Key header. Pass any unique value (a UUID is conventional); we cache the response for 24 hours and return the same response on a repeat with the same key.

curl -X POST https://api.nimbusnexus.net/v1/vms \
  -H "Authorization: Bearer $NIMBUS_KEY" \
  -H "Idempotency-Key: 8e1c91a4-04d0-4d77-8e6e-2f7c1a5b8e84" \
  -H "Content-Type: application/json" \
  -d '{ "name": "web", "size": "gp-2-4", "region": "us-east-1", "image": "ubuntu-24.04" }'

Use idempotency keys when your client might retry a request after a network failure but can't tell whether the original succeeded. The default-no-key behavior is "best effort retry"; with a key, retries are safe.

GET endpoints are always idempotent and don't need the header.

Long-running operations

Some actions don't complete during the request lifecycle:

  • VM POST returns 202 while the hypervisor allocates the instance.
  • Database POST returns 202 while replicas come up.
  • Volume resize and VM migration return 202.

The response body for any 202 includes an operation object:

{
  "id": "vm_01H8FZQ4XW9KMP4XQNR3VT7DC2",
  "state": "provisioning",
  "operation": {
    "id": "op_01H8FZ...",
    "kind": "vm_create",
    "status": "running",
    "progress": 0.4,
    "started_at": "2026-05-19T18:30:00Z"
  }
}

Two ways to track progress:

  1. Poll — GET /v1/operations/{op_id} once a second. Cheap; counts as a regular API call against rate limits.
  2. Webhook — subscribe a callback URL in the dashboard. We POST when the operation reaches a terminal state (succeeded or failed).

Most operations finish in under 60 seconds. The exception is VM image pulls on first use (~90 seconds) and Kafka cluster setup (~180 seconds).

Timestamps

All timestamps are RFC 3339 / ISO 8601 in UTC with the Z suffix:

2026-05-19T18:30:00Z

Never local time, never with an offset, never an integer epoch (except in webhook signature headers, which carry a Unix timestamp because that's the convention HMAC signers use). Date.parse() in JavaScript and equivalent stdlib parsers in other languages handle this format natively.

What's next