API

The API, and how it behaves

The frontend you are reading is one client of a Rails 8 JSON API. Every endpoint is documented by its own request spec — the OpenAPI schema is generated from the tests, so it cannot drift from the implementation. This page is the shape of the thing; the Swagger UI is the reference.

Authentication

Signing in returns a JWT in the Authorization header — there is no sign-up endpoint, because registration is closed. In this frontend that token never reaches client-side JavaScript: a Next.js route handler receives it and writes an httpOnly cookie, and every later request is proxied server-side. Signing out rotates the token's jti, which revokes every device at once.

Everything is scoped to you

Every record is reached through current_user.applications. Asking for someone else's application returns 404, not 403 — a 403 would confirm the row exists.

Errors carry a code

Every failure responds with an error sentence and a stable machine-readable code; validation failures add a details array naming each field and its error type. This frontend localizes off the code, never the sentence. The status still groups them:

CodeWhen
409An optimistic-locking conflict — the row changed under you.
422An illegal FSM transition, a validation failure, a bad pre-fill URL, or a site that refuses to be read by anything but a browser.
502A pre-fill that got nowhere: the page couldn't be fetched, or it was fetched and yielded no posting.
503The AI key is missing, or Postgres is down.

Cursor pagination

The applications list takes ?after=…&limit=20 — limit clamped to 1–100, default 10 — and answers with a data array beside a meta object carrying next_cursor and has_more. The cursor is a Base64 timestamp. A malformed one is ignored and you get the first page rather than an error. Filters compose with it server-side.

Endpoints

POST/api/v1/auth/sign_inExchange credentials for a token.
DELETE/api/v1/auth/sign_outRevoke the token on every device.
DELETE/api/v1/auth/accountErase the account and everything under it. There is no sign-up endpoint to undo it with.
GET/api/v1/applicationsList your applications, paginated and filterable.
POST/api/v1/applicationsCreate one. The status must be an entry state.
POST/api/v1/applications/prefillRead a job posting URL and fill the fields with Claude.
GET/api/v1/applications/:idOne application, with its valid next states and timeline.
PATCH/api/v1/applications/:idEdit the fields. Not the status — that is a transition.
DELETE/api/v1/applications/:idDelete it.
PATCH/api/v1/applications/:id/transitionMove the status through the state machine.
GET/api/v1/applications/:id/resumeDownload the stored resume as a PDF.
GET/api/v1/applications/:id/cover_letterDownload the stored cover letter as a PDF.
GET/api/v1/transitionsThe state machine, read-only — every state and its legal next states.
GET/api/v1/dashboardAggregate counts, the filter facets, ghost risk, and the current user.
GET/api/v1/meThe signed-in user's profile.
GET/upHealth check — pings Postgres.

The full reference

Request and response schemas, every parameter, and a console to call the API live. Generated by rswag from the request specs.