> ## Documentation Index
> Fetch the complete documentation index at: https://docs.wede.pt/llms.txt
> Use this file to discover all available pages before exploring further.

# Devices and offline dispatch

> Register field devices and sync the dispatches they queue while offline.

Devices belong to the optional teams, dispatch and missions module. A field device (a dispatcher tablet, a vehicle terminal) can keep creating dispatch requests with no connection. It numbers each request with its own sequence number, starting at 1, and sends the queue when it reconnects. wede stores each request once, checks that no sequence number is missing, and processes the requests with the same mechanism as an online [Action dispatch](/api-reference/teams/dispatch-action.md).

## Authentication

Session token (`Authorization: Bearer <token>`).

| Operation | Permission | Tenant roles with it |
| - | - | - |
| Register, sync | `sdk:access` | `company_admin`, `company_tech`, `field_operator` |
| Read a device queue | `dispatch:view` | `company_admin`, `operational_supervisor`, `corporate_client` |

> [!WARNING]
>
> The integration API key (`x-wede-api-key`) has neither permission. Calls with an API key return `403 forbidden`.

***

## Register a device

```http theme={null}
POST /v1/devices/register
```

Registers the device for your tenant. Safe to call on every app start: calling it again updates `app_version` and the device's last seen time.

**device\_id** `string` *required*

Your stable identifier for the device, 1 to 200 characters.

**platform** `string` *required*

`ios`, `android`, `web` or `other`.

**app\_version** `string`

Up to 50 characters.

```bash theme={null}
curl -X POST https://api.wede.pt/v1/devices/register \
  -H "Authorization: Bearer YOUR_SESSION_TOKEN" \
  -H "Content-Type: application/json" \
  -d '{ "device_id": "tablet-lisboa-07", "platform": "android", "app_version": "3.2.0" }'
```

```json Response 201 theme={null}
{ "device_id": "tablet-lisboa-07", "registered": true }
```

| Status | `error` | When |
| - | - | - |
| `400` | `validation_error` | The body does not match the schema. |

***

## Sync a device

```http theme={null}
POST /v1/devices/sync
```

**device\_id** `string` *required*

A device registered for your tenant.

**last\_received\_seq** `integer` *required*

0 or more. The last sequence number the device has confirmed on its side. wede stores the highest value it receives.

**dispatches** `object[]` *required*

Up to 500 queued dispatch requests. Send `[]` to process what is already pending.

**dispatches\[].sequence\_number** `integer` *required*

1 or more, one per request, with no gaps.

**dispatches\[].created\_offline\_at** `string` *required*

When the request was created on the device, ISO 8601 in UTC, for example `2026-06-09T18:30:00Z`.

**dispatches\[].action\_id** `string`

UUID of the catalog action. Needed for processing.

**dispatches\[].event\_lat** `number`

Incident latitude. Needed for processing.

**dispatches\[].event\_lng** `number`

Incident longitude. Needed for processing.

**dispatches\[].vertical** `string`

Vertical code.

**dispatches\[].priority** `string`

Priority of the incident.

**dispatches\[].payload** `object`

Any data you want to keep with the request.

```bash theme={null}
curl -X POST https://api.wede.pt/v1/devices/sync \
  -H "Authorization: Bearer YOUR_SESSION_TOKEN" \
  -H "Content-Type: application/json" \
  -d '{
    "device_id": "tablet-lisboa-07",
    "last_received_seq": 0,
    "dispatches": [
      {
        "sequence_number": 1,
        "action_id": "1c9d7f3e-2b4a-4c8e-9f10-3a5b7c9d1e2f",
        "event_lat": 38.7169,
        "event_lng": -9.1395,
        "priority": "critical",
        "created_offline_at": "2026-06-09T18:30:00Z"
      }
    ]
  }'
```

```json Response 200 theme={null}
{
  "accepted": [1],
  "duplicates": [],
  "failed": [],
  "server_seq": 1,
  "device_last_received_seq": 0,
  "synced_at": "2026-06-09T18:45:00.000Z",
  "integrity_gap_detected": false,
  "integrity_incident_id": null
}
```

