PayChecker
API Reference

PayChecker API Reference

Complete API reference for the PayChecker REST API. All endpoints require authentication via JWT token (or dev header in development mode).

Base URLs

API Versioning

Current version: v1 (prefix: /api/v1)

Authentication

Development Mode

In development mode, use the X-Dev-Member-Number header:

curl -H "X-Dev-Member-Number: 35973" \
  http://localhost:8000/api/v1/shifts

Production Mode

Use the Authorization header with JWT token:

curl -H "Authorization: Bearer <jwt-token>" \
  https://api.example.com/api/v1/shifts

Interactive Documentation

Common Response Codes

Status CodeMeaning
200Success
201Created
204No Content (successful delete)
400Bad Request (validation error)
401Unauthorized (missing/invalid auth)
403Forbidden (insufficient permissions)
404Not Found
422Unprocessable Entity (validation error)
500Internal Server Error

Endpoints

Health Check

GET /health

Check API health and database connectivity.

Request:

curl http://localhost:8000/health

Response:

{
  "status": "healthy",
  "database": "connected",
  "version": "0.2.0"
}

Authorization Endpoints

Check Facility Data Management Permission

GET /api/auth/can-manage-facility-data

Check if current user can manage facility data (FacRep permission).

Request:

curl -H "X-Dev-Member-Number: 35973" \
  http://localhost:8000/api/auth/can-manage-facility-data

Response:

{
  "can_manage": true,
  "facility_code": "ZDC"
}

Member Endpoints

Get Facility Members

GET /api/members/facility/{facility_code}

Get all members at a specific facility. Requires FacRep permission.

Parameters:

  • facility_code (path) - Facility code (e.g., "ZDC", "N90")

Request:

curl -H "X-Dev-Member-Number: 35973" \
  http://localhost:8000/api/members/facility/ZDC

Response:

[
  {
    "membernumber": "35973",
    "firstname": "John",
    "lastname": "Doe",
    "email": "john.doe@natca.net",
    "facility_code": "ZDC"
  }
]

Pay Period Endpoints

Get Current Pay Period

GET /api/v1/pay-periods/current

Get the current pay period based on today's date.

Request:

curl http://localhost:8000/api/v1/pay-periods/current

Response:

{
  "pay_period_number": 1,
  "year": 2025,
  "start_date": "2024-12-29",
  "end_date": "2025-01-11",
  "pay_date": "2025-01-17"
}

Get Pay Period Range

GET /api/v1/pay-periods

Get a range of pay periods with navigation controls.

Query Parameters:

  • year (required) - Year (e.g., 2025)
  • start_period (required) - Starting pay period (1-26)
  • end_period (required) - Ending pay period (1-26)

Request:

curl "http://localhost:8000/api/v1/pay-periods?year=2025&start_period=1&end_period=3"

Response:

{
  "pay_periods": [
    {
      "pay_period_number": 1,
      "year": 2025,
      "start_date": "2024-12-29",
      "end_date": "2025-01-11",
      "pay_date": "2025-01-17"
    },
    {
      "pay_period_number": 2,
      "year": 2025,
      "start_date": "2025-01-12",
      "end_date": "2025-01-25",
      "pay_date": "2025-01-31"
    },
    {
      "pay_period_number": 3,
      "year": 2025,
      "start_date": "2025-01-26",
      "end_date": "2025-02-08",
      "pay_date": "2025-02-14"
    }
  ],
  "has_prev": false,
  "has_next": true
}

Shift Endpoints

List Shifts

GET /api/v1/shifts

Get shifts for authenticated user, with pagination and filtering.

Query Parameters:

  • pay_period (optional) - Filter by pay period number (1-26)
  • year (optional) - Filter by year (default: current year)
  • shift_date (optional) - Filter by specific date (ISO format)
  • limit (optional) - Results per page (default: 100, max: 1000)
  • offset (optional) - Pagination offset (default: 0)

Request:

curl -H "X-Dev-Member-Number: 35973" \
  "http://localhost:8000/api/v1/shifts?pay_period=1&year=2025"

Response:

