Skip to content

API Reference

root edited this page Mar 16, 2026 · 6 revisions

API Reference

All API endpoints return JSON. Authenticated endpoints require a valid sl_session cookie. Unauthorized requests return 401. Admin-only endpoints return 403 for non-admin users.


Authentication

GET /api/auth/status

Check the current auth state.

Response:

  • {"status": "setup"} — no users exist, first-run setup needed
  • {"status": "login"} — users exist but no valid session
  • {"status": "authenticated", "user": {...}} — logged in; includes user object

POST /api/auth/setup

Create the first admin account. Only works when no users exist.

Body:

{
  "username": "admin",
  "display_name": "Admin User",
  "password": "secret",
  "color": "#f5a623"
}
  • username — 2–50 chars, alphanumeric + underscores (required)
  • display_name — 1–100 chars (required)
  • password — 4–200 chars (required)
  • color — hex color, optional (auto-assigned if omitted)

Response: 200 with user object. Sets sl_session cookie.

Errors: 400 if setup already completed.

POST /api/auth/login

Body:

{
  "username": "admin",
  "password": "secret"
}

Response: 200 with user object. Sets sl_session cookie (30-day expiry).

Errors: 401 invalid credentials.

POST /api/auth/logout

Clears the session cookie and deletes the server-side session record.

Response: {"success": true}

POST /api/auth/register (admin only)

Create a new regular user. Same body format as /api/auth/setup.

Response: 200 with new user object.

Errors: 409 if username already taken. 403 if not admin.


Users

GET /api/users

List all users. Returns id, username, display_name, is_admin, color, created_at.

PUT /api/users/:id

Update a user. Admins can update any user; regular users can only update themselves.

Body (all fields optional):

{
  "display_name": "New Name",
  "color": "#3ecf8e",
  "password": "newpass",
  "is_admin": true
}

is_admin can only be changed by an admin.

DELETE /api/users/:id (admin only)

Permanently delete a user and all their sessions.

Errors: 400 if trying to delete yourself.


Shifts

POST /api/shifts

Create a new shift.

Body:

{
  "date": "2026-03-10",
  "hourly_rate": 15.00,
  "hours_worked": 8.0,
  "tip_mode": "total",
  "tip_input": 120.00,
  "notes": "Busy Saturday night",
  "job_id": 1
}
  • dateYYYY-MM-DD format (required)
  • hourly_rate — non-negative number (required)
  • hours_worked — non-negative number (required)
  • tip_mode"total" or "per_hour" (required)
  • tip_input — non-negative number (required)
  • notes — string, optional (default "")
  • job_id — integer or null, optional (default null)

Server computes: total_tips, wage_total, grand_total.

Response: {"id": 1, "total_tips": 120, "wage_total": 120, "grand_total": 240}

GET /api/shifts

List shifts. Soft-deleted shifts are excluded.

Query params:

  • from — start date (YYYY-MM-DD)
  • to — end date (YYYY-MM-DD)
  • user_id — filter by user

Returns shifts joined with user display name/color and job name/color. Ordered by date DESC.

PUT /api/shifts/:id

Update a shift. Same body as POST. You must own the shift or be an admin.

DELETE /api/shifts/:id

Soft-delete a shift (sets deleted_at timestamp). You must own it or be an admin.

POST /api/shifts/:id/restore

Restore a soft-deleted shift (clears deleted_at).


Jobs

GET /api/jobs

List all jobs, ordered by name ascending. Includes tip_payment field.

POST /api/jobs

Body:

