Skip to main content

API Logs

Every request made with your organization's API keys is recorded — successful or not — as one row. Use it to debug an integration, confirm a call actually reached us, see which key produced which traffic, and understand a 4xx before contacting Fleeta support.

curl "https://openapi.fleeta.io/v1/api-logs?status=5xx" \
-H "Authorization: Bearer flt_live_..."

The endpoint is GET /v1/api-logs and requires the usage:read scope — the same scope as GET /v1/usage, available on every paid tier.

What is recorded

One row per request, holding only request metadata:

FieldDescription
idRequest identifier. Same value as the X-Request-Id response header and the requestId in an error body — quote it when you contact Fleeta support
atWhen the request was handled (RFC 3339, UTC)
methodHTTP method
routeThe route template (/v1/devices/{psn}), not the resolved path
statusHTTP status code
statusClass2xx / 4xx / 5xx … derived from status
errorCodeThe code from the problem+json body on failures; null on success
latencyMsServer-side handler time in milliseconds (excludes network time)
keyIdThe API key that made the call
ipClient IP address
userAgentClient user agent (truncated to 256 characters)

What is not recorded

Request and response bodies are never stored. Fleet data flowing through this API — GPS traces, event video metadata, driver-identifiable records — is personal data, and keeping copies of it in a log is a liability rather than a feature. So a log row tells you that a call happened and how it ended, never what it carried. errorCode is the one field that carries the failure reason, which is why it exists.

Query strings and path parameters are not stored either — route is the template, so a PSN or a job ID never lands in the log.

Requests that never reach the log

Some rejections are stopped before the API runs, so they cannot be attributed to an organization and are not listed:

  • 401 unauthorized — a missing, malformed, or revoked key is rejected at the authentication layer. There is no organization to file the row under.
  • 429 rate_limited — requests-per-second throttling is applied at the edge, before your request is dispatched.
  • 429 quota_exceeded — the edge usage-plan safety net, likewise applied before dispatch (see Rate Limits).
  • 404 route_not_found — a path or method that does not exist is answered at the edge (as problem+json with a requestId) and never routes to the API, so a typo in the URL leaves no trace here.

A 429 monthly_limit_exceeded is recorded, because the monthly call counter is enforced inside the API. If you are debugging silence rather than errors, keep this asymmetry in mind: an empty log can mean "the key was rejected", not only "nothing was sent".

Retention

DataRetained
Individual request rows (GET /v1/api-logs)30 days
Daily rollups (GET /v1/usage)13 months

Rows expire automatically once they are 30 days old, so the queried window may not exceed 30 days. Daily totals live for 13 months so you can compare against the same month last year and settle billing questions after the raw rows are gone.

Querying

ParameterDescription
from / toTime range (ISO 8601). Defaults to the last 24 hours; the window may not exceed 30 days
statusAn exact status code (200) or a class (2xx, 4xx, 5xx)
methodGET, POST, PUT, PATCH, DELETE
routeRoute template prefix/v1/devices matches /v1/devices, /v1/devices/{psn}, /v1/devices/{psn}/gps
keyIdRestrict to a single API key
limitPage size (default 50, max 200)
afterCursor from the previous response

Rows come back newest first, with cursor pagination — the log is a time series that keeps growing, so page numbers would silently duplicate or skip rows.

# All 5xx responses from one key over the last 7 days
curl -G "https://openapi.fleeta.io/v1/api-logs" \
-H "Authorization: Bearer flt_live_..." \
--data-urlencode "from=2026-07-30T00:00:00Z" \
--data-urlencode "status=5xx" \
--data-urlencode "keyId=key_9f2c"
{
"data": [
{
"id": "8916e1c1-2f4a-4d1e-9a7b-0b2c3d4e5f60",
"at": "2026-08-06T04:12:07Z",
"method": "GET",
"route": "/v1/devices/{psn}",
"status": 200,
"statusClass": "2xx",
"errorCode": null,
"latencyMs": 74,
"keyId": "key_9f2c",
"ip": "203.0.113.24",
"userAgent": "fleeta-openapi-node/1.2.0"
},
{
"id": "3f0b7a52-91cd-4a10-8e77-1d5b2c9f4a08",
"at": "2026-08-06T04:09:55Z",
"method": "GET",
"route": "/v1/events/{eventId}/video",
"status": 403,
"statusClass": "4xx",
"errorCode": "quota_exceeded",
"latencyMs": 41,
"keyId": "key_9f2c",
"ip": "203.0.113.24",
"userAgent": "fleeta-openapi-node/1.2.0"
}
],
"pagination": {
"nextCursor": "eyJkIjoiMjAyNi0wOC0wNiIsInNrIjoiMTc4NTk5ODg5ODAxMiJ9",
"hasMore": true
}
}

Walking the full range is the usual cursor loop:

async function* apiLogs(params) {
let after;
do {
const qs = new URLSearchParams({ ...params, limit: '200', ...(after ? { after } : {}) });
const res = await fetch(`https://openapi.fleeta.io/v1/api-logs?${qs}`, {
headers: { Authorization: `Bearer ${process.env.FLEETA_API_KEY}` },
});
const { data, pagination } = await res.json();
yield* data;
after = pagination.hasMore ? pagination.nextCursor : undefined;
} while (after);
}

// Which endpoints failed in the last 24 hours?
const failures = {};
for await (const row of apiLogs({ status: '5xx' })) {
failures[row.route] = (failures[row.route] || 0) + 1;
}

GET /v1/usage carries the rolled-up view of the same data alongside your quota status — the last 30 UTC calendar days of call volume and the split by status class.

{
"data": {
"period": "2026-08",
"tier": "standard",
"apiCalls": { "used": 27400, "limit": 70500 },
"rate": { "limitRps": 3, "burst": 6 },
"volumeQuotas": [{ "bucket": "transfer_bytes", "limit": 60129542144, "used": 5550677, "period": "2026-08", "unit": "bytes" }],
"callHistory": {
"windowDays": 30,
"from": "2026-07-08",
"to": "2026-08-06",
"aggregatedDays": 2,
"avgLatencyMs": 128,
"days": [
{
"date": "2026-08-05",
"calls": 412,
"avgLatencyMs": 131,
"statusClasses": [
{ "statusClass": "2xx", "count": 402 },
{ "statusClass": "4xx", "count": 9 },
{ "statusClass": "5xx", "count": 1 }
]
},
{
"date": "2026-08-06",
"calls": 418,
"avgLatencyMs": 125,
"statusClasses": [
{ "statusClass": "2xx", "count": 410 },
{ "statusClass": "4xx", "count": 8 }
]
}
]
},
"statusBreakdown": {
"windowDays": 30,
"aggregatedDays": 2,
"total": 830,
"classes": [
{ "statusClass": "2xx", "count": 812, "ratio": 0.9783 },
{ "statusClass": "4xx", "count": 17, "ratio": 0.0205 },
{ "statusClass": "5xx", "count": 1, "ratio": 0.0012 }
]
}
}
}
A missing day is not a zero day

callHistory.days contains only days that have a rollup. A day that is absent means no rollup exists for it — which includes today until its rollup is produced — and that is not the same statement as "you made zero calls". Days are never padded with zeroes, so do not read absence as traffic data. aggregatedDays tells you how many days the figures actually cover, and statusBreakdown.ratio is computed over exactly that set. Both callHistory and statusBreakdown are null when the rollup is unavailable.

ratio is null rather than 0 when the total is zero — a share of nothing is undefined, not zero. avgLatencyMs is null when latency was not rolled up for every day in the window, rather than an average of the days that happened to have it.

API logs vs. audit logs

They are stored separately and answer different questions. Reach for the one that matches your question:

API logs (/v1/api-logs)Audit logs (/v1/audit-logs)
QuestionWhat calls were made?What changed in the account?
SubjectRequests to this APIAccount activity — by people and by API keys
ReaderDevelopers integrating the APIAdministrators, compliance
Typical useDebugging a 403, tracing a request ID, capacity planningWho deleted that geofence, who changed device settings
Scopeusage:read (paid tiers)audit:read (enterprise)
Retention30 days (13 months rolled up)Per the platform audit policy

An API log row says "DELETE /v1/geofences/{geofenceId} returned 204 in 96 ms". The corresponding audit log entry says which geofence was removed and by whom. Neither replaces the other.

API-key actions in the audit log

Changes made through this API are recorded in the same audit trail as the actions people take in the Fleeta webviewer and apps — administrators see both on one timeline in the webviewer's Audit Log screen. In this endpoint's responses you can tell the two apart:

Person (webviewer / app)API key (this API)
actorTypenull — that pipeline records no actor kind, and we do not guess oneapi_key (or system for an action the platform took on its own, such as suspending a webhook after repeated delivery failures)
actorIdThe account emailThe API key ID (key_…) — the same keyId you see in API logs
ip / userAgentThe person's browser or appThe client that called the API

Only actions that change or command something are recorded — creating, updating or deleting a geofence or webhook, renaming or pre-registering a device, rebooting, starting or cancelling a GPS export or a recording recall, deleting an SD-card recording, issuing an event video URL, and the SD-card listing and metadata commands that wake the dashcam. Plain reads (GET /v1/devices, GET /v1/events, …) are not audit events; they are in the API log.

Each entry's eventType follows the category.action form of the rest of the audit log — for example geofence.create, device.device_reboot, export.job_create, video.recall_create, video.url.issue, auth.webhook_create, auth.webhook_rotate_secret — and target carries the resource the action touched (psn, geofenceId, webhookId, jobId, eventId, filename). Webhook subscription changes are filed under the auth category (they belong to your API key), so category=auth lists them alongside sign-in and account-security events. Filter by key with actor=key_…, or search with q. Sandbox (flt_test_*) activity is not written to the audit trail.

# Everything one API key changed in the last 24 hours
curl -G "https://openapi.fleeta.io/v1/audit-logs" \
-H "Authorization: Bearer flt_live_..." \
--data-urlencode "actor=key_9f2c" \
--data-urlencode "from=2026-08-05T04:00:00Z"

The entry lands in the audit log within seconds of the response and is written on a best-effort basis: if the audit pipeline is unreachable the API call still succeeds, and the entry is logged on the server for later review rather than being retried.