Skip to content

Reference

All request and response bodies are JSON. Authenticated calls accept either an Authorization: Bearer <token> header or the session cookie.

MethodPathAuthSuccessNotable failures
POST/api/v1/auth/signup—201 user400 invalid email or password, 409 email taken
POST/api/v1/auth/login—200 token + expiry, sets cookie401 invalid credentials, 429 rate limited
POST/api/v1/auth/logout✓204401 unauthenticated
GET/api/v1/me✓200 user401 unauthenticated
GET/api/v1/version—200 build tag—
GET/healthz—200 process alive—
GET/readyz—200 ready503 database unreachable

Request bodies are capped at 1 MiB and reject unknown fields. The server sets read, write, and idle timeouts, and returns an X-Request-Id on every response that also appears in the log line for that request.

Every error shares one envelope, so clients can switch on a stable string rather than parse prose:

{"error": "invalid_credentials", "message": "email or password is incorrect"}
CodeStatusMeaning
bad_request400Body was not valid JSON, or contained unknown fields
invalid_email400Address failed validation
invalid_password400Outside the 8–512 byte policy
email_taken409An account with that address exists
invalid_credentials401Wrong email, wrong password, or disabled account — deliberately indistinguishable
unauthenticated401Missing, unknown, or expired session token
rate_limited429Login budget exhausted; see Retry-After
unready503Database unreachable — readiness only
internal500Something failed; the cause is in the logs, not the response
VariableDefaultDescription
LABAPP_DB_URL(required)PostgreSQL connection string; the service refuses to start without it
LABAPP_ADDR:8080Listen address
LABAPP_ENVdevdev gives human-readable logs; anything else emits JSON
LABAPP_LOG_LEVELinfodebug, info, warn, or error

Applied by embedded migrations at startup, under an advisory lock so concurrent replicas cannot race.

create table users (
id uuid primary key default gen_random_uuid(),
email text not null unique, -- stored lowercased
password_hash text not null, -- PHC string, self-describing
status text not null default 'active', -- active | disabled
created_at timestamptz not null default now(),
updated_at timestamptz not null default now()
);
create table sessions (
id uuid primary key default gen_random_uuid(),
user_id uuid not null references users(id) on delete cascade,
token_hash bytea not null unique, -- sha256 of the opaque token
created_at timestamptz not null default now(),
expires_at timestamptz not null,
user_agent text not null default '',
ip inet
);

on delete cascade means deleting a user revokes their sessions in the same statement. Expired rows are currently invisible to queries but not yet swept — a background job is the obvious next chore.

TargetWhat it does
checkFormatting, go vet, linter, race-detector tests — the commit gate
runStarts a development database and runs the service locally
test / test-raceUnit tests, optionally under the race detector
test-integrationStore tests against a disposable PostgreSQL container
acceptanceEnd-to-end auth flow against a live deployment
deployBuild, push, upgrade, then poll until that exact build answers
rollbackReturn to the previous release
db-upCreate or refresh the database and its secret
status / logs / urlInspect the running deployment