Overview
Every request is scoped to one agency — the agency that owns the API key you send. All timestamps are ISO 8601 in UTC. All responses are JSON.
Export off-duty time
Approved (or any status) time entries with officer, shift, and job detail, ready for payroll or your records system.
Ingest on-duty time
Send regular-duty shifts and approved leave so Cadence can see conflicts before an officer takes an off-duty job.
| Endpoint | Method | Purpose |
|---|---|---|
| /api/public/v1/export/off-duty-time | GET | Read off-duty time entries for a date range. |
| /api/public/v1/ingest/on-duty | POST | Create or update on-duty shift and leave records. |
Base URL: https://cadencepublicsafety.com
Get a token
- Sign in as an agency admin.
- Go to Settings → Integrations.
- Give the key a name that says where it will be used, then create it.
- Copy the key immediately — it starts with
cdk_and is shown only once. Cadence stores a hash, not the key itself. - Revoke a key at any time from the same page; revoked keys stop working instantly.
Treat a key like a password. Use a separate key per integration so you can revoke one without breaking the others.
Authentication
Send the key as a bearer token on every request.
Authorization: Bearer cdk_your_key_hereA missing key returns 401 with { "error": "Missing API key" }. An unknown or revoked key returns 401 with { "error": "Invalid or revoked API key" }.
Export off-duty time
GET /api/public/v1/export/off-duty-time
Query parameters
| Parameter | Type | Description |
|---|---|---|
| from | required, YYYY-MM-DD | Start of the range, matched against check-in time. |
| to | required, YYYY-MM-DD | End of the range, matched against check-in time. |
| status | optional | One of approved, submitted, open, rejected, or all. Defaults to approved. |
Example request
curl -H "Authorization: Bearer cdk_your_key_here" \
"https://cadencepublicsafety.com/api/public/v1/export/off-duty-time?from=2026-01-01&to=2026-01-31&status=approved"Example response
{
"agency_id": "0b1e...",
"from": "2026-01-01",
"to": "2026-01-31",
"status": "approved",
"count": 1,
"records": [
{
"id": "e2b0...",
"officer": { "id": "9a1c...", "name": "J. Rivera", "email": "j.rivera@agency.gov" },
"shift": {
"id": "5f3d...",
"starts_at": "2026-01-14T22:00:00.000Z",
"ends_at": "2026-01-15T04:00:00.000Z"
},
"job": {
"id": "77c9...",
"title": "Stadium gate security",
"event_name": "Winter Classic",
"location_address": "500 Main St",
"business_name": "Riverside Arena"
},
"check_in_at": "2026-01-14T21:56:00.000Z",
"check_out_at": "2026-01-15T04:03:00.000Z",
"hours": 6.1,
"notes": null,
"status": "approved",
"reviewed_at": "2026-01-16T15:02:00.000Z"
}
]
}Record fields
| Field | Type | Description |
|---|---|---|
| id | uuid | Time entry identifier — stable, use it to dedupe. |
| officer | object | Officer id, name, and email. |
| shift | object | Scheduled shift id and start/end times. |
| job | object | Job id, title, event name, address, and requesting business name. |
| check_in_at | timestamp | When the officer checked in. |
| check_out_at | timestamp | null | When the officer checked out. |
| hours | number | null | Recorded hours for the entry. |
| notes | string | null | Officer notes on the entry. |
| status | string | open, submitted, approved, or rejected. |
| reviewed_at | timestamp | null | When an admin reviewed it. |
Records are returned newest check-in first.
Ingest on-duty time
POST /api/public/v1/ingest/on-duty
Send a records array — up to 500 per request. Each record is matched to an officer by email and must belong to an active member of your agency. Sending the same external_id again updates the existing record instead of creating a duplicate, so you can safely re-send.
Record fields
| Field | Type | Description |
|---|---|---|
| officer_email | required, string | Email of an active officer in your agency. Case-insensitive. |
| external_id | required, string | Your system's id for this shift or leave. Used to update instead of duplicate. |
| type | required | Either shift or leave. |
| starts_at | required, ISO timestamp | Start of the block. |
| ends_at | required, ISO timestamp | End of the block. Must be after starts_at. |
| source | optional, string | Label for where the record came from. Defaults to api. |
Example request
curl -X POST \
-H "Authorization: Bearer cdk_your_key_here" \
-H "Content-Type: application/json" \
-d '{
"records": [
{
"officer_email": "j.rivera@agency.gov",
"external_id": "rms-shift-88213",
"type": "shift",
"starts_at": "2026-01-14T06:00:00Z",
"ends_at": "2026-01-14T16:00:00Z",
"source": "rms"
}
]
}' \
"https://cadencepublicsafety.com/api/public/v1/ingest/on-duty"Example response
{
"agency_id": "0b1e...",
"received": 1,
"accepted": 1,
"results": [
{ "external_id": "rms-shift-88213", "status": "ok" }
]
}The request returns 200 even when some records fail. Check each entry in results: a failed one has "status": "error" and a message, for example an officer email that is not an active member.
Errors
| Status | Meaning | Common cause |
|---|---|---|
| 400 | Bad request | Missing from/to, bad date, unknown status value, invalid JSON, no records array, or more than 500 records. |
| 401 | Unauthorized | No Authorization header, or the key is unknown or revoked. |
| 500 | Server error | Cadence could not read or write the records. Retry, then contact support. |
Error bodies are always { "error": "message" }.
Limits and behavior
- Requests only ever see and write data for the agency that owns the key.
- Maximum 500 records per ingest request — split larger syncs into batches.
- Timestamps must be ISO 8601; send UTC to avoid ambiguity.
- Export filters on check-in time, not on shift start time.
- Each key records a last-used time so you can spot stale integrations.
- Ingest is idempotent per external id — nightly full re-syncs are safe.
Need something the API doesn't cover?
Tell us what your records or CAD system needs and we'll look at adding it.
Contact us