Neyos API
The Neyos API is a REST API that lets you drive your fleet from your own tools: check the state of your trackers, send them remote commands and manage the webhooks that feed your systems in real time.
| Base URL | https://api.neyos-equipment.com/api/v1 |
| Format | JSON (UTF-8) |
| Authentication | Authorization: Bearer ctk_xxxxx on every request |
| Protocol | HTTPS only |
404 error — never the data itself.Authentication
Every request must carry your customer token in the Authorization header:
curl https://api.neyos-equipment.com/api/v1/trackers \
-H "Authorization: Bearer ctk_xxxxxxxxxxxxxxxxxxxxxxx"
A call without a token, or with an invalid, expired or revoked token, returns 401 Unauthorized.
ctk_… token grants access to your whole fleet. Keep it server-side, in a secret manager — never in front-end code, a Git repository or a mobile application.Conventions
Identifiers
Trackers are referenced by their serial, the serial number engraved on the casing. Other resources carry a prefixed identifier:
| Prefix | Resource |
|---|---|
| — | Tracker — referenced by its serial |
tac_ | TrackerAction (command) |
end_ | Webhook (endpoint) |
Dates
On the API, all dates are serialized as Unix milliseconds (number) or null.
tracker.* and message.created events return ISO 8601 dates, while tracker_action.* events return milliseconds. See Date formats.Pagination
List endpoints accept the following parameters:
| Parameter | Type | Default | Description |
|---|---|---|---|
page | integer | 1 | Page number |
perPage | integer | 25 | Items per page (max 100) |
sort | string | varies | Sort field |
order | asc | desc | varies | Sort order |
search | string | — | Text search (fields depend on the endpoint) |
A paginated response always has the following shape:
{
"meta": {
"total": 142,
"perPage": 25,
"currentPage": 1,
"lastPage": 6,
"firstPage": 1
},
"data": [ ]
}
Models
| Field | Type | Description |
|---|---|---|
serial | string | Physical serial number (unique) — your reference |
alias | string | null | Custom name given to the tracker |
notes | string | null | Free-form notes |
network | string | null | Network in use (lte-m, gsm, nb-iot…) |
mcc / mnc | string | null | Mobile Country Code / Mobile Network Code |
rsrq | number | null | Signal quality |
battery | number | null | Battery level (%) |
temperature | number | null | Temperature (°C) |
latitude | number | null | WGS84 latitude |
longitude | number | null | WGS84 longitude |
altitude | number | null | Altitude (m) |
speed | number | null | Speed |
connected | boolean | Tracker currently connected |
connectedAt | number | null | Last connection (ms) |
sleep | boolean | Tracker asleep |
sleepAt | number | null | Entered sleep (ms) |
charging | boolean | Tracker charging |
chargingAt | number | null | Charging started (ms) |
sentAt | number | null | Last frame received (ms) |
lastPointAt | number | null | Last GPS point (ms) |
configSentAt | number | null | Last configuration sent (ms) |
zoneTrack | boolean | Zone tracking (geofencing) enabled |
createdAt | number | Creation date (ms) |
updatedAt | number | Last modification (ms) |
| Field | Type | Description |
|---|---|---|
id | string (tac_…) | Unique command identifier |
serial | string | Serial number of the target tracker |
type | string | Command type (see below) |
payload | object | Commands actually transmitted to the tracker |
priority | number | Priority, from 1 (high) to 100 (low) |
status | string | Current state (see below) |
response | object | null | Acknowledgement returned by the tracker |
createdAt | number | Creation date (ms) |
updatedAt | number | Last update (ms) |
attempts | array | History of send attempts — present on GET /actions/:id |
| Field | Type | Description |
|---|---|---|
id | string (end_…) | Unique identifier |
url | string | HTTP(S) URL called with POST |
description | string | null | Free-form description |
enabled | boolean | Webhook enabled or disabled |
sentAt | number | null | Last call (ms) |
createdAt | number | Creation date (ms) |
updatedAt | number | Last modification (ms) |
Command types
Two command types can be created through the public API:
type | Description | Required fields |
|---|---|---|
custom_commands | Sends a list of textual commands | commands: string[] |
power_off | Powers the tracker off | — |
Other types (load_config, send_config, time_sync…) are generated by the platform. They may appear in a tracker's history or in your webhooks, but cannot be created from the API.
Command states
status | Meaning |
|---|---|
waiting_config_loaded | Waiting for the tracker configuration to be loaded |
waiting | Queued, not transmitted yet |
pending | Transmitted to the tracker, awaiting acknowledgement |
received | Partial acknowledgement received |
success | Executed successfully |
failed | Failed after several attempts |
canceled | Canceled before execution |
merged | Merged with another command |
Typical life cycle: waiting → pending → received → success or failed.
Trackers
List trackers
GET /api/v1/trackers
Parameters: page, perPage, sort (default serial), order, search (filters on serial and alias).
Additional filters — several values can be separated by a comma:
| Parameter | Values |
|---|---|
connected | true | false |
charging | true | false |
sleep | true | false |
zoneTrack | true | false |
curl "https://api.neyos-equipment.com/api/v1/trackers?perPage=10&connected=true&search=depot" \
-H "Authorization: Bearer ctk_xxxxx"
200 response — a paginated object containing an array of Tracker in data.
Retrieve a tracker
GET /api/v1/trackers/:serial
200 response — a Tracker object.
404 error — the tracker does not exist or does not belong to your organization.
Update a tracker
PATCH /api/v1/trackers/:serial
Only the alias and notes fields can be modified. Both are optional and can be set back to null.
{
"alias": "Truck 12 - north depot",
"notes": "Assigned to the Lyon-Grenoble route"
}
200 response — the updated tracker.
tracker.updated webhook event.Command history of a tracker
GET /api/v1/trackers/:serial/actions
Parameters: page, perPage, sort (default createdAt), order (default desc).
| Filter | Values (several separated by ,) |
|---|---|
status | waiting, pending, received, success, failed, canceled, merged |
type | custom_commands, power_off, load_config, send_config, time_sync |
200 response — a paginated object of TrackerAction.
Commands
Send a command to a tracker
POST /api/v1/trackers/:serial/actions
| Field | Type | Required | Default |
|---|---|---|---|
type | "custom_commands" | "power_off" | yes | — |
commands | string[] | if type = custom_commands | — |
priority | number (1 to 100) | no | 50 |
{
"type": "custom_commands",
"commands": ["gnss set --rate=5000", "modem set --network_1=0"],
"priority": 50
}
{
"type": "power_off"
}
201 response — the command has been created and queued:
{
"status": "created",
"data": { }
}
200 response — the tracker is already in the requested state, no command was created:
{
"status": "already_up_to_date",
"message": "Tracker is already up to date"
}
Errors: 404 (unknown tracker), 422 (validation).
tracker_action.updated webhook event, or by polling GET /api/v1/actions/:id.Send commands to several trackers
POST /api/v1/actions/_bulk
{
"trackerActions": [
{ "serial": "AB123", "type": "power_off" },
{ "serial": "AB124", "type": "custom_commands", "commands": ["gnss set --rate=10000"] },
{ "serial": "AB125", "type": "custom_commands", "commands": ["modem set --apn=mybiz"], "priority": 20 }
]
}
Each entry is processed independently: a single failure (tracker outside your organization, invalid command…) does not make the others fail.
201 response
{
"total": 3,
"created": 2,
"failed": 1,
"results": [
{ "index": 0, "status": "created", "id": "tac_xxx", "data": { } },
{ "index": 1, "status": "created", "id": "tac_yyy", "data": { } }
],
"errors": [
{ "index": 2, "status": "error", "message": "Tracker not found", "input": { } }
]
}
The index field matches the position of the entry in the array you sent, so each result can be mapped back to its original request.
Track a command
GET /api/v1/actions/:id
200 response — a TrackerAction with its attempts relation: each send attempt contains sentAt, receivedAt and response.
Cancel a command
PUT /api/v1/actions/:id/cancel
A command can only be canceled as long as it has not been transmitted to the tracker, that is, while its status is waiting. A command already sent cannot be recalled.
200 response
{ "status": "canceled" }
Webhooks
Webhooks push your organization's events to the URL of your choice with POST. Every enabled webhook attached to your account receives each event.
For the server-side setup, the detailed payloads and complete examples, see the Webhook page.
List your webhooks
GET /api/v1/webhooks
Parameters: page, perPage, sort (default createdAt), order (default desc), search (filters on url).
Retrieve a webhook
GET /api/v1/webhooks/:id
Create a webhook
POST /api/v1/webhooks
| Field | Type | Required | Default |
|---|---|---|---|
url | string (valid URL) | yes | — |
description | string | null | no | null |
enabled | boolean | no | true |
{
"url": "https://app.my-domain.com/integrations/neyos",
"description": "Production webhook",
"enabled": true
}
201 response — the created webhook, along with its signingSecret:
{
"id": "end_xxx",
"url": "https://app.my-domain.com/integrations/neyos",
"description": "Production webhook",
"enabled": true,
"signingSecret": "whsec_2aB3cD...",
"createdAt": 1737031800000,
"updatedAt": 1737031800000
}
signingSecret is returned only once, on creation. It appears in no other response. Store it in your secret manager straight away: if it is lost, the only option is to delete the webhook and create a new one.Update a webhook
PUT /api/v1/webhooks/:id
Same fields as on creation, all optional: only the fields you provide are modified. 200 response — the updated webhook.
Delete a webhook
DELETE /api/v1/webhooks/:id
204 response — no content.
Event types
type | Emitted when… | data contains |
|---|---|---|
tracker.created | A tracker is attached to your organization | Tracker |
tracker.updated | A tracker's alias or notes are modified | Tracker |
tracker.deleted | A tracker is deleted | Tracker |
tracker_action.created | A command is created (via the API or the platform) | TrackerAction |
tracker_action.updated | A command changes state (sent, acknowledged, success, failure…) | TrackerAction |
tracker_action.deleted | A command is deleted | TrackerAction |
message.created | A new frame is received from a tracker | Message |
tracker.updated is not emitted on every position or battery change: it is deliberately limited to alias and notes modifications. To follow a tracker's field activity, listen to message.created.Every notification uses the same envelope:
{
"type": "tracker.updated",
"data": { }
}
Date formats
Date formats depend on the event — this is the main integration pitfall:
| Context | Format |
|---|---|
| REST API responses | Unix milliseconds |
tracker.* and message.created events | ISO 8601 (2026-09-01T10:28:03.000+00:00) |
tracker_action.* events | Unix milliseconds |
timestamp field of an element (point, event) | Unix seconds |
Delivery guarantees
- Disabled webhooks (
enabled: false) receive no calls. - Expected response: a
200status code. Any other response, as well as a timeout or an unreachable host, is logged as a failure. - Calls are asynchronous, through a job queue: two notifications close in time may arrive out of order. Rely on the timestamps in the payload, not on the order of reception.
- The same event may be received several times: use the
X-Neyos-Event-Idheader as an idempotency key. - Every notification is logged on the Neyos side (URL, body sent, HTTP status, response time, outcome), and the webhook's
sentAtfield is updated on each send.
Notification signature
Every outgoing webhook call is signed with HMAC-SHA256 using the signingSecret returned when the webhook was created, and carries the X-Neyos-Timestamp, X-Neyos-Signature and X-Neyos-Event-Id headers.
Error codes
| Code | Typical cases |
|---|---|
200 | Success |
201 | Resource created |
204 | Success with no content (deletion) |
401 | Missing, invalid, expired or revoked token |
403 | Forbidden action |
404 | Resource not found or belonging to another organization |
422 | Validation error (invalid type, malformed url, empty commands…) |
500 | Internal error |
422 validation errors detail the offending fields:
{
"errors": [
{ "message": "The url field must be a valid URL", "rule": "url", "field": "url" }
]
}