Voxbi Cockpit APIs
All endpoints

Change presence status for the authenticated user (user)

Record a presence status change for the authenticated user. This opens a new timepoint for the selected status and, when the status is mapped to the external Tempus system, propagates the change and returns the updated work-time totals.

status_id accepts either a Cockpit status UUID or an external Tempus mapping identifier, which is resolved to the matching Cockpit status server-side. The user and PBX are always derived from the authenticated token and must not be sent in the request body.

HTTP: bearerAuth

User bearer token. Default authentication for customer-facing endpoints. Obtain a token by calling POST /login with your credentials, then send it on every subsequent request as Authorization: Bearer <token>. The token inherits the permissions and PBX scope of the authenticated user.

HTTP Authorization Scheme
bearer
Bearer format
Bearer <token>
accept header · string
example: application/json

Request body · required

Request schema
status_id*string
Target status. A Cockpit status UUID, or an external Tempus mapping identifier that is resolved server-side.
example: 550e8400-e29b-41d4-a716-446655440000
location_idstring | null · uuid
Optional location to associate with the status. Must reference a location enabled for presence status.
example: 550e8400-e29b-41d4-a716-446655440010
sub_status_idsarray<string>
Optional sub-status identifiers linked to the selected status. A single value unless the status allows multiselect; required when the status mandates a sub-status selection.
[]string · uuid
sourcesarray<string>
Origin channels for the status change.
[]string
triggersarray<string>
Triggers that initiated the status change.
[]string
meta_dataobject | null
Arbitrary metadata, stored verbatim on the time point. GPS coordinates may be sent here (as `lat`/`lng`, `latitude`/`longitude`, or `gpsLat`/`gpsLong`, with an optional `accuracy` and `gpsTimestamp`) or in `gpsData`. Either way the server normalises them onto the time point as `device_gps` and compares them with the selected `location_id`, exposing the result as `location_check`. See the `TempusTimePoint` model. Coordinates sent here are recorded as-is. They are never replaced by the coordinates configured on `location_id`, which is what makes a `location_id` that disagrees with the device's position detectable afterwards.
example: {"lat":49.6116,"lng":6.1319}
gpsDataobject | null
The device's GPS fix, as clients have historically sent it. An alternative container for the coordinates described under `meta_data`: both are accepted, both are stored, and sending both no longer discards this one. Also forwarded to the downstream Tempus webhook. Values are read tolerantly. A fix that is unusable (only one of the pair, non-numeric, or out of range) does not fail the request: it is stored as sent, and `location_check.reason` reports `no_device_gps`.
example: {"gpsLat":49.6116,"gpsLong":6.1319,"accuracy":12.5,"gpsTimestamp":1783418859289}
commentstring | null
Optional free-text comment.
length: 0–255
example: Starting morning shift

Responses

Status change processed without opening a new time point: either the submit collapsed onto the already-open time point (same status and same `location_id` inside the same clock minute), or the status carries no Tempus mapping id so nothing was synced downstream. In the latter case `ui.data` is an empty array.
Response schema
uiobject
dataobject
workTimesobject
Free-form object
messagestring | null
A new time point was opened. `ui.data` is the created time point, including `device_gps` (the coordinates the client sent, stored as sent) and `location_check` (how they compare with the selected location's geofence).
Response schema
uiobject
dataobject
A Tempus time point as serialized by the V1 `TimePointResource`. A time point records a single interval during which an employee was in a given presence or attendance status (for example working, on break, or off). Each time point belongs to exactly one Pbx (tenant) and one user, references the status that was active during the interval, and is bounded by a `start_time` and an optional `end_time`. An open time point (one with `end_time` set to null) represents the employee's current, ongoing status. When a new time point is opened for an employee, any previously open time point for that same employee is automatically closed by setting its `end_time`. This object documents the exact keys returned by the resource, not every raw database column. Columns such as `successive_count`, `comment`, `mapping_id`, `deleted_at`, `created_by`, and `updated_by` exist on the model but are not exposed by this resource. The `sources`, `triggers`, and `meta_data` fields are stored as JSON and returned as free-form objects. Location data comes in three distinct parts that must not be conflated: `location_id` plus the nested `location` object describe the *configured* location record the employee selected; `device_gps` records where the *device* said it was; `location_check` is the comparison of the two.
id*string · uuid
Unique time point identifier.
read-only
example: 550e8400-e29b-41d4-a716-446655440000
pbx_id*string · uuid
Identifier of the Pbx (tenant) that owns this time point.
example: 550e8400-e29b-41d4-a716-446655440001
user_id*string · uuid
Identifier of the user (employee) this time point belongs to.
example: 550e8400-e29b-41d4-a716-446655440002
status_id*string · uuid
Identifier of the Tempus status that was active during this interval (for example working, break, or off). References a `TempusStatus` record.
example: 550e8400-e29b-41d4-a716-446655440003
location_idstring | null · uuid
Identifier of the location the status was recorded against. Null when no location was selected.
example: 550e8400-e29b-41d4-a716-446655440010
locationobject | null
The configured location record referenced by `location_id`, including its own coordinates and geofence radius. Null when `location_id` is null. These are the location's *configured* coordinates, not the device's: see `device_gps` for the latter.
idstring · uuid
example: 550e8400-e29b-41d4-a716-446655440010
namestring
example: HQ Luxembourg
descriptionstring | null
street_nostring | null
example: 12
streetstring | null
example: Rue de Bonnevoie
citystring | null
example: Luxembourg
postalstring | null
example: 1260
country_idstring | null · uuid
example: 550e8400-e29b-41d4-a716-446655440011
latitudenumber | null · double
The location's configured latitude, in degrees.
example: 49.6008
longitudenumber | null · double
The location's configured longitude, in degrees.
example: 6.133
geofence_radius_minteger | null
Geofence radius around the configured point, in metres.
example: 150
is_fixed_locationboolean
example: 1
start_time*string · date-time
Timestamp marking the beginning of the status interval.
example: 2024-03-01T08:29:07Z
end_timestring | null · date-time
Timestamp marking the end of the status interval. Null while the time point is still open, which indicates the employee's current ongoing status.
example: 2024-03-01T12:00:00Z
sourcesobject | null
Free-form JSON object describing the origin systems that contributed to this time point (for example the cockpit UI, a badge reader, or an API client). Structure varies by integration. Null when no source data is recorded.
example: {"channel":"cockpit","device_id":"550e8400-e29b-41d4-a716-446655440010"}
triggersobject | null
Free-form JSON object describing the events that triggered the creation or closing of this time point (for example a manual status change or an automated rollover). Structure varies by integration. Null when no trigger data is recorded.
example: {"type":"manual_change","actor_id":"550e8400-e29b-41d4-a716-446655440002"}
meta_dataobject | null
Free-form JSON object holding additional key-value metadata attached to the time point, exactly as the client sent it. Keys are integration-specific. Null when no metadata is stored. Two keys are written by the server rather than the client: `device_gps` and `location_check`, both also surfaced as top-level fields of this resource (see below).
example: {"note":"Started shift remotely","gpsLat":49.95,"gpsLong":6.133}
device_gpsobject | null
The coordinates the client reported when this status was recorded, stored exactly as sent. Read from whichever field the client already uses (`gpsData`, or GPS keys inside `meta_data`, under any of the historical spellings such as `gpsLat` / `lat` / `latitude`). This is never derived from, corrected by, or overwritten with the coordinates configured on the selected location: it records where the device claimed to be, which is only useful as evidence if it is left untouched. Compare it with `location.latitude` / `location.longitude`, which describe the configured location record instead. Null when the request carried no usable fix (no coordinates at all, only one of the pair, or values outside the valid latitude / longitude range). A request with an unusable fix still succeeds, and whatever it sent is preserved verbatim under `meta_data`.
latitude*number · double
Latitude reported by the device, in degrees.
example: 49.95
longitude*number · double
Longitude reported by the device, in degrees.
example: 6.133
accuracy_mnumber | null · double
Accuracy radius of the fix in metres, as reported by the device. Null when the client sent none. Used as tolerance by `location_check`.
example: 12.5
captured_atstring | null · date-time
The device's GPS timestamp, parsed. Null when the client sent none or sent a value that could not be parsed.
example: 2026-07-07T10:07:39+00:00
captured_at_rawstring | number | null
The GPS timestamp exactly as the client sent it: epoch milliseconds, epoch seconds, or an ISO 8601 string. Kept alongside the parsed value so nothing the client reported is lost.
example: 1783418859289
location_checkobject | null
Comparison of `device_gps` against the selected location's configured coordinates and geofence radius. This is the integrity signal: it shows when an employee recorded a status against a location they were not actually at. Null when the time point has neither a device fix nor a location, and on time points written before this check existed.
location_idstring | null · uuid
The location the status was recorded against, if any.
example: 550e8400-e29b-41d4-a716-446655440010
location_latitudenumber | null · double
The location's configured latitude at the time of the check.
example: 49.6008
location_longitudenumber | null · double
The location's configured longitude at the time of the check.
example: 6.133
geofence_radius_minteger | null
The location's configured geofence radius in metres.
example: 150
distance_minteger | null
Distance in metres between the device fix and the location's configured point. Recorded whenever both sides have coordinates, even when the location has no geofence radius and no in-or-out verdict is possible: the distance alone is the useful signal.
example: 38957
within_geofenceboolean | null
Whether the device fix falls inside the geofence. The device's own reported accuracy is added to the radius as tolerance, so a poor fix slightly outside a small geofence is not treated as a discrepancy. Null when no verdict was possible.
example:
verdictstring
`match`: the device fix is inside the geofence. `mismatch`: the device fix is outside it, meaning the selected location disagrees with where the device said it was. `unknown`: not enough data to judge, see `reason`.
enum: match mismatch unknown
example: mismatch
reasonstring | null
Why the verdict is `unknown`. `no_device_gps`: the request carried no usable fix. `no_location`: no location was selected, or it does not belong to this tenant. `location_without_coordinates`: the location record has no coordinates configured. `no_geofence_radius`: the location has coordinates but no radius, so only `distance_m` is meaningful. Null when the verdict is `match` or `mismatch`.
enum: no_device_gps no_location location_without_coordinates no_geofence_radius null
is_synced*boolean
Whether this time point has been synchronized to the downstream time-and-attendance system. False until the record has been exported.
example:
created_atstring | null · date-time
Timestamp when the time point was created.
read-only
example: 2024-03-01T08:29:07Z
updated_atstring | null · date-time
Timestamp when the time point was last updated.
read-only
example: 2024-03-01T08:29:07Z
workTimesobject
Free-form object
Authorization Token Missing. This error is returned when the authorization token is missing.
Response schema
errorstring
Error message
example: Authorization Token is missing
Data Not Found. This error is returned when the requested data is not found.
Response schema
messagearray<string>
[]string
Another status change for this employee is already in progress and could not be resolved. Retry once after a short delay.
Response schema
datanull
errorsarray<string>
[]string
messagestring
Unprocessable Parameters. This error is returned when a parameter is not valid.
Response schema
messagestring
example: The given data was invalid.
errorsobject
filterarray<string>
[]string
sortarray<string>
[]string
pagearray<string>
[]string
per_pagearray<string>
[]string
post https://cockpit.voxbi.com/api/v1/change-status
Base URL
Request sample
curl -X POST 'https://cockpit.voxbi.com/api/v1/change-status' \
  -H 'Authorization: Bearer YOUR_TOKEN' \
  -H 'Content-Type: application/json' \
  --data '{"status_id":"550e8400-e29b-41d4-a716-446655440000","location_id":"550e8400-e29b-41d4-a716-446655440010","sub_status_ids":["550e8400-e29b-41d4-a716-446655440020"],"sources":["mobile"],"triggers":["manual"],"meta_data":{"app_version":"3.4.1"},"gpsData":{"gpsLat":49.6116,"gpsLong":6.1319,"accuracy":12.5,"gpsTimestamp":1783418859289},"comment":"Starting morning shift"}'
const response = await fetch('https://cockpit.voxbi.com/api/v1/change-status', {
  method: 'POST',
  headers: {
    'Content-Type': 'application/json',
    'Authorization': `Bearer ${YOUR_TOKEN}`,
  },
  body: JSON.stringify({
    "status_id": "550e8400-e29b-41d4-a716-446655440000",
    "location_id": "550e8400-e29b-41d4-a716-446655440010",
    "sub_status_ids": [
        "550e8400-e29b-41d4-a716-446655440020"
    ],
    "sources": [
        "mobile"
    ],
    "triggers": [
        "manual"
    ],
    "meta_data": {
        "app_version": "3.4.1"
    },
    "gpsData": {
        "gpsLat": 49.6116,
        "gpsLong": 6.1319,
        "accuracy": 12.5,
        "gpsTimestamp": 1783418859289
    },
    "comment": "Starting morning shift"
}),
});

const data = await response.json();
console.log(data);
import requests

response = requests.post('https://cockpit.voxbi.com/api/v1/change-status',
    headers={'Authorization': f'Bearer {YOUR_TOKEN}'},
    json={
    "status_id": "550e8400-e29b-41d4-a716-446655440000",
    "location_id": "550e8400-e29b-41d4-a716-446655440010",
    "sub_status_ids": [
        "550e8400-e29b-41d4-a716-446655440020"
    ],
    "sources": [
        "mobile"
    ],
    "triggers": [
        "manual"
    ],
    "meta_data": {
        "app_version": "3.4.1"
    },
    "gpsData": {
        "gpsLat": 49.6116,
        "gpsLong": 6.1319,
        "accuracy": 12.5,
        "gpsTimestamp": 1783418859289
    },
    "comment": "Starting morning shift"
}
)
response.raise_for_status()
data = response.json()
print(data)
<?php
$context = stream_context_create([
    'http' => [
        'method'  => 'POST',
        'header'  => "Content-Type: application/json\r\nAuthorization: Bearer YOUR_TOKEN",
        'content' => '{
    \"status_id\": \"550e8400-e29b-41d4-a716-446655440000\",
    \"location_id\": \"550e8400-e29b-41d4-a716-446655440010\",
    \"sub_status_ids\": [
        \"550e8400-e29b-41d4-a716-446655440020\"
    ],
    \"sources\": [
        \"mobile\"
    ],
    \"triggers\": [
        \"manual\"
    ],
    \"meta_data\": {
        \"app_version\": \"3.4.1\"
    },
    \"gpsData\": {
        \"gpsLat\": 49.6116,
        \"gpsLong\": 6.1319,
        \"accuracy\": 12.5,
        \"gpsTimestamp\": 1783418859289
    },
    \"comment\": \"Starting morning shift\"
}',
    ],
]);

$response = file_get_contents('https://cockpit.voxbi.com/api/v1/change-status', false, $context);
$data = json_decode($response, true);
print_r($data);
Sample request
{}
"status_id": "550e8400-e29b-41d4-a716-446655440000",
"location_id": "550e8400-e29b-41d4-a716-446655440010",
"sub_status_ids": [],
"550e8400-e29b-41d4-a716-446655440020"
],
"sources": [],
"mobile"
],
"triggers": [],
"manual"
],
"meta_data": {},
"app_version": "3.4.1"
},
"gpsData": {},
"gpsLat": 49.6116,
"gpsLong": 6.1319,
"accuracy": 12.5,
"gpsTimestamp": 1783418859289
},
"comment": "Starting morning shift"
}
{}
"ui": {},
"data": []
},
"workTimes": {},
"daily": "08:00:00",
"weekly": "40:00:00"
},
"message": "Status applied"
}
{}
"ui": {},
"data": {}
"id": "550e8400-e29b-41d4-a716-446655440000",
"pbx_id": "550e8400-e29b-41d4-a716-446655440001",
"user_id": "550e8400-e29b-41d4-a716-446655440002",
"status_id": "550e8400-e29b-41d4-a716-446655440003",
"location_id": "550e8400-e29b-41d4-a716-446655440010",
"start_time": "2026-07-07T10:07:39.000000Z",
"end_time": null,
"sources": [],
"mobile"
],
"triggers": [],
"manual"
],
"meta_data": {},
"app_version": "3.4.1",
"gpsLat": 49.95,
"gpsLong": 6.133,
"source": "api_v1",
"device_gps": {},
"latitude": 49.95,
"longitude": 6.133,
"accuracy_m": 12.5,
"captured_at": "2026-07-07T10:07:39+00:00",
"captured_at_raw": 1783418859289
},
"location_check": {}
"location_id": "550e8400-e29b-41d4-a716-446655440010",
"location_latitude": 49.6008,
"location_longitude": 6.133,
"geofence_radius_m": 150,
"distance_m": 38957,
"within_geofence": false,
"verdict": "mismatch",
"reason": null
}
},
"is_synced": false,
"location": {},
"id": "550e8400-e29b-41d4-a716-446655440010",
"name": "HQ Luxembourg",
"latitude": 49.6008,
"longitude": 6.133,
"geofence_radius_m": 150,
"is_fixed_location": true
},
"device_gps": {},
"latitude": 49.95,
"longitude": 6.133,
"accuracy_m": 12.5,
"captured_at": "2026-07-07T10:07:39+00:00",
"captured_at_raw": 1783418859289
},
"location_check": {},
"location_id": "550e8400-e29b-41d4-a716-446655440010",
"location_latitude": 49.6008,
"location_longitude": 6.133,
"geofence_radius_m": 150,
"distance_m": 38957,
"within_geofence": false,
"verdict": "mismatch",
"reason": null
},
"created_at": "2026-07-07T10:07:39.000000Z",
"updated_at": "2026-07-07T10:07:39.000000Z"
}
},
"workTimes": {}
"daily": "08:00:00",
"weekly": "40:00:00"
}
}
{}
"error": "Authorization Token is missing"
}
Cache-Control string
example: private, must-revalidate
Connection string
example: keep-alive
Content-Type string
example: application/json
Vary string
example: Origin
X-RateLimit-Limit integer
Max requests allowed in the current rate-limit window.
example: 60
X-RateLimit-Remaining integer
Requests remaining in the current rate-limit window.
example: 57
{}
"message": []
"Data not found"
]
}
Cache-Control string
example: private, must-revalidate
Connection string
example: keep-alive
Content-Type string
example: application/json
Vary string
example: Origin
X-RateLimit-Limit integer
Max requests allowed in the current rate-limit window.
example: 60
X-RateLimit-Remaining integer
Requests remaining in the current rate-limit window.
example: 57
{}
"data": null,
"errors": [],
"Another status change is already in progress. Please try again."
],
"message": "Status change failed."
}
{}
"errors": {}
"filter": [],
"The filter field must be an array."
],
"sort": [],
"The sort field must be a string."
],
"page": [],
"The page field must be an integer."
],
"per_page": []
"The per page field must be an integer."
]
}
}
Cache-Control string
example: private, must-revalidate
Connection string
example: keep-alive
Content-Type string
example: application/json
Vary string
example: Origin
X-RateLimit-Limit integer
Max requests allowed in the current rate-limit window.
example: 60
X-RateLimit-Remaining integer
Requests remaining in the current rate-limit window.
example: 57