Tenant Isolation
An API key is bound to the issuing company (masterEmail). One key = one company — there is no separate organization ID parameter; the key itself determines the data access boundary. The authentication layer (authorizer) resolves the key, attaches the company anchor to the request context, and every query is forced to that anchor's scope.
Company A's key structurally cannot read company B's devices, events, or GPS data. Isolation is not an optional filter applied by each API's application code — it is enforced uniformly in a shared layer, so it cannot be bypassed with query parameters or path manipulation.
Fail-closed principle
Isolation always fails closed.
- A request whose company anchor cannot be resolved is handled toward empty results, never toward broader data exposure.
- Fetching another company's resource by ID returns
404, not403— resources outside your scope do not even reveal their existence.
# Looking up a PSN not in my company — the same 404 regardless of whether another company owns it
curl "https://openapi.fleeta.io/v1/devices/OTHER_COMPANY_PSN" \
-H "Authorization: Bearer flt_live_..."
{
"type": "https://developers.fleeta.io/errors/device_not_found",
"title": "Not Found",
"status": 404,
"code": "device_not_found",
"detail": "Device with PSN 'OTHER_COMPANY_PSN' not found.",
"requestId": "a2f9c481-6d35-4b70-8e12-5c93a0d7f4b6"
}
Consequently, 404 does not distinguish "the resource does not exist" from "it is outside my company's scope." Clients should handle both cases the same way.
Account hierarchy is not exposed in the API
The webviewer's account hierarchy (master / submaster / group viewer) is deliberately not exposed in the API. An API key is a company-level principal, not a user account, and access control is expressed along only two axes.
-
Scopes — which domains can be read/written (
devices:readetc., see Authentication) -
Vehicle group restriction (optional, enterprise tier only) — an enterprise key can be pinned to one or more vehicle groups at issuance; only data for vehicles in those groups is returned. Unrestricted keys cover the whole company. Group identifiers use the public
grp_<id>format (e.g.grp_12) — list your organization's groups withGET /v1/groups. Requesting agroupIdoutside the key's pinned set returns403 group_not_allowed.A
groupIdyou pass has exactly three failure modes, checked in this order:Situation Response Not in grp_<id>form400 invalid_parameterWell-formed, but outside the key's pinned set 403 group_not_allowedWell-formed and allowed, but no such group in the organization 404 group_not_foundA group that does exist but has no vehicles assigned is not an error — it returns
200with an empty list. None of the three leaks anything about other organizations: a group belonging to someone else is indistinguishable from one that never existed.noteGroup-pinned keys are supported at the platform level but cannot yet be created from the console — self-service keys are currently issued with the full scope set of their tier (intended for your own backend integration). Group filtering per request via the
groupIdquery parameter is available to every key.
If you need per-user or per-role granularity, issue separate keys with the appropriate scope/group restrictions for each use case. Keeping the account hierarchy out of the API contract minimizes the public surface, and the API contract stays stable even if the internal permission model changes.
Security perspective (ISO/IEC 27001)
This isolation model is designed to satisfy the ISO/IEC 27001 access control
(A.9) requirements. The tenant boundary is enforced in the authentication
layer and defaults to fail-closed, so an implementation mistake in an
individual endpoint cannot lead to cross-organization exposure. Keys are
stored on the server only as sha256 hashes (plaintext is never stored), and
read/write activity through the API can be traced via the audit log
(GET /v1/audit-logs, scope audit:read).
Multi-tenant integration for partners/integrators
Partners and integrators managing multiple companies use one key per company. A single integration codebase can implement multi-tenant access by swapping in the appropriate company's key.
// Manage per-company keys as context — use the right company's key per request
const companies = {
acme: process.env.KEY_ACME, // company A's key → company A's data only
globex: process.env.KEY_GLOBEX, // company B's key → company B's data only
};
async function listDevices(company) {
const res = await fetch('https://openapi.fleeta.io/v1/devices', {
headers: { Authorization: `Bearer ${companies[company]}` },
});
return (await res.json()).data;
}
Because the key itself is the tenant boundary, an integration bug that passes the wrong company identifier and mixes data across tenants is structurally impossible.
Related links
- Authentication — key issuance, scopes, handling rules
- Errors — 404 handling and problem+json
- Getting Started — from key issuance to your first call