API
esc

Type to search.

Conventions

The shapes every endpoint shares — base URLs and versioning, ids, timestamps, pagination and the error envelope.

Base URL and versioning

Environment Base URL
Production https://api.veeton.com/v1
Development https://api.veeton.net/v1

Versioning is single-axis and lives in the path. Pin to /v1. Within a major version only additive changes ship: new fields, new optional parameters, new enum values where the docs say a set is open. Clients must ignore fields they do not know.

Identifiers

Every resource is addressed by an id: a 26-character Crockford base32 ULID such as 01HX5K2MZ7A3Q4FBNDC0EVDXY1. Ids are opaque and non-enumerable. The one exception is model ids: Veeton-curated system models carry shorter legacy ids ("19274"), so treat model_id as an opaque string rather than validating its shape.

Webhook event ids are prefixed: evt_….

Timestamps

All timestamps are RFC 3339 in UTC, 2026-09-03T14:30:09Z. Query parameters that take a timestamp (since= on exports) accept an offset as well.

Pagination

Every list endpoint returns the same envelope and takes the same parameters.

{
"data": [ ],
"next_cursor": "eyJpZCI6MTIzNDV9",
"has_more": true
}
  • limit: 1 to 100, default 25.
  • cursor: the next_cursor from the previous page. Opaque; do not construct it.

Pagination is keyset-based, so adding or deleting rows while you page never skips or repeats an item. An unusable cursor is refused with 400 invalid_cursor.

Errors

Every error, from any endpoint, is the same envelope.

404 Not Found
{
"error": {
"type": "not_found",
"code": "product_not_found",
"message": "Product 01HX5K2MZ7A3Q4FBNDC0EVDXY1 was not found.",
"param": "inputs.product_id"
}
}
Field Meaning
type The class of failure: invalid_request, not_found, unauthorized, forbidden, conflict, rate_limited, internal, not_implemented. Switch on this for behaviour.
code A stable, specific identifier such as reference_already_exists. Switch on this for messaging.
message Human-readable, safe to show an operator. Not stable; do not parse it.
param When present, the request field the error is about.

Two endpoints validate many things at once and return a list instead of a single error, so you never retry-and-bisect: POST /v1/imports returns { "errors": [{ "line", "code", "message", "param?" }] } and POST /v1/tasks/tryon returns { "errors": [{ "input_index", "type", "code", "message", "param?" }] }.

A 5xx never echoes internal detail. The response carries a generic message and the X-Request-Id to quote to support; the detail is on our side, keyed by that id.

Request ids

Every response carries X-Request-Id. Send your own value in the same header and it is echoed back (up to 128 characters of letters, digits, ., _, : and -), so you can correlate a call across your logs and ours.

Images

Images are typed by their bytes, never by a declared Content-Type. JPEG, PNG and WebP are accepted, up to 10 MB each. Signed URLs to image bytes last one hour; the image id is permanent and GET /v1/images/{id} returns a fresh URL.

CORS

The API answers CORS preflights permissively so it can be called from any backend. It is designed for server-to-server use: a machine key in a browser is a machine key in public.