Skip to content

API conventions

Every File Service route follows the same conventions. Read this page once before you write a client by hand; the generated API operations assume it.

The API is at https://files.centralcodestation.com/api/v1. Routes about files sit under /tenants/:tenant_id/applications/:application_id, written P on this site. GET /health needs no token.

Every other route takes Authorization: Bearer <token>, a token of the Identity Server for the Files API (aud: files-api): a user token with files:access, or a machine token with files:manage, files:read or files:config.manage. See Contexts and tokens.

Each operation says who may call it:

Audience Means
user A user token
machine A machine token
public No token (health, the public share link routes)

A token of the wrong kind answers principal_not_allowed. Most routes take both.

  • Routes are kebab-case: /files/by-external-ref/:external_ref.
  • Every field is snake_case, in bodies, answers, query parameters and nested objects: file_id, next_cursor, max_file_size_bytes.
  • Ids are ULIDs (01J9Z6S9Q5F5R6Y2W2GZ3B3K4D) or Identity Server ids; the root folder is root.
  • Times are ISO 8601 in UTC; sizes are bytes.

Lists take ?after=<cursor>&limit=<n> and answer { items, next_cursor }. Pass next_cursor unchanged as after for the next page; it is null on the last page. limit defaults to 100 (at most 500) on listings, 50 (at most 200) on search; a larger value is capped. A cursor from another listing answers invalid_cursor.

Every non-2xx answer has the same body:

{ "status_code": 409, "error": "name_conflict", "message": "A folder or file with this name already exists here", "path": "/api/v1/tenants/fitness-co/applications/fitness-app/uploads", "timestamp": "2026-10-22T09:30:00.000Z" }
  • error is a stable snake_case code: branch on it. message is for people and may change; for validation errors it is an array of every problem.
  • Some codes add fields: quota_exceeded the numbers, partial_permission blocked, rendition_not_available rendition_status, file_locked lock, rate_limited retry_after.
  • New codes may appear: treat an unknown one by its status.
  • Retry 429, 500 and 503 with back-off. Do not retry other 4xx answers without changing the request.

Every code is in the error catalog.

The POSTs that create something (a ticket, a version ticket, a folder, a copy, a share link) accept an Idempotency-Key header. Within 24 hours, the same key with the same body answers the first answer again; the same key with another body answers idempotency_key_reused. Generate the key once per intended operation and reuse it on every retry.

Files and folders carry revision. Send If-Match: <revision> on a PATCH; if the item changed meanwhile, the answer is 412 revision_mismatch and nothing changes.

Some operations answer 202 with a job_id: copying a folder, trashing or restoring a folder with content, moving a large subtree, emptying the trash, regenerating renditions, completing a very large multipart upload. GET P/jobs/:job_id answers { job_id, type, status, progress, result, error }; status is queued, running, done or failed.

A 429 answers rate_limited with a Retry-After header in seconds. The limits per caller, client and address are on Limits.

JSON bodies are at most 256 KB (payload_too_large). File bytes never go through the API: they go to the signed URLs of an upload ticket.

A browser may call the API from an origin registered on your application’s web client at the Identity Server, without credentials (the token is a header, never a cookie). The signed upload and download URLs work from any origin and need no registration.

Operations marked as planned (check-out, check-in, edit sessions) are in the document so clients can be written against them; they answer 501 not_implemented until they are served.