Skip to content

API overview

Spantail is built around a single REST API. The web app, the CLI, and the MCP server are all clients of the same endpoints — anything they can do, you can do directly over HTTP.

The REST API lives under /api/v1 on your instance:

https://<your-instance>/api/v1

Replace <your-instance> with your own Worker domain (its *.workers.dev URL or a custom domain). A few endpoints sit outside /api/v1: authentication under /api/auth (see Authentication), the health check at /api/health, and the public share views under /share.

Spantail is pre-1.0, so /api/v1 may still change in breaking ways between releases.

From v1.0.0 on, /api/v1 only grows: endpoints and fields may be added, but never removed or repurposed. A breaking change would ship as /api/v2 alongside the existing /api/v1, which keeps working. Write clients that ignore response fields they do not recognize.

Every /api response carries the instance’s version in the x-spantail-version header, so a client can tell which server it is talking to without an extra request.

  • JSON by default. Request and response bodies are application/json; charset=utf-8; send Content-Type: application/json on every request with a body. A few endpoints return binary instead (e.g. avatar and workspace-logo image bytes), as noted on their pages.
  • Timestamps are ISO 8601 strings in UTC (e.g. 2026-06-28T09:30:00.000Z).
  • Dates are calendar-day strings YYYY-MM-DD, interpreted in the relevant user’s timezone (see Dates and time).
  • Durations are integer minutes.
  • IDs are opaque strings. Do not parse or construct them.
  • Unknown fields in a request body are rejected — bodies are validated strictly.

Every endpoint except a handful of public ones (health, share views, sign-in, accepting an invitation) requires authentication. You can authenticate with a session cookie, a Personal Access Token, or an Agent Access Token. See Authentication for the full model, token formats, and scopes.

Timezone is a per-user concept (user.timezone, null means UTC). Workspaces and projects have none. A date and a timestamp are independent:

  • A work entry’s entryDate is a local date string in the author’s timezone, frozen at write.
  • All timestamps are UTC instants; durations are integer minutes.
  • Agent entries store only timestamps; their calendar day is derived from startedAt in the viewer’s timezone at read time.
  • Reports resolve relative ranges and the generation date in the running user’s timezone.

List endpoints that can return large result sets accept limit and offset query parameters. The default when limit is omitted is endpoint-dependent — some apply a default and cap (for example, work entries default to 50 and cap at 200), while others treat limit as optional and return the full set; each endpoint page notes its behavior. Results are returned as a plain JSON array; page by incrementing offset.

curl "https://<your-instance>/api/v1/work-entries?workspaceId=wrk_demo&limit=50&offset=50" \
  -H "Authorization: Bearer spantail_pat_yourtoken"

List endpoints accept resource-specific query parameters — typically workspaceId (required), plus optional projectId, userId, tag, and a from/to date range. The exact parameters are documented on each resource page.

Errors are returned with the appropriate HTTP status and a structured JSON body. Stack traces are never exposed.

{
  "error": {
    "code": "forbidden",
    "message": "Only the author can modify a work entry"
  }
}
Code HTTP Meaning
bad_request 400 Malformed or invalid request (failed validation, illegal state change).
unauthorized 401 Missing, invalid, or expired credential.
forbidden 403 Authenticated but not allowed to perform the action.
insufficient_scope 403 The Personal Access Token lacks the required scope.
not_found 404 The resource does not exist, or is hidden from the caller.
conflict 409 The request conflicts with current state (e.g. duplicate slug).
rate_limited 429 Too many requests for this credential. Back off and retry.
internal 500 Unexpected server error.

Write-heavy and ingest endpoints — creating work entries, ingesting agent activity — are rate-limited per credential (per token, or per user for sessions). Exceeding the limit returns 429 with code rate_limited. Back off and retry.

GET /api/health public returns { "status": "ok" } with no authentication. Use it for uptime monitoring.