{
  "name": "Restaurant ABC",
  "default_rate": 15.00,
  "color": "#3ecf8e",
  "overtime_threshold": 40,
  "overtime_multiplier": 1.5,
  "tip_payment": "cash"
}
  • name — 1–100 chars (required)
  • default_rate — non-negative number (default 0)
  • color — hex color (default #f5a623)
  • overtime_threshold — weekly hours before OT (default 40)
  • overtime_multiplier — OT pay multiplier (default 1.5)
  • tip_payment"cash" or "paycheck" (default "cash")
    • "cash" — tips received nightly in cash; excluded from paycheck gross but still taxed
    • "paycheck" — tips included in the paycheck

PUT /api/jobs/:id

Update a job. Same body as POST.

DELETE /api/jobs/:id

Archive a job (sets archived = 1). Does not delete associated shifts.


Templates

GET /api/templates

List all templates with joined job name.

POST /api/templates

Body:

{
  "name": "Weeknight Shift",
  "job_id": 1,
  "hourly_rate": 15.00,
  "hours_worked": 6.0,
  "tip_mode": "total",
  "tip_input": 80.00,
  "notes": ""
}

DELETE /api/templates/:id

Permanently delete a template.


Goals

GET /api/goals

List all goals, newest first.

POST /api/goals

Body:

{
  "period": "weekly",
  "target_amount": 1000.00,
  "active": true
}
  • period"weekly" or "monthly"
  • target_amount — non-negative number
  • active — boolean, optional (default true)

Setting active: true deactivates any other goal with the same period.

PUT /api/goals/:id

Update a goal. Same body as POST.

DELETE /api/goals/:id

Permanently delete a goal.


Settings

GET /api/settings

Returns all settings from the meta table as key-value pairs, including:

  • pay_week_start_day — 0 (Sun) through 6 (Sat), default 1 (Mon)
  • pay_period_typeweekly, biweekly, semimonthly, or monthly
  • pay_period_anchor — anchor date for biweekly periods

PUT /api/settings (admin only)

Update settings. Body is a flat object of key-value pairs:

{
  "pay_week_start_day": "1",
  "pay_period_type": "biweekly",
  "pay_period_anchor": "2026-01-06"
}

Tax Configuration

GET /api/tax-config

List all tax/deduction items, ordered by sort_order.

PUT /api/tax-config (admin only)

Replace all tax config items. Body is an array:

[
  {"key": "federal", "label": "Federal Income Tax", "rate": 0.22, "flat_amount": 0, "enabled": true, "sort_order": 0},
  {"key": "state", "label": "State Income Tax", "rate": 0.05, "flat_amount": 0, "enabled": true, "sort_order": 1}
]

Paycheck Estimate

GET /api/paycheck-estimate

Returns the estimated paycheck for the current pay period.

Response:

{
  "period": {
    "type": "Weekly",
    "start": "2026-03-09",
    "end": "2026-03-15",
    "total_days": 7,
    "elapsed_days": 5,
    "remaining_days": 2
  },
  "current": {
    "wages": 600.00,
    "tips": 400.00,
    "cash_tips": 300.00,
    "paycheck_tips": 100.00,
    "gross": 1000.00,
    "paycheck_gross": 700.00,
    "hours": 40.0,
    "shifts": 5
  },
  "previous": {
    "gross": 950.00,
    "hours": 38.0,
    "shifts": 5
  },
  "taxes": [
    {"key": "federal", "label": "Federal Income Tax", "rate": 0.22, "flat_amount": 0, "amount": 220.00}
  ],
  "total_tax": 350.00,
  "net_pay": 350.00,
  "projected_gross": 980.00,
  "projected_net": 490.00,
  "projected_cash_tips": 420.00
}

Key fields:

  • current.gross — total earnings (wages + all tips)
  • current.cash_tips — tips from jobs configured as "cash" (already received nightly)
  • current.paycheck_tips — tips from jobs configured as "paycheck"
  • current.paycheck_gross — wages + paycheck tips only (what appears on your check)
  • total_tax — taxes on ALL earnings including cash tips
  • net_pay — paycheck gross minus total tax (your check amount)
  • projected_cash_tips — projected cash tips through end of period

Analytics & Summary

GET /api/summary

Returns earnings aggregations for multiple periods.

Query params: user_id — optional filter.

Response keys: this_week, last_week, biweekly, this_month, last_month, ytd, all_time

Each contains: shifts, total_hours, total_wages, total_tips, grand_total, avg_shift, avg_tips_per_hour.

GET /api/trends

Aggregated time-series data for charts.

Query params: period"week" (12 weeks), "month" (12 months, default), or "year" (3 years).

Returns array of: period, shifts, total_hours, total_wages, total_tips, grand_total, tips_per_hour, effective_rate.

GET /api/overtime

Current-week overtime status.

Response: total_hours, threshold, multiplier, regular_hours, overtime_hours, is_overtime.

GET /api/tax-estimate

Estimated tax liability.

Query params: period"month" or "ytd" (default).

Response: wages, tips, total, est_wage_tax, est_tip_tax, est_total_tax, wage_rate, tip_rate.

GET /api/analytics/effective-rate

Average effective hourly rate by day of week (0=Sun through 6=Sat).

GET /api/analytics/extremes

Top and bottom shifts by grand total.

Query params: count — number of results per list (default 5).

Response: {"best": [...], "worst": [...]}

GET /api/analytics/tip-ratio

Monthly tip percentage trend.

Returns array of: period, total_tips, grand_total, tip_pct.


Export & Import

GET /api/export/pdf

Download a styled PDF report.

Query params:

  • from, to — date range (optional; omit for all-time)
  • label — custom title for the report header

Returns application/pdf.

GET /api/export/csv

Download shifts as CSV.

Query params: from, to — date range (optional).

Returns text/csv.

POST /api/import/csv

Import shifts from CSV. Send the raw CSV text as the request body with any content type.

Limits: 5 MB max.

Response: {"imported": 15, "errors": 2}


Validation Errors

All endpoints validated by Zod return 400 with:

{
  "error": "Validation failed",
  "details": {
    "field_name": ["Error message"]
  }
}

Clone this wiki locally