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

text
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
curl -sS "https://api.owlmar.com/v1/vessels" \
-H "Authorization: Bearer owlmar_live_JS9k2MxT7pQwR4nZ..."

Creating a key

From Settings → API Keys in the app:

  1. 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.
  2. Optionally set an expiration date. Keys without one are valid until revoked.
  3. Optionally narrow orgScope (which vessels) and moduleScopes (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.
  4. 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"]. level is read or full; full implies read. There's no wildcard module grant — list each module explicitly.
json
{
"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.

Note

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:

json
{ "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:

json
{ "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:

json
{ "error": "Invalid or revoked API key", "code": "AUTH_REQUIRED" }
Warning

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.

Danger

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.