Skip to content

API conventions

What holds everywhere, rather than repeated on every route.

The error envelope

Every error has the same shape.

json
{
  "status": "error",
  "message": "CREDITS_EXHAUSTED",
  "messageDetail": "Not enough credits."
}

message carries an uppercase machine code, to read in your code. messageDetail carries a readable sentence, to show or to log. Never branch your logic on messageDetail: it is prose, it can change.

The full list is on the Error codes page.

The request_id

Every response carries an x-request-id header. It is the only thing worth quoting if you open a ticket: it locates the exact call in the logs.

The libraries put it on every exception and every result.

python
except SpavikError as exc:
    logger.error("call refused", code=exc.code, request_id=exc.request_id)

Idempotency

The five operations that change something accept an Idempotency-Key header: POST /v1/models, /predict, /outcomes, /refresh and POST /v1/forecast.

Replaying the same call with the same key returns the already computed response, with no second charge. That is what makes retrying after a network cut safe: without it, you would choose between paying twice and losing a result you already paid for.

sh
curl -X POST "$SPAVIK_BASE_URL/v1/models/{id}/predict" \
  -H "X-API-Key: $SPAVIK_API_KEY" \
  -H 'Idempotency-Key: 6f1c8b90-...' \
  -H 'Content-Type: application/json' -d '{"rows":[...]}'

Draw one key per logical call, not per attempt: replaying it unchanged is what protects you. The libraries handle this.

Two codes relate to it: IDEMPOTENCY_KEY_REUSED if you reuse a key for different content, REQUEST_IN_PROGRESS if the first call has not answered yet.

Pagination

Lists are walked with limit and offset, and return a pagination object.

json
{
  "data": [ ... ],
  "pagination": { "total": 128, "limit": 50, "offset": 0, "has_more": true }
}
RouteDefault limitMaximum
GET /v1/models, /v1/api-keys50200
GET /v1/requests100500

The libraries hide all of this: models() is an iterator that fetches pages as it goes.

python
for model in spavik.models():
    print(model.id)

Time windows

Routes that read history accept from and to as ISO 8601 dates, or days for a rolling window.

ParameterDefaultMaximum
days30365

GET /v1/requests additionally accepts status=error, which keeps only failures: enough to track down a rejection that happened in production without reproducing it.

Retries

Three families deserve a retry, and only three: 429, the 5xx, and network failures. A 4xx will not change its mind; replaying it only wastes time.

The API exposes no Retry-After header today. The libraries therefore back off exponentially with jitter, three attempts at most, replaying the same idempotency key.

Identifiers

They are opaque and prefixed. Do not parse them, do not rebuild them.

PrefixObject
mdl_model
ver_model version
whk_webhook
sk-spv-api-API key
sk-spv-admin-admin key

Dates

All dates are ISO 8601, in UTC, with the Z suffix.

Part of this documentation is generated from the OpenAPI contract.