{
  "shifts": [
    {
      "id": "uuid-1",
      "membernumber": "35973",
      "shift_date": "2025-01-06",
      "shift_type": "regular",
      "start_time": "07:00:00",
      "end_time": "15:00:00",
      "created_at": "2025-01-01T12:00:00Z",
      "updated_at": "2025-01-01T12:00:00Z"
    }
  ],
  "total": 1,
  "limit": 100,
  "offset": 0
}

Create Shift

POST /api/v1/shifts

Create a new shift.

Request Body:

{
  "shift_date": "2025-01-06",
  "shift_type": "regular",
  "start_time": "07:00:00",
  "end_time": "15:00:00"
}

Shift Types:

  • regular - Regular shift
  • tos - Time Off Station (auto-creates full-shift TOS entry)
  • tnw - Time Not Worked (auto-creates full-shift TNW entry)

Request:

curl -X POST \
  -H "Content-Type: application/json" \
  -H "X-Dev-Member-Number: 35973" \
  -d '{"shift_date":"2025-01-06","shift_type":"regular","start_time":"07:00:00","end_time":"15:00:00"}' \
  http://localhost:8000/api/v1/shifts

Response (201 Created):

{
  "id": "uuid-1",
  "membernumber": "35973",
  "shift_date": "2025-01-06",
  "shift_type": "regular",
  "start_time": "07:00:00",
  "end_time": "15:00:00",
  "created_at": "2025-01-01T12:00:00Z",
  "updated_at": "2025-01-01T12:00:00Z"
}

Get Single Shift

GET /api/v1/shifts/{shift_id}

Get details for a specific shift.

Parameters:

  • shift_id (path) - Shift UUID

Request:

curl -H "X-Dev-Member-Number: 35973" \
  http://localhost:8000/api/v1/shifts/uuid-1

Response:

{
  "id": "uuid-1",
  "membernumber": "35973",
  "shift_date": "2025-01-06",
  "shift_type": "regular",
  "start_time": "07:00:00",
  "end_time": "15:00:00",
  "created_at": "2025-01-01T12:00:00Z",
  "updated_at": "2025-01-01T12:00:00Z"
}

Update Shift

PUT /api/v1/shifts/{shift_id}

Update an existing shift. Supports partial updates.

Parameters:

  • shift_id (path) - Shift UUID

Request Body (partial update example):

{
  "end_time": "16:00:00"
}

Request:

curl -X PUT \
  -H "Content-Type: application/json" \
  -H "X-Dev-Member-Number: 35973" \
  -d '{"end_time":"16:00:00"}' \
  http://localhost:8000/api/v1/shifts/uuid-1

Response:

{
  "id": "uuid-1",
  "membernumber": "35973",
  "shift_date": "2025-01-06",
  "shift_type": "regular",
  "start_time": "07:00:00",
  "end_time": "16:00:00",
  "created_at": "2025-01-01T12:00:00Z",
  "updated_at": "2025-01-01T13:00:00Z"
}

Delete Shift

DELETE /api/v1/shifts/{shift_id}

Soft-delete a shift. Cascades to all related premium time entries.

Parameters:

  • shift_id (path) - Shift UUID

Request:

curl -X DELETE \
  -H "X-Dev-Member-Number: 35973" \
  http://localhost:8000/api/v1/shifts/uuid-1

Response (204 No Content)

Get Aggregated Shifts

GET /api/v1/shifts/aggregated

Get shifts with nested premium time entries.

Query Parameters:

  • pay_period (required) - Pay period number (1-26)
  • year (required) - Year

Request:

curl -H "X-Dev-Member-Number: 35973" \
  "http://localhost:8000/api/v1/shifts/aggregated?pay_period=1&year=2025"

Response:

{
  "shifts": [
    {
      "id": "uuid-1",
      "shift_date": "2025-01-06",
      "shift_type": "regular",
      "start_time": "07:00:00",
      "end_time": "15:00:00",
      "ojti_time": [
        {
          "id": "uuid-ojti-1",
          "start_time": "07:00:00",
          "end_time": "09:00:00"
        }
      ],
      "cic_time": [],
      "tos_tnw": []
    }
  ]
}

