API conventions
Everything that is true of every endpoint, in one place, so the individual reference pages can stay short. Read this once and the rest of the API stops surprising you.
Base URL and versioning
Every endpoint lives under /v1.
https://api.esy.com/v1The version is in the path, and a breaking change means a new path — /v1 will not change shape under you. Additive changes, such as a new field in a response, happen within /v1, so parse responses tolerantly and ignore fields you do not recognise.
Authentication
One bearer token on every request. See Authentication.
Authorization: Bearer esy_sk_…Casing
The wire format is camelCase in both directions — templateId, workspaceId, actualCostUsd. The Python service behind it uses snake_case internally, which occasionally leaks into an error message; the documented contract is camelCase.
Identifiers
Most ids are prefixed, which means you can tell what an id refers to without context — useful in logs.
| Object | Format | Example |
|---|---|---|
| Run | run- | run-fb0677b2 |
| Artifact | artifact- | artifact-5a6a9501 |
| Step | step- | step-3b |
| Order | order- | order-1f2e3d4c |
| Budget | budget- | budget-7a1c… |
| Workspace / project | UUID | 9a1b6d4c-d1dc-… |
| Workflow | human slug | generate-illustration |
Pagination
List endpoints return an envelope with items and total, and take offset and limit query parameters. limit is capped at 100.
{
"items": [ … ],
"total": 128
}offset/limit at all and return everything, and a few use a cursor instead. Check the page for the endpoint you are calling rather than assuming. Where you see total, it is the count of matching records, not the count in this page.Filtering
Most list endpoints accept workspaceId and projectId, and collection endpoints for runs also accept status. Some require workspaceIdoutright — omitting it returns a 422 naming the missing query parameter.
Idempotency
There is no server-side idempotency yet. POST /v1/runs creates a run every time it is called, so a client that retries after a timeout can start — and pay for — the same work twice.
// Safe: reads can be retried freely.
GET /v1/runs/run-fb0677b2
// Not safe to blind-retry: a second call starts a second run and bills twice.
POST /v1/runsIf a run creation times out, list recent runs and look for yours before retrying. For batch work, prefer Generation Orders, which carry an orderDedupeKey so duplicate items collapse rather than multiply.
Errors
Every error body has a top-level detail, which may be a string, an object, or an array. Errors covers each code with real responses.
Rate limits
None are enforced per key today, and no rate-limit headers are returned. Do not take that as licence to hammer the API: bound your own concurrency, especially when fanning out runs. Use an order rather than a thousand parallel POSTs.
The endpoints you will use most
The full surface
The API is considerably larger than these docs cover — publications, outlets, workers, documents, media, finance. The authoritative list is the OpenAPI document, which is generated from the routers and therefore cannot drift:
https://api.esy.com/openapi.jsonThese pages document the parts that are stable and that an external caller needs. If something appears in the spec but not here, treat it as internal and subject to change.
- Everything is under
/v1, camelCase both ways, bearer auth on every call. - Lists return
{ items, total }withoffset/limit, capped at 100 — but not every endpoint paginates. - No idempotency keys: never blind-retry a
POST /v1/runs. openapi.jsonis the authoritative surface; these pages are the curated part.