**accepted** `integer[]`

Sequence numbers stored in this call.

**duplicates** `integer[]`

Sequence numbers wede already had for this device. They are not stored again, so retrying a sync is safe.

**failed** `integer[]`

Sequence numbers that could not be stored.

**server\_seq** `integer`

Highest sequence number wede has received from this device.

**device\_last\_received\_seq** `integer`

Highest `last_received_seq` the device has reported.

**synced\_at** `string`

ISO 8601 timestamp of this sync.

**integrity\_gap\_detected** `boolean`

`true` when a sequence number is missing for this device.

**integrity\_incident\_id** `string | null`

The security incident wede recorded for the gap, or `null`.

wede sends the `device.synced` webhook with `{ device_id, accepted, duplicates }`, where `accepted` and `duplicates` are counts.

### Sequence integrity

Sequence numbers must start at 1 and have no gaps for each device. When one is missing, wede:

1. Blocks every pending request that comes after the first missing number (status `blocked_integrity`). Requests before the gap are still processed.
2. Records a security incident and alerts your tenant administrators by email.
3. Returns `integrity_gap_detected: true` with the incident id.

### Processing

In the same call, wede processes every `pending` request of the device in sequence order, including requests stored in earlier syncs:

| Request | Result status |
| - | - |
| No `action_id`, `event_lat` or `event_lng` | `failed`, `error_message` `missing_action_id_or_coordinates` |
| `action_id` not in your catalog | `failed`, `error_message` `action_not_found` |
| Complete | `processed`: wede ranks the teams as in [Action dispatch](/api-reference/teams/dispatch-action.md), with event id `<device_id>:<sequence_number>`, and dispatches automatically when automatic dispatch is on and a tier 1 team reaches your minimum score. `mission_id` holds the mission when one was created. |

Because the event id is fixed per request, processing the same request again never creates a second mission for the same team.

| Status | `error` | When |
| - | - | - |
| `400` | `validation_error` | The body does not match the schema. |
| `404` | `device_not_found` | The device is not registered for your tenant. |

***

## Read a device queue

```http theme={null}
GET /v1/devices/{device_id}/queue
```

Returns up to 100 queued requests of the device with one status, in sequence order.

**device\_id** `string` *required*

Device identifier.

**status** `string` (default: pending)

`pending`, `processing`, `processed`, `failed` or `blocked_integrity`. Any other value reads `pending`.

```bash theme={null}
curl "https://api.wede.pt/v1/devices/tablet-lisboa-07/queue?status=failed" \
  -H "Authorization: Bearer YOUR_SESSION_TOKEN"
```

```json Response 200 theme={null}
{
  "data": [
    {
      "id": "c1d2e3f4-a5b6-4c7d-8e9f-0a1b2c3d4e5f",
      "device_id": "tablet-lisboa-07",
      "tenant_id": "9c145f7e-83be-4039-8e9c-650796acd3ee",
      "sequence_number": "2",
      "action_id": null,
      "event_lat": null,
      "event_lng": null,
      "vertical": null,
      "priority": null,
      "payload": null,
      "status": "failed",
      "created_offline_at": "2026-06-09T18:31:00.000Z",
      "received_at": "2026-06-09T18:45:00.000Z",
      "processed_at": null,
      "mission_id": null,
      "error_message": "missing_action_id_or_coordinates"
    }
  ],
  "count": 1
}
```

`sequence_number`, `event_lat` and `event_lng` are returned as strings.

***

## Errors on every device endpoint

| Status | `error` | When |
| - | - | - |
| `401` | `unauthorized` | Missing, invalid or expired credentials. |
| `403` | `forbidden` | Your role lacks the permission, or you used an API key. |
| `500` | `internal_error` | Unexpected server error. |

## SDKs

`registerDevice` and `syncDeviceQueue` in JavaScript, `register_device` and `sync_device_queue` in Python.


This documentation is built and hosted on [Mintlify](https://mintlify.com), a developer documentation platform.