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:
| Code | When |
|---|---|
| 409 | An optimistic-locking conflict — the row changed under you. |
| 422 | An illegal FSM transition, a validation failure, a bad pre-fill URL, or a site that refuses to be read by anything but a browser. |
| 502 | A pre-fill that got nowhere: the page couldn't be fetched, or it was fetched and yielded no posting. |
| 503 | The 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_in | Exchange credentials for a token. |
| DELETE | /api/v1/auth/sign_out | Revoke the token on every device. |
| DELETE | /api/v1/auth/account | Erase the account and everything under it. There is no sign-up endpoint to undo it with. |
| GET | /api/v1/applications | List your applications, paginated and filterable. |
| POST | /api/v1/applications | Create one. The status must be an entry state. |
| POST | /api/v1/applications/prefill | Read a job posting URL and fill the fields with Claude. |
| GET | /api/v1/applications/:id | One application, with its valid next states and timeline. |
| PATCH | /api/v1/applications/:id | Edit the fields. Not the status — that is a transition. |
| DELETE | /api/v1/applications/:id | Delete it. |
| PATCH | /api/v1/applications/:id/transition | Move the status through the state machine. |
| GET | /api/v1/applications/:id/resume | Download the stored resume as a PDF. |
| GET | /api/v1/applications/:id/cover_letter | Download the stored cover letter as a PDF. |
| GET | /api/v1/transitions | The state machine, read-only — every state and its legal next states. |
| GET | /api/v1/dashboard | Aggregate counts, the filter facets, ghost risk, and the current user. |
| GET | /api/v1/me | The signed-in user's profile. |
| GET | /up | Health 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.