The fleet & telematics data plane for your integration.
A public REST API for reading and acting on fleets, vehicles, drivers, journeys, live positions, video and device commands. Built for third-party developers.
From credentials to first call in four steps.
Every endpoint requires an OAuth2 client-credentials bearer token carrying the scopes it needs. There is no user login: this is a server-to-server integration.
Get a client_id and client_secret for your integration from
your Haloniq administrator. Credentials are scoped to your organization and its
descendant tenants.
Exchange your credentials at the identity token endpoint using the
client_credentials grant. Ask only for the scopes you need.
curl -X POST https://auth.tst.haloniq.com/connect/token \
-d grant_type=client_credentials \
-d client_id=YOUR_CLIENT_ID \
-d client_secret=YOUR_CLIENT_SECRET \
-d scope="thirdparty.api.fleets_read thirdparty.api.vehicles_read"
Send the returned access_token as a Bearer header. Responses
are snake_case JSON; list endpoints are cursor-paginated.
curl https://app-haloniq-developerapi-tst-weu.azurewebsites.net/v1/fleets \
-H "Authorization: Bearer $ACCESS_TOKEN"
Browse every resource, schema and example in the Scalar reference, or pull the machine-readable OpenAPI 3.1 document to generate a client SDK.
One scope per resource and action.
Requests are authorized by OAuth2 scopes. Read and write are granted
separately, and device commands are split so wake-up can be allowed without reboot.
A missing scope returns 403.
| Scope | Grants |
|---|---|
| Read | |
thirdparty.api.organizations_read | Read organizations across your tenant hierarchy. |
thirdparty.api.fleets_read | Read fleets and their membership. |
thirdparty.api.vehicles_read | Read vehicles and vehicle telemetry. |
thirdparty.api.drivers_read | Read driver records. |
thirdparty.api.devices_read | Read devices, device telemetry and media items. |
thirdparty.api.events_read | Search telematics events over a date window. |
thirdparty.api.journeys_read | Query completed journeys for a vehicle, fleet or device. |
thirdparty.api.live_positions_read | Read real-time vehicle positions. |
thirdparty.api.webhooks_read | Read webhook subscription configuration. |
| Write | |
thirdparty.api.fleets_write | Create and update fleets. |
thirdparty.api.vehicles_write | Create and update vehicles. |
thirdparty.api.drivers_write | Update driver records. |
thirdparty.api.devices_write | Assign and unassign devices. |
thirdparty.api.video_requests_write | Request video and media uploads from devices. |
thirdparty.api.live_streams_write | Start live video streams from device cameras. |
thirdparty.api.playback_sessions_write | Start video playback sessions. |
thirdparty.api.webhooks_write | Create and manage webhook subscriptions. |
| Device commands | |
thirdparty.api.device_commands_reboot | Send a reboot command to a device. |
thirdparty.api.device_commands_wake_up | Send a wake-up command to a device. |
What the API exposes.
A versioned surface over the platform's fleet and telematics data
plane, all under /v1.
Read and manage your fleet structure and vehicle records, including per-vehicle telemetry.
Read and update driver records across your organization.
Query completed journeys over a date window for a vehicle, fleet or device.
Poll real-time vehicle positions across the fleets you can see.
Search telematics events with narrowing filters and a date window.
List device media with signed download URLs and request new video uploads.
Start live streams and playback sessions from device cameras.
List devices, read telemetry, assign to vehicles, and reboot or wake them remotely.
Traverse your tenant's organization hierarchy and descendants.
Subscribe to CloudEvents 1.0 messages the platform POSTs to your endpoints.
Push, not poll: CloudEvents delivered to your endpoint.
Subscribe once and the platform POSTs a
application/cloudevents+json envelope to your endpoint whenever something
happens on your fleet. Every message follows the CloudEvents 1.0 specification, so
the envelope is identical across all topic types. Only the data payload
changes with the event type.
The CloudEvents envelope
| Field | Type | Notes |
|---|---|---|
id | string | Required. Producer idempotency key (event / alarm id, or hardwareId:utc). De-duplicate on this. |
source | uri-reference | Required. Origin of the event, e.g. /brigade/events, /brigade/livetrack. |
type | string | Required. One of the five topic types below. Discriminates the data schema. |
specversion | string | Required. Constant "1.0". |
time | date-time | Device wire time (not processing time). |
datacontenttype | string | Media type of data; defaults to application/json. |
subject | string | Device / hardware id. |
dataschema | uri | Schema URI for the data payload. |
data | object | The type-specific payload, one of the five records below. |
Topic types & payloads
Five outbound topic types. The CloudEvents type discriminates
the schema. Payload property names are camelCase, unlike the snake_case REST API.
| CloudEvents type | Fires on | Payload highlights |
|---|---|---|
live.gps | Each live GPS fix (topic live_gps) | hardwareId, latitude, longitude, heading, speed, utc, invalidGps |
journey.closed | A journey completes (topic journeys) | journeyId, hardwareId, startUtc / endUtc, start & end lat-long, distanceMeters, durationSeconds |
alarm.triggered | A device raises an alarm (topic alarms) | id, hardwareId, triggerOn, alarmKey, locations[], speedPoints[], headingPoints[] |
event.generic | A telematics event (topic events) | eventId, hardwareId, triggerUtc, eventTypes[], eventCategories[], isAccident, locations[], address, vehicleId |
media.ready | Requested media is ready (topic media) | mediaId, hardwareId, fileName, mimeType, channel, downloadUrl (time-limited SAS) |
Delivery transports
The delivery plane fans each event out to the subscriber's chosen transport. Through the Developer API today you subscribe over HTTPS callback; the Event Hub and Kafka transports are platform capabilities provisioned out-of-band (roadmap for self-serve).
| Transport | Via Developer API | Best for |
|---|---|---|
HttpCallback | Yes, the default and only self-serve transport. POSTs the CloudEvents envelope to your HTTPS URL. | Any subscriber with an HTTPS endpoint. |
AzureEventHub | Platform / roadmap, provisioned out-of-band. | High-volume Azure-native streaming into your own Event Hub. |
Kafka | Platform / roadmap, provisioned out-of-band (SASL/SSL). | Streaming into your own Kafka cluster. |
Delivery headers & retries
| Header | Meaning |
|---|---|
X-Webhook-Key | Your organization's shared auth key, sent verbatim so you can authenticate the sender. Declared in the OpenAPI webhooks doc. |
X-Delivery-Attempt | 1-based attempt number; a value >1 means a retry after a prior non-2xx / timeout. De-duplicate against the envelope id. Declared in the OpenAPI webhooks doc. |
Idempotency-Key | Stable per (event, destination) delivery id. Use it for exactly-once handling. |
traceparent | W3C trace context, when present. |
Optional per-destination custom headers exist at the delivery-worker level but are not settable through the Developer API today.
Authentication is a shared key, not a signature. Each delivery carries your
X-Webhook-Key verbatim so you can authenticate the sender. There is
no HMAC and no payload signing today, so verify the shared key on receipt and
rotate it via the regenerate-auth-key endpoint. Producer instances can retain
the previous key for up to 60 seconds while the change propagates.
Subscribing with the webhook-config API
Manage your subscription under
/v1/organizations/{organizationId}/webhook-config. Reads require
thirdparty.api.webhooks_read; every mutation requires
thirdparty.api.webhooks_write. Optimistic concurrency uses
ETag / If-Match (stale → 412); secrets are never
returned by GET (only an auth_key_hint).
| Method & path | Scope | Purpose |
|---|---|---|
GET /webhook-config | webhooks_read | Read the config; 404 if none. Secrets redacted to auth_key_hint. |
PUT /webhook-config | webhooks_write | Create-or-replace the whole config. The first config must use is_enabled: false until its key is captured; immediate activation returns 422 webhook.initial_activation_forbidden. If-Match is required once it exists. |
PATCH /webhook-config | webhooks_write | Enable / disable delivery. Requires the current If-Match; allow up to 60 seconds for propagation. |
DELETE /webhook-config | webhooks_write | Delete the config. Requires the current If-Match; returns 204. Delivery suppression can take up to 60 seconds. |
POST /webhook-config/regenerate-auth-key | webhooks_write | Rotate the auth key; the raw key is returned exactly once and cannot be retrieved later. Store it immediately. A concurrent update can return 412; the previous key can remain in use for up to 60 seconds. |
PUT /webhook-config/topics/{topicType} | webhooks_write | Upsert one topic (max 25). Requires the current If-Match. {topicType} = events | alarms | media | live_gps | journeys. |
DELETE /webhook-config/topics/{topicType} | webhooks_write | Remove one topic. Requires the current If-Match. The last remaining topic cannot be removed (409). |
# 1. Create the whole configuration (omit If-Match only on first creation)
curl -X PUT https://app-haloniq-developerapi-tst-weu.azurewebsites.net/v1/organizations/$ORG_ID/webhook-config \
-H "Authorization: Bearer $ACCESS_TOKEN" \
-H "Content-Type: application/json" \
-d '{"is_enabled":false,"topics":[{"type":"journeys","is_enabled":true,"delivery":{"url":"https://your-app.example.com/hooks/haloniq"}}]}'
# 2. Generate and capture the delivery key now; GET will never return it
AUTH_KEY=$(curl -fsS -X POST \
https://app-haloniq-developerapi-tst-weu.azurewebsites.net/v1/organizations/$ORG_ID/webhook-config/regenerate-auth-key \
-H "Authorization: Bearer $ACCESS_TOKEN" \
| jq -er '.auth_key')
printf 'Store this webhook key in your secret manager now:\n%s\n' "$AUTH_KEY"
# 3. Read the current ETag after key rotation
ETAG=$(curl -fsS -D - -o /dev/null \
https://app-haloniq-developerapi-tst-weu.azurewebsites.net/v1/organizations/$ORG_ID/webhook-config \
-H "Authorization: Bearer $ACCESS_TOKEN" \
| sed -n 's/^[Ee][Tt][Aa][Gg]:[[:space:]]*//p' | tr -d '\r')
# 4. Enable delivery only after your receiver accepts the stored key
curl -X PATCH https://app-haloniq-developerapi-tst-weu.azurewebsites.net/v1/organizations/$ORG_ID/webhook-config \
-H "Authorization: Bearer $ACCESS_TOKEN" \
-H "Content-Type: application/json" \
-H "If-Match: $ETAG" \
-d '{"is_enabled":true}'
The raw auth key is unrecoverable. Move $AUTH_KEY into
your secret manager immediately, never commit or log it, and then unset AUTH_KEY.
Every later GET returns only auth_key_hint; losing the raw value requires
another key rotation.
After every successful mutation, read the configuration again and use its
new ETag for the next conditional write.
At write time, callback URLs must be absolute HTTPS, ≤2048 chars.
Literal hosts in loopback, link-local, IPv4 private, IPv6 unique-local and IPv4-mapped blocked
ranges return 422 webhook.ssrf_blocked. Hostnames are not DNS-resolved during this
write-time check, so a hostname that later resolves to a private address can still be saved.
At delivery time the dispatcher independently resolves and pins an approved public destination
IP; unsafe DNS answers or rebinding fail closed and no callback is sent.
Generate a typed client from the OpenAPI 3.1 document.
The API publishes a machine-readable
OpenAPI 3.1 document at /openapi/v1.json. Point a
generator at it and get a fully typed client with no hand-written HTTP. Two
generators are supported: Microsoft Kiota (the partner-SDK generator, multi-language)
and NSwag (a dependency-light single-file C# client).
Microsoft Kiota: multi-language typed client
Kiota emits a request-builder tree
(client.V1.Fleets[id].GetAsync()) and generates for C#, Java, Go, Python,
TypeScript, PHP and Ruby from the same spec. OpenAPI 3.1 is detected natively, so no flag is needed.
# freeze the OpenAPI 3.1 document as the generation input
curl -fsSL https://app-haloniq-developerapi-tst-weu.azurewebsites.net/openapi/v1.json -o v1.json
# one-time: install the generator
dotnet tool install --global Microsoft.OpenApi.Kiota
# generate the typed C# client (multi-file, request-builder tree)
kiota generate \
-l CSharp \
-d v1.json \
-o ./sdk/kiota \
-n Haloniq.DeveloperApi.Client \
-c HaloniqApiClient \
--clean-output
NSwag: single-file C# client
NSwag emits one .cs file with flat methods
(client.GetFleetAsync(id)). Pin a 3.1-capable 14.x build, because 13.x
mishandles the 3.1 type-arrays.
# one-time: install a 3.1-capable build (pin 14.7.1)
dotnet tool install --global NSwag.ConsoleCore --version 14.7.1
# emit a single-file C# client
nswag openapi2csclient \
/input:v1.json \
/classname:HaloniqApiClient \
/namespace:Haloniq.DeveloperApi.Client \
/output:HaloniqApiClient.cs
Which to choose
| Dimension | Kiota | NSwag |
|---|---|---|
| Output shape | Many files: models plus a request-builder tree | One .cs file |
| Call style | Fluent: client.V1.Fleets[id].GetAsync() | Flat: client.GetFleetAsync(id) |
| Languages | C# / Java / Go / Python / TypeScript / PHP / Ruby | C# / TypeScript |
| Serialization | System.Text.Json (pluggable) | Newtonsoft.Json by default (System.Text.Json opt-in) |
| Dependencies | Microsoft.Kiota.Bundle | Just Newtonsoft.Json (none with System.Text.Json) |
| OpenAPI 3.1 | Native, no flag | Needs a 14.x build |
Rule of thumb: ship a partner SDK with Kiota when you need more than one
target language: one spec generates C#, Java, Python, Go and TypeScript from the
same tool. Reach for NSwag when you want a dependency-light, drop-in single C# file
for a quick smoke call, a console harness, or an independent cross-check of the contract.
Both parse the 3.1.1 document; verify by running dotnet build on
the generated project and confirming 0 errors.
Live generation input
Use the deployed OpenAPI 3.1 document as the
source of truth. Freeze that document in your own release pipeline, commit the generated client
in your integration repository, and regenerate it after a spec change with kiota update
or by re-running the NSwag command above.