Get Shift Summary

GET /api/v1/shifts/summary

Get calculated totals for a pay period.

Query Parameters:

  • pay_period (required) - Pay period number (1-26)
  • year (required) - Year

Request:

curl -H "X-Dev-Member-Number: 35973" \
  "http://localhost:8000/api/v1/shifts/summary?pay_period=1&year=2025"

Response:

{
  "pay_period": 1,
  "year": 2025,
  "total_regular_hours": 80.0,
  "total_ojti_hours": 4.0,
  "total_cic_hours": 2.0,
  "total_tos_hours": 0.0,
  "total_tnw_hours": 0.0
}

OJTI Time Endpoints

Create OJTI Time

POST /api/v1/ojti-time

Add OJTI time to a shift.

Request Body:

{
  "shift_id": "uuid-1",
  "start_time": "07:00:00",
  "end_time": "09:00:00"
}

Request:

curl -X POST \
  -H "Content-Type: application/json" \
  -H "X-Dev-Member-Number: 35973" \
  -d '{"shift_id":"uuid-1","start_time":"07:00:00","end_time":"09:00:00"}' \
  http://localhost:8000/api/v1/ojti-time

Response (201 Created):

{
  "id": "uuid-ojti-1",
  "shift_id": "uuid-1",
  "start_time": "07:00:00",
  "end_time": "09:00:00",
  "created_at": "2025-01-01T12:00:00Z"
}

Update OJTI Time

PUT /api/v1/ojti-time/{ojti_id}

Update OJTI time entry.

Request:

curl -X PUT \
  -H "Content-Type: application/json" \
  -H "X-Dev-Member-Number: 35973" \
  -d '{"end_time":"10:00:00"}' \
  http://localhost:8000/api/v1/ojti-time/uuid-ojti-1

Delete OJTI Time

DELETE /api/v1/ojti-time/{ojti_id}

Delete OJTI time entry.

Request:

curl -X DELETE \
  -H "X-Dev-Member-Number: 35973" \
  http://localhost:8000/api/v1/ojti-time/uuid-ojti-1

Response (204 No Content)


CIC Time Endpoints

Create CIC Time

POST /api/v1/cic-time

Add CIC time to a shift.

Request Body:

{
  "shift_id": "uuid-1",
  "start_time": "07:00:00",
  "end_time": "08:00:00"
}

Request:

curl -X POST \
  -H "Content-Type: application/json" \
  -H "X-Dev-Member-Number: 35973" \
  -d '{"shift_id":"uuid-1","start_time":"07:00:00","end_time":"08:00:00"}' \
  http://localhost:8000/api/v1/cic-time

Response (201 Created):

{
  "id": "uuid-cic-1",
  "shift_id": "uuid-1",
  "start_time": "07:00:00",
  "end_time": "08:00:00",
  "created_at": "2025-01-01T12:00:00Z"
}

Update CIC Time

PUT /api/v1/cic-time/{cic_id}

Update CIC time entry.

Delete CIC Time

DELETE /api/v1/cic-time/{cic_id}

Delete CIC time entry.


TOS/TNW Endpoints

Create TOS/TNW Entry

POST /api/v1/tos-tnw

Add TOS or TNW entry to a shift.

Request Body:

{
  "shift_id": "uuid-1",
  "entry_type": "tos",
  "sub_type": "overtime",
  "start_time": "15:00:00",
  "end_time": "17:00:00"
}

TOS Sub-types:

  • overtime - Overtime
  • credit_hours_earned - Credit Hours Earned

TNW Sub-types:

  • annual_leave - Annual Leave
  • sick_leave - Sick Leave
  • credit_hours_used - Credit Hours Used
  • comp_time_used - Comp Time Used
  • furlough - Furlough
  • lwop - Leave Without Pay
  • admin_leave - Administrative Leave
  • military_leave - Military Leave
  • holiday_leave - Holiday Leave
  • gov_shutdown - Government Shutdown
  • other - Other

Request:

