Documentation · API

One API, two ways to authenticate.

38 routes. Every one authenticates, and every client-supplied id is authorized separately — a valid session only proves you are some user.


Authentication

A cookie for browsers, a bearer token for everything else.

Both carry the same JWT. Browsers get it as an HttpOnly cookie, which page scripts cannot read. Native clients ask for it explicitly and send it as a bearer token. When both are present, the bearer token wins.

Getting a token as a native client
POST /api/auth Content-Type: application/json X-DevDash-Client: native { "action": "login", "email": "you@example.com", "pin": "…" }

The raw token is returned in the response body only when the request carries the native client header. The header is still spelled X-DevDash-Client — it is a wire name that shipped mobile clients are pinned to, so it did not get renamed with the brand. Browsers deliberately never send it, so a script on a page cannot read or exfiltrate a session even if it can make the request.

Using it
GET /api/projects Authorization: Bearer <jwt>

The same two credentials authenticate the WebSocket upgrade — the cookie, or the token as a query parameter.


Shape

What to expect from a route.

Authentication is mandatory everywhere

There is no unauthenticated data route. Two-factor is enforced at the API layer as well as in the interface, so a half-enrolled account cannot read data by talking to the API directly.

A 404 may mean "not yours"

Denials by id return 404 rather than 403, deliberately, so ids cannot be enumerated by watching which ones come back forbidden.

Auth is one route, dispatched by action

Login, two-factor verification, enrolment, registration, PIN reset, two-factor reset, machine switching and logout are all POSTs to the auth route with an action field.

Listing a secret never returns it

Credentials, two-factor secrets and machine API keys are encrypted at rest and are never included in a list response — only an explicit reveal route returns one.

The projects list is the expensive call

It live-checks every port on every request, relaying through the agent when one is online. Treat it as the hot path it is, and do not poll it in a tight loop.


The contract

OpenAPI, hand-maintained.

The full specification lives in the repository at docs/api/openapi.yaml. It is written by hand rather than generated, which is worth knowing in both directions: it describes what native clients are actually pinned to, and it drifts if a route change does not update it in the same commit.

If you are building against it and find a mismatch, that is a bug worth reporting — a shipped mobile app is pinned to that document in a way the web interface never was.