Authentication
Every /v1/* request authenticates with a long-lived bearer key, not a session cookie or an
OAuth flow — there's no "login" concept for a third-party integration.
Key format
owlmar_live_JS9k2MxT7pQwR4nZ... (≈50 characters total)
Keys are generated server-side from 256 bits of real entropy — not a password, not something a human chose. Only the SHA-256 hash is stored; OwlMar cannot show you your key again after creation, so store it somewhere durable (a secrets manager or environment variable) the moment you create it.
curl -sS "https://api.owlmar.com/v1/vessels" \-H "Authorization: Bearer owlmar_live_JS9k2MxT7pQwR4nZ..."
Creating a key
From Settings → API Keys in the app:
- Click Create API Key and give it a label that identifies what it's for (e.g. "Zapier integration", "Rishat ERP sync") — you'll thank yourself later when you have five keys and need to know which one to revoke.
- Optionally set an expiration date. Keys without one are valid until revoked.
- Optionally narrow
orgScope(which vessels) andmoduleScopes(which modules, and at what level) — see below. Leaving both at their defaults grants access to everything the creating user can already see in the app. - Copy the raw secret. This is the only time it's shown.
orgScope and moduleScopes
A key's access is the intersection of two independent scopes, both narrower than or equal to what the creating user could already do in the app:
orgScope— which vessels the key may act on. A list of vessel IDs, or the wildcard["*"](equivalently, an empty array[]) meaning "all of the owner's vessels."moduleScopes— which modules, and at what level. A flat list of"<featureKey>:<level>"strings, e.g.["equipment_tracking:read", "maintenance_logs:full"].levelisreadorfull;fullimpliesread. There's no wildcard module grant — list each module explicitly.
{"orgScope": ["3f1c9e2a-...", "8b4d0f11-..."],"moduleScopes": ["maintenance_logs:full", "expense_tracking:read"]}
The key above can read and write maintenance events, and read (not write) expenses, on exactly those two vessels — nothing else, even if the creating user owns ten more vessels.
Scope narrowing is defense-in-depth, not the only boundary. Every request is also checked against the creating user's real, DB-backed role and ownership — a key can never exceed what its creator could already do in the app, even if a scope were misconfigured to be too broad.
Requesting a route your key isn't scoped for returns a 403:
{ "error": "This API key is not scoped for read access to equipment_tracking", "code": "INSUFFICIENT_SCOPE" }
Requesting a vessel outside orgScope returns a different 403:
{ "error": "This API key is not scoped to this vessel", "code": "VESSEL_OUT_OF_SCOPE" }
Revocation
Revoking a key from Settings → API Keys takes effect on that key's very next request —
there's no caching layer to wait out. A revoked (or expired, or simply wrong) key returns the
same generic 401:
{ "error": "Invalid or revoked API key", "code": "AUTH_REQUIRED" }
Auth failures never distinguish "missing header" from "malformed token" from "revoked key" — this is deliberate (the same non-enumeration principle used elsewhere in the app), so don't build integration logic that depends on telling these apart from the response alone.
Rotation practice
Because a key is shown exactly once, the safe rotation pattern is: create a new key, update your integration's configuration to use it, confirm it's working, then revoke the old one — never revoke first. For integrations you don't control end-to-end (a SaaS tool that stores your key on your behalf), coordinate the swap with that tool's own credential-update flow before revoking.
Never expose your API key in client-side JavaScript, a mobile app bundle, a public repository, or a support ticket. If a key leaks, revoke it immediately and create a replacement — there is no way to "unexpose" a leaked secret.
