Skip to content

REST API

Everything Lithora does in the browser it also does over HTTP. JSON in, JSON out, one Bearer credential, no SDK required.

Base URL

Every path is prefixed with /api. Requests and responses are JSON; send Content-Type: application/json on anything with a body.

EnvironmentBase URL
Productionhttps://api.lithora.app
Local developmenthttp://localhost:8000

The API host is api.lithora.app even though the product lives on lithora.app. That is deliberate and stable — point your integrations at it.

Your first call

Mint a personal access token at Settings → API Tokens with the teams:read scope, export it, and confirm it works. GET /api/auth/me is the cheapest way to prove a credential is live.

Verify a token
curl -s https://api.lithora.app/api/auth/me \
  -H "Authorization: Bearer $LITHORA_TOKEN"

A 200 with your user object means you are in. A 401 means the token is missing, malformed, expired or revoked. Full walkthrough on Authentication.

Where to go next

Authentication

Mint a personal access token, pick scopes, send it, revoke it. Start here.

Endpoint reference

Teams, projects, tasks, the work graph and the confirmation-gated agent — with real request and response bodies.

CLI

Install lithora-cli, authenticate, and script your workspace from a terminal or CI job.

Python SDK

pip install lithora — a typed client with resource namespaces and real exception classes.

GitHub webhooks

HMAC verification, the normalized github.* event names, and every payload shape.

OpenAPI 3.1 spec

The curated machine-readable contract. Feed it to your generator of choice.

Conventions

  • Ids are opaque strings. Read team_id, project_id, task_id back from the API; never construct or parse them.
  • Timestamps are ISO 8601 UTC strings, e.g. 2026-08-01T09:14:22.481Z. Date-only fields such as due_date accept either a date or a full timestamp.
  • Everything is tenant-scoped. A project belongs to a team and a task belongs to a project. If you are not a member of the owning team you get a 403, and if the resource is in the trash you get a 404 — trashed items never appear in list responses.
  • Guests are read-only. A guest seat can read shared work and will be refused with 403 on any write.
  • Collection responses are not paginated. GET /api/tasks returns a nested subtask tree as a bare array, hard-capped at 10,000 tasks per response. Filter with project_id or team_id rather than fetching everything.

Errors

Every non-2xx response uses one structured envelope. The correlation_id is also returned in the X-Correlation-ID response header — include it when you contact support and we can find the exact request in our logs.

403 Forbidden
{
  "success": false,
  "verified": false,
  "status": "failure",
  "error_code": "PERMISSION_DENIED",
  "message": "You don't have access to this project",
  "detail": "You don't have access to this project",
  "correlation_id": "1f0e9b6a-3c25-4f8b-9a0d-2b71c4e8d5a3",
  "timestamp": "2026-08-01T09:14:22.481Z"
}

On a 422, detail is an object listing each field that failed validation instead of a string. Parse defensively.

422 Unprocessable Entity
{
  "success": false,
  "status": "failure",
  "error_code": "VALIDATION_ERROR",
  "message": "Request validation failed",
  "detail": {
    "errors": [
      { "field": "body -> title", "message": "String should have at least 1 character", "type": "string_too_short" }
    ]
  },
  "correlation_id": "8c31d0f4-27ab-4f19-b3ec-6a5d2b904177",
  "timestamp": "2026-08-01T09:16:40.102Z"
}

Status codes

Statuserror_codeWhat it means
400INVALID_REQUESTThe request was understood but rejected — an unknown scope, a bad date, a limit reached.
401AUTHENTICATION_REQUIREDMissing, malformed, expired or revoked credential.
403PERMISSION_DENIEDAuthenticated, but not allowed: not a team member, a guest attempting a write, or a token missing a required scope.
404RESOURCE_NOT_FOUNDNo such resource, or it is in the trash.
409RESOURCE_CONFLICTThe write collided — most often a task_id already in use.
422VALIDATION_ERRORThe body failed schema validation. detail.errors lists the offending fields.
429RATE_LIMIT_EXCEEDEDToo many requests. Back off and retry.
5xxINTERNAL_ERROROur fault. message and detail are deliberately generic; quote the correlation id.

Rate limits

Limits are declared per endpoint and keyed by client IP. There is no global request cap today, so treat the numbers below as the enforced floor rather than a budget to spend — write clients that back off on 429 regardless.

Endpoint groupLimit
POST /api/auth/login10 / minute
OTP request and verify routes5 / minute
File uploads10 / minute
AI and model-backed routes30 / minute
POST /api/github/webhooks120 / minute
Core CRUD (tasks, projects, teams, work items)No declared per-route limit

AI usage is metered separately

Agent calls also draw on your plan's AI allowance. When that allowance is exhausted you get a 402 or 403 naming the limit — not a 429. Retrying will not help; see Billing & Plans.

Interactive docs are off in production

FastAPI's Swagger UI, ReDoc and runtime schema are disabled on api.lithora.app on purpose, so the full internal endpoint catalogue cannot be enumerated by outsiders. This documentation and the curated OpenAPI 3.1 spec are the public contract. If you run the backend locally, http://localhost:8000/api/docs works as usual.