curl -X POST \
  -H "Content-Type: application/json" \
  -H "X-Dev-Member-Number: 35973" \
  -d '{"shift_id":"uuid-1","entry_type":"tos","sub_type":"overtime","start_time":"15:00:00","end_time":"17:00:00"}' \
  http://localhost:8000/api/v1/tos-tnw

Response (201 Created):

{
  "id": "uuid-tos-1",
  "shift_id": "uuid-1",
  "entry_type": "tos",
  "sub_type": "overtime",
  "start_time": "15:00:00",
  "end_time": "17:00:00",
  "created_at": "2025-01-01T12:00:00Z"
}

Update TOS/TNW Entry

PUT /api/v1/tos-tnw/{entry_id}

Update TOS/TNW entry.

Delete TOS/TNW Entry

DELETE /api/v1/tos-tnw/{entry_id}

Delete TOS/TNW entry.


LES Parser Endpoints

Parse LES PDF

POST /api/parse

Parse a Leave & Earnings Statement PDF.

Request (multipart/form-data):

curl -X POST \
  -F "file=@/path/to/les.pdf" \
  http://localhost:8000/api/parse

Response:

{
  "pay_period": {
    "pay_period_number": 1,
    "year": 2025,
    "membernumber": "35973",
    "hourly_base_rate": 45.50
  },
  "earnings": [
    {
      "description": "Regular",
      "hours": 80.0,
      "amount": 3640.00
    }
  ],
  "deductions": [
    {
      "description": "Federal Tax",
      "amount": 728.00
    }
  ],
  "leave_balances": {
    "annual_leave": 120.0,
    "sick_leave": 80.0
  }
}

Verification Endpoints

Verify Pay

POST /api/verify

Verify expected vs actual pay for a pay period.

Request Body:

{
  "membernumber": "35973",
  "pay_period": 1,
  "year": 2025
}

Request:

curl -X POST \
  -H "Content-Type: application/json" \
  -H "X-Dev-Member-Number: 35973" \
  -d '{"membernumber":"35973","pay_period":1,"year":2025}' \
  http://localhost:8000/api/verify

Response:

{
  "pay_period": 1,
  "year": 2025,
  "expected_pay": 3640.00,
  "actual_pay": 3640.00,
  "difference": 0.00,
  "status": "match",
  "discrepancies": []
}

Possible Status Values:

  • match - Expected and actual pay match (within $0.01)
  • discrepancy - Difference detected
  • missing_data - Insufficient data for verification

Error Responses

Validation Error (422)

{
  "detail": [
    {
      "type": "missing",
      "loc": ["body", "shift_date"],
      "msg": "Field required",
      "input": {}
    }
  ]
}

Authentication Error (401)

{
  "detail": "Not authenticated"
}

Authorization Error (403)

{
  "detail": "Insufficient permissions"
}

Not Found (404)

{
  "detail": "Shift not found"
}

Data Models

Shift

{
  id: string (uuid)
  membernumber: string
  shift_date: string (ISO date)
  shift_type: "regular" | "tos" | "tnw"
  start_time: string (HH:MM:SS)
  end_time: string (HH:MM:SS)
  created_at: string (ISO datetime)
  updated_at: string (ISO datetime)
}

OJTI Time

{
  id: string (uuid)
  shift_id: string (uuid)
  start_time: string (HH:MM:SS)
  end_time: string (HH:MM:SS)
  created_at: string (ISO datetime)
}

CIC Time

{
  id: string (uuid)
  shift_id: string (uuid)
  start_time: string (HH:MM:SS)
  end_time: string (HH:MM:SS)
  created_at: string (ISO datetime)
}

TOS/TNW Entry

{
  id: string (uuid)
  shift_id: string (uuid)
  entry_type: "tos" | "tnw"
  sub_type: string
  start_time: string (HH:MM:SS)
  end_time: string (HH:MM:SS)
  created_at: string (ISO datetime)
}

Rate Limits

Currently no rate limits are enforced. Production deployment will implement:

  • 100 requests per minute per user
  • 1000 requests per hour per user

Related Documentation


For interactive API exploration, visit the Swagger UI (opens in a new tab) in your local development environment.