Conventions
Representations
- JSON, UTF-8. Ids are strings (
"id": "42"). - Money, prices, quantities and percentages are decimal strings (
"limit_price": "187.2500"), never floats. - Times are ISO 8601 in UTC (
"2026-10-26T14:05:00Z"); dates areYYYY-MM-DD. - Orders, fills and prices are simulated; responses say
"simulated": truewhere it matters.
Pagination, filters, sorting, fields
Lists are cursor-paginated: limit (1 to 200, default 50) and cursor (the next_cursor of the previous page).
{"data": [ ... ], "next_cursor": "eyJrIjo0MX0", "has_more": true}Cursors are stable: rows added while you page do not shift the pages. Filters are the documented query parameters of each list (an unknown filter is refused with validation_failed, never ignored). sort=-id (newest first, the default) or sort=id. fields=id,symbol,state returns only those fields (sparse fieldsets).
Idempotency-Key
Every POST that creates or acts needs an Idempotency-Key header (any unique string up to 255 characters, such as a UUID). Vestoris stores the answer for 24 hours: retrying with the same key returns the same answer (with Idempotent-Replayed: true) and does nothing twice. The same key with a different request is refused (idempotency_key_reused); a retry while the first is still running gets 409 idempotency_in_progress. The file uploads of the data API are the exception: they are deduplicated by file content.
ETag and If-Match
GETs of things you can change send an ETag. Updates (PATCH, PUT, order amendments) need If-Match with that ETag: 428 without it, 412 precondition_failed when someone changed the resource since you read it (the answer carries the current ETag). Read again, check, retry.
Rate limits
Per client (or token) and per user, per minute. Every answer carries RateLimit-Limit, RateLimit-Remaining and RateLimit-Reset (seconds). Over the limit: 429 with Retry-After. Admins set each client's limit within the firm's default.
Long operations: 202 and jobs
Test benches, reports and rebalances of more than three accounts answer 202 Accepted with a job and a Location to poll:
{"id": "17", "kind": "compliance_test_bench", "status": "queued", "result": null, "poll": "/api/v1/jobs/17"}Poll GET /api/v1/jobs/17 until status is succeeded (the result holds the outcome) or failed (error says why).
CORS
Off by default. An Admin can list the exact origins of a browser app on its client; only those get CORS headers.
Versioning and deprecation
See the changelog and deprecation policy.