Developers

Every vehicle, position, and alert you can see in Dragon Fleet, you can also read programmatically — on every plan, with no enterprise tier and no extra approval step. Mint a scoped token in Settings → API tokens, pick exactly the scopes and vehicles it needs, and call the same REST API the web app itself uses.

Programmatic access is not gated by subscription tier — it is part of the base product, the same way your data always being exportable is.

Base URL & auth

Every endpoint below is served from this origin under /api/v1 — for example, the hosted deployment serves it at https://api.apps.drwifi.nz/api/v1. A self-hosted instance serves the same paths from its own origin.

Authenticate every request with a scoped API token as a bearer credential:

Authorization: Bearer dragonfleet_sk_...

Tokens are shown in full exactly once at creation time and stored hashed thereafter — there is no way to retrieve a lost token; revoke it and issue a new one.

Scopes

A token can only call an endpoint if it was issued with that endpoint's required scope. Vehicle-scoped endpoints (everything under /vehicles/{id}) are further limited to the specific vehicles the token was issued for — re-checked against the issuing user's current access on every request, not just at issuance. Organization-level reads (/geofences, /drivers) return every record in the token's organization regardless of its vehicle scope.

vehicles:read

Vehicle metadata and device listings.

positions:read:live

The single most recent position fix — the most sensitive read in the product.

positions:read:history

Bucketed position history, speed alerts, and mileage reports.

alerts:read

The generic alert-event feed (currently: speed alerts; more alert types land here over time).

geofences:read

The organization's geofence definitions.

geofences:write

Reserved vocabulary only — no endpoint currently accepts it. Every geofence mutation route rejects scoped tokens outright (403); this scope exists so the read/write pair is symmetric for a future task that revisits token-scoped writes.

drivers:read

The organization's driver roster and per-vehicle driver assignments.

No write scope grants a mutation today — issuing, listing, and revoking tokens, and every create/update/delete endpoint in the API, are session-only in v1. A scoped token that calls a mutation route always gets 403.

Endpoints

Every endpoint below also works with a signed-in user's session — this list covers the subset a scoped API token can reach.

GET/api/v1/vehiclesrequiresvehicles:read

List vehicles visible to the caller.

Response: Vehicle[]

GET/api/v1/vehicles/{id}requiresvehicles:read

A single vehicle by id. 404 if it doesn't exist or isn't visible to this token.

Response: Vehicle

GET/api/v1/vehicles/{id}/devicesrequiresvehicles:read

Devices attached to a vehicle.

Response: Device[]

GET/api/v1/vehicles/{id}/positionrequirespositions:read:live

The vehicle's single most recent position fix — "where is this vehicle right now".

Response: { position: Position | null }

GET/api/v1/vehicles/{id}/historyrequirespositions:read:history

Hourly-bucketed position trend data (up to ~1h stale — never the live-tracking path).

Query: from=<ISO 8601>&to=<ISO 8601> (both required, from < to)

Response: { history: HistoryBucket[] }

GET/api/v1/vehicles/{id}/trackrequirespositions:read:history

The full ordered (ts asc), server-decoded position track for a bounded window — the trip-replay path. Server-side even-sampling capped at 3000 points. Requires the advanced_reports plan (a 402 upgrade response otherwise — the one token endpoint that can 402).

Query: from=<ISO 8601>&to=<ISO 8601> (both required, from < to)

Response: { track: TrackPoint[] }

GET/api/v1/vehicles/{id}/alertsrequirespositions:read:history

Speed-alert events for a vehicle, newest first, location decoded to lon/lat.

Query: from=<ISO 8601>&to=<ISO 8601> (both optional, both-or-neither)

Response: { alerts: VehicleAlert[] }

GET/api/v1/vehicles/{id}/mileagerequirespositions:read:history

Total distance travelled over a window — a billing-ready mileage report.

Query: driver_assignment_id=<uuid> OR (from=<ISO 8601>&to=<ISO 8601>)

Response: { distance_km, positions_count, from, to, driver? }

GET/api/v1/vehicles/{id}/alert-eventsrequiresalerts:read

The generic, decoded alert-event feed across every alert type the platform emits.

Query: from=&to=&type=<alert_type>&limit=<1-500, default 200> (all optional)

Response: { alert_events: AlertEvent[] }

GET/api/v1/geofencesrequiresgeofences:read

The caller's organization's geofences, geometry decoded to GeoJSON-shaped numbers.

Response: Geofence[]

GET/api/v1/driversrequiresdrivers:read

The caller's organization's driver roster.

Response: Driver[]

GET/api/v1/vehicles/{id}/driversrequiresdrivers:read

A vehicle's driver assignments, current and past, newest first, driver joined in.

Response: DriverAssignmentWithDriver[]

Errors

Errors are always a JSON object with an error string, never a raw upstream message:

{ "error": "Token is missing required scope: positions:read:live" }
400

Malformed request — a bad query param, invalid body, or a bad vehicle/token id.

401

Missing, malformed, unknown, revoked, or expired token.

402

The organization's subscription entitlement blocks the action.

403

Authenticated, but this token's scopes don't cover this endpoint (or it's a mutation route — no write scopes exist for tokens in v1).

404

The resource doesn't exist, or isn't visible to this token — deliberately conflated so a token can never learn that a vehicle it can't see exists.

409

Valid and authorized, but conflicts with existing state.

500

Something failed on our end. Never contains upstream error detail.

Example: curl

curl https://api.apps.drwifi.nz/api/v1/vehicles/<vehicle-id>/position \
  -H "Authorization: Bearer dragonfleet_sk_..."

Example: JavaScript

Plain fetch:

const res = await fetch(
  "https://api.apps.drwifi.nz/api/v1/vehicles/<vehicle-id>/history?from=2026-07-01T00:00:00Z&to=2026-07-08T00:00:00Z",
  { headers: { Authorization: "Bearer dragonfleet_sk_..." } },
);
const { history } = await res.json();

Or the typed client this app itself uses (@dragonfleet/api-client on npm within this monorepo — createApiClient accepts a static scoped token exactly the same way it accepts a session token):

import { createApiClient } from "@dragonfleet/api-client";

const client = createApiClient({
  baseUrl: "https://api.apps.drwifi.nz",
  getAuthToken: () => "dragonfleet_sk_...",
});

const vehicles = await client.vehicles.list();

What's not here yet

There is no machine-readable OpenAPI/Swagger document yet — this hand-written page is the honest v1. The endpoint list above is kept accurate by cross-checking it against the actual route handlers, not generated from them; if that drifts, treat this page as the bug and the route handlers as the source of truth. A generated spec is a reasonable follow-up if real third-party integration demand shows up, not before.