All API routes are prefixed with /api. Authenticated routes require a valid tracker_auth cookie (set automatically on login/register).
Base URL: https://tracker.lakshmanp230.workers.dev/api (or your deployed URL).
Create a new account. Gated by ALLOW_REGISTRATION.
Request:
{ "email": "you@example.com", "password": "your-password" }Response 200:
{ "id": "uuid", "email": "you@example.com" }Errors: 403 if registration is disabled, 409 if email taken.
Request:
{ "email": "you@example.com", "password": "your-password" }Response 200:
{ "id": "uuid", "email": "you@example.com" }Errors: 401 on bad credentials.
Clears the auth cookie. No body required.
Response 200: { "ok": true }
Returns the current user from the session cookie.
Response 200:
{ "id": "uuid", "email": "you@example.com" }Errors: 401 if not authenticated.
Request:
{ "currentPassword": "...", "newPassword": "..." }Response 200: { "ok": true }
Errors: 401 if current password is wrong.
Deletes the user and all associated data (jobs, contacts, activities, reminders via cascade).
Response 200: { "ok": true }
All job endpoints require authentication.
List all jobs for the logged-in user.
Query params: ?archived=1 to include archived jobs (excluded by default).
Response 200:
[
{
"id": "uuid",
"user_id": "uuid",
"company": "Acme Corp",
"title": "Software Engineer",
"url": "https://...",
"location": "Remote",
"salary_min": 80000,
"salary_max": 120000,
"salary_currency": "USD",
"salary_period": "year",
"description": "...",
"notes": "...",
"status": "applied",
"sort_order": 0,
"applied_at": "2026-07-01T00:00:00.000Z",
"archived": 0,
"created_at": "...",
"updated_at": "..."
}
]Create a new job.
Request:
{
"company": "Acme Corp",
"title": "Software Engineer",
"status": "wishlist",
"url": "https://...",
"location": "Remote",
"salary_min": 80000,
"salary_max": 120000,
"salary_currency": "USD",
"salary_period": "year",
"description": "...",
"notes": "..."
}Only company, title, and status are required.
Response 201: The created job object.
Get a single job.
Response 200: The job object.
Errors: 404 if not found or not owned by the user.
Update job fields. Only send the fields you want to change.
Request: Partial job object.
Response 200: The updated job object.
Note: Setting status to "applied" auto-stamps applied_at if it was null. Status changes are auto-logged to the activity timeline.
Delete a job and its child resources (contacts, activities, reminders).
Response 200: { "ok": true }
Move a job to a new position (drag-and-drop).
Request:
{ "status": "interview", "index": 2 }index is the 0-based position in the destination column.
Response 200: Array of all jobs in the destination column with renumbered sort_order values.
Contacts, activities, and reminders share identical routing patterns under a job.
Same pattern for activities and reminders.
Contact schema: { name, role?, email?, phone?, linkedin?, notes? }
Activity schema: { type, title, notes?, happened_at? } — valid types: applied, phone_screen, interview, onsite, offer, rejected, note, follow_up, status_change
Reminder schema: { note, due_at }
Mark a reminder as completed (sets completed_at).
Returns reminders due within 7 days, joined with job company and title.
Returns computed analytics for the authenticated user.
Response 200:
{
"funnel": { "wishlist": 5, "applied": 3, "interview": 2, "offer": 1, "rejected": 2 },
"totalActive": 13,
"responseRate": 0.4,
"offers": 1,
"avgDaysToInterview": 14.5,
"weekly": [
{ "weekStart": "2026-04-20", "count": 3 }
]
}