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
- Development: http://localhost:8000 (opens in a new tab)
- Production: https://your-backend.railway.app (opens in a new tab)
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/shiftsProduction Mode
Use the Authorization header with JWT token:
curl -H "Authorization: Bearer <jwt-token>" \
https://api.example.com/api/v1/shiftsInteractive Documentation
- Swagger UI: http://localhost:8000/docs (opens in a new tab)
- ReDoc: http://localhost:8000/redoc (opens in a new tab)
Common Response Codes
| Status Code | Meaning |
|---|---|
| 200 | Success |
| 201 | Created |
| 204 | No Content (successful delete) |
| 400 | Bad Request (validation error) |
| 401 | Unauthorized (missing/invalid auth) |
| 403 | Forbidden (insufficient permissions) |
| 404 | Not Found |
| 422 | Unprocessable Entity (validation error) |
| 500 | Internal Server Error |
Endpoints
Health Check
GET /health
Check API health and database connectivity.
Request:
curl http://localhost:8000/healthResponse:
{
"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-dataResponse:
{
"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/ZDCResponse:
[
{
"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/currentResponse:
{
"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 shifttos- 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/shiftsResponse (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-1Response:
{
"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-1Response:
{
"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-1Response (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-timeResponse (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-1Delete 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-1Response (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-timeResponse (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- Overtimecredit_hours_earned- Credit Hours Earned
TNW Sub-types:
annual_leave- Annual Leavesick_leave- Sick Leavecredit_hours_used- Credit Hours Usedcomp_time_used- Comp Time Usedfurlough- Furloughlwop- Leave Without Payadmin_leave- Administrative Leavemilitary_leave- Military Leaveholiday_leave- Holiday Leavegov_shutdown- Government Shutdownother- 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-tnwResponse (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/parseResponse:
{
"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/verifyResponse:
{
"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 detectedmissing_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.