# Job Tracker — LLM Access Guide ## What this app is A personal job application tracker. Supports CRUD for applications, contacts, and documents. Multi-tenant: each user only sees their own data. Admin users (isAdmin flag) bypass tenant scoping. ## Authentication ### User session (Google OAuth) - Login: GET /login → redirects to Google OAuth - Session managed by better-auth (httpOnly cookies) - All API operations require a valid session ### Per-user API token (Bearer) - Each user can generate a personal API token in the dashboard - Header: `Authorization: Bearer ` - Token is scoped to the owner's data only - Admin users' tokens get cross-tenant read access - Token is shown once on creation, stored as SHA-256 hash in the database - Generate: POST /api/token (session required), Revoke: DELETE /api/token ### Admin privileges - Admin access is per-user, stored as `isAdmin` boolean in the database - The first user matching `ALLOWED_EMAIL` is automatically bootstrapped as admin on first login - Admin users can manage other users' admin status via the admin UI - Admin users bypass userId filtering (see all tenants' data) - At least one admin user must always exist (last-admin protection) ### Share token (read-only public) - Query param: `/share?token=$SHARE_TOKEN` - Read-only view of all applications and documents - Token set via environment variable — never hardcoded --- ## REST API ### Applications | Method | Path | Auth | Description | |--------|------|------|-------------| | GET | /api/applications | session or token | List all (filtered by userId unless admin) | | POST | /api/applications | session or token | Create application | | GET | /api/applications/:id | session or token | Get single | | PATCH | /api/applications/:id | session or token | Update | | DELETE | /api/applications/:id | session or token | Delete | ### Contacts (per application) | Method | Path | Auth | Description | |--------|------|------|-------------| | GET | /api/applications/:id/contacts | session or token | List contacts | | POST | /api/applications/:id/contacts | session or token | Create contact | | PATCH | /api/applications/:id/contacts/:contactId | session or token | Update contact | | DELETE | /api/applications/:id/contacts/:contactId | session or token | Delete contact | ### Documents | Method | Path | Auth | Description | |--------|------|------|-------------| | GET | /api/documents | session or token | List documents | | POST | /api/documents | session or token | Upload (multipart/form-data, field: file) | | PATCH | /api/documents/:id | session or token | Update: rename (`{ "originalName": "new.pdf" }`) or update links (`{ "applicationIds": [...] }`) | | DELETE | /api/documents/:id | session or token | Delete | | GET | /api/documents/:id/file | session, token, or share token | Download file | ### Token management | Method | Path | Auth | Description | |--------|------|------|-------------| | GET | /api/token | session | Get current token metadata | | POST | /api/token | session | Generate new token (revokes existing) | | DELETE | /api/token | session | Revoke token | ### Admin (requires isAdmin=true) | Method | Path | Auth | Description | |--------|------|------|-------------| | GET | /api/users | admin session | List all users | | PATCH | /api/users/:id | admin session | Update user admin status | --- ## Application status values `inbound` | `applied` | `interview` | `offer` | `rejected` ## Example: create application via API token ``` POST /api/applications Authorization: Bearer jt_ Content-Type: application/json { "company": "Acme Corp", "role": "Senior Engineer", "status": "applied", "appliedAt": "2026-03-06", "notes": "Referral from a contact" } ``` ## Security notes - PUBLIC_READ_TOKEN is a server-side env var only - NEXT_PUBLIC_* env vars do NOT contain any tokens - Rate limiting: 60 req/min per IP on API routes - All inputs validated; max upload size 15MB - Row Level Security: userId enforced at query level via lib/tenant.ts - Admin privileges managed per-user in the database, not via env vars