Skip to content

REST API

The Orcha REST API is a thin, versioned HTTP contract at /v1. Internally it executes the same operations as the app’s own GraphQL API, but it presents them as a small, stable REST surface for external clients.

On Orcha’s hosted service:

https://api.orcha.run/v1

Self-hosting? Substitute your own domain — the API lives under your backend’s /api path (for example https://your-domain.com/api/v1), because the reverse proxy routes /api to the backend.

Every request is authenticated with a Personal Access Token in an Authorization header. Mint one in the app from the avatar menu → API Tokens.

Terminal window
curl https://api.orcha.run/v1/me \
-H "Authorization: Bearer orcha_pat_your_token_here"

A token is bound to a single Role, so the request is automatically scoped to that Role’s organization — there is nothing else to pass. A token may be read-only: it can call every GET endpoint but is refused on writes.

MethodPathWhat it does
GET/v1/meThe identity and organization the token acts as
GET/v1/me/next-ticketsYour work queue, in scheduler priority order
GET/v1/ticketsList tickets (filter + paginate)
POST/v1/ticketsCreate a ticket
GET/v1/tickets/{id}A single ticket’s full detail
PATCH/v1/tickets/{id}Update a ticket’s fields (partial)
POST/v1/tickets/{id}/transitionMove a ticket through its lifecycle
GET/v1/tickets/{id}/bodyRead a ticket’s Markdown body + version
PUT/v1/tickets/{id}/bodyWrite a ticket’s Markdown body
GET/v1/projectsList projects (filter + paginate)
GET/v1/projects/{id}A single project’s detail
GET/v1/projects/{id}/bodyRead a project’s Markdown body + version
PUT/v1/projects/{id}/bodyWrite a project’s Markdown body
GET/v1/scheduleYour outstanding scheduled work and ETAs

The lifecycle action on a transition is one of schedule, start, advance, close, or cancel. Body writes use optimistic concurrency: a write conditions on the version you read, and a concurrent edit comes back as a conflict to rebase on rather than a silent overwrite.

List endpoints return a pageInfo object with cursor metadata. nextCursor is an opaque token; pass it back as ?after= to fetch the next page. It is null on the last page.

Errors come back as JSON with a stable shape:

{ "error": { "code": "UNAUTHENTICATED", "message": "" } }

The code is a stable machine-readable string; the message is for humans.

Every endpoint shares a per-token rate limit. When you exceed it you get 429 Too Many Requests with a Retry-After header giving the seconds to wait before the window resets.

The full reasoning — why thin REST over GraphQL, why role-scoped tokens, why a hand-authored spec — is in ADR 0006.