API Documentation
Hoursight is an HTTP API. Every endpoint is bearer-token authenticated except /health, /readiness, /metrics, and /docs. All responses are JSON except PDF reports.
Alpha access. Bearer tokens are issued manually during the pilot. Email dbacon@aioelectric.org.
Authentication
Include your bearer token in the Authorization header on every request:
curl https://api.hoursight.ai/v1/jobs \
-H "Authorization: Bearer YOUR_TOKEN"
Submitting a calibration
- Upload your labor DB, historical bids, and job cost export.
- Submit a job referencing those uploads.
- Poll until
status: "completed". - Download the signed report and CSV.
1. Upload files
curl -X POST https://api.hoursight.ai/v1/uploads/labor_db \
-H "Authorization: Bearer $TOKEN" \
-F "file=@labor_db.xlsx"
# Returns: { "upload_id": "up_abc123", "kind": "labor_db", "sha256": "..." }
2. Submit the job
curl -X POST https://api.hoursight.ai/v1/jobs \
-H "Authorization: Bearer $TOKEN" \
-H "Content-Type: application/json" \
-H "Idempotency-Key: nightly-2026-09-17" \
-d '{
"labor_db_upload_id": "up_abc123",
"bids_upload_id": "up_def456",
"job_cost_upload_id": "up_ghi789",
"options": { "matcher": "fuzzy" }
}'
# Returns: { "job_id": "job_xyz", "status": "queued" }
3. Poll status
curl https://api.hoursight.ai/v1/jobs/job_xyz \
-H "Authorization: Bearer $TOKEN"
# Returns: { "job_id": "job_xyz", "status": "running" | "completed" | "failed",
# "progress": 0.65, "eta_seconds": 42 }
4. Download outputs
curl https://api.hoursight.ai/v1/jobs/job_xyz/report \
-H "Authorization: Bearer $TOKEN" \
-o calibration-report.pdf
curl https://api.hoursight.ai/v1/jobs/job_xyz/diff.pdf \
-H "Authorization: Bearer $TOKEN" \
-o month-over-month-diff.pdf
Webhooks
Register an HTTPS URL to receive job lifecycle events. Every payload is signed with an HMAC-SHA256 header for verification.
curl -X POST https://api.hoursight.ai/v1/webhooks \
-H "Authorization: Bearer $TOKEN" \
-H "Content-Type: application/json" \
-d '{
"url": "https://your-app.example.com/hooks/hoursight",
"events": ["job.completed", "job.failed"]
}'
# Returns: { "webhook_id": "wh_...", "signing_secret": "whsec_..." }
Verify with:
expected = hmac_sha256(signing_secret, timestamp + "." + body)
provided = request.headers["Hoursight-Signature"] # "v1,"
assert constant_time_compare(expected, provided)
Rate limits
Per tenant: 60 uploads/hr, 240 job submissions/day, 3600 read requests/hr. Exceeded requests return 429 with a Retry-After header.
Idempotency
Every mutating endpoint accepts an Idempotency-Key header. Replaying a request with the same key within 24 hours returns the original response.
Error responses
{
"error": {
"code": "invalid_upload_kind",
"message": "labor_db must be an .xlsx file",
"request_id": "req_abc..."
}
}
Always include the request_id when reporting issues — it lets us trace the full request through logs.
Status codes we return
200Success201Resource created400Invalid input401Missing or bad bearer token403Token can't access this resource404Resource not found409Idempotency conflict422Semantic validation error429Rate limit
OpenAPI
Interactive docs live at /docs on the live API (once your token is issued).