DocsGuide

Authentication

An API key, the access token it turns into, how long a token lives, and the pattern for an app that acts as a person.

Keys and tokens

An API key is a client id and a client secret. You do not send the key itself on a call; you exchange it for an access token and send the token. The exchange is the OAuth 2.0 client credentials grant at the identity provider:

curl -u "$KEY_ID:$KEY_SECRET" https://auth.inorbit.hr/oauth2/token \
  -d grant_type=client_credentials \
  -d audience=tbd-api \
  -d scope="identity:read account:read"
{ "access_token": "eyJ…", "token_type": "bearer", "expires_in": 899, "scope": "identity:read account:read" }

Then, on every call:

Authorization: Bearer eyJ…

The token is a signed JWT with audience tbd-api and the scopes it was given. The gateway verifies the signature and the audience on every request, admits the call only when the token holds the route's scope, and hands it on with the identity inside; the services behind it never see the secret and never verify a token themselves.

Where keys come from

Keys are made and revoked in the console at https://console.inorbit.hr/keys/, for your personal account or for a team you own or administer. The secret is shown once, with this exchange filled in.

Scopes

A key holds the scopes ticked when it was made, and a token holds the ones asked for in the exchange: scope is a space-separated list, any subset of the key's. Asking for a scope the key does not hold is refused with invalid_scope; a call to a route whose scope the token lacks is 403 forbidden. A key's scopes are fixed: to change them, make a new key and revoke the old one.

ScopeLets a token call
identity:readGET /v1/me: the caller as the gateway verified it
account:readGET /v1/accounts/me: the account, its plan, the key's name and last use
usage:readGET /v1/accounts/orgs/{org_id}/units and /usage, GET /v1/accounts/units/categories: the account's units and the price list
radar:readGET /v1/radar/digests, /v1/radar/digests/{id}, /v1/radar/items: the radar's published digests

Any token from a key may fetch GET /v1/openapi.json, the document for its plan. Every operation in the Reference names its scope. Each product opens to keys with scopes of its own as it is published.

Lifetime, rotation, revocation

  • A token lives fifteen minutes. Fetch a new one before it expires; a call with an expired token is 401 unauthenticated.
  • To rotate, make a second key, move your fleet to it, then revoke the first. An account holds up to ten keys at once.
  • Revoking a key refuses new tokens at once. A token already issued keeps working until it expires, which is at most fifteen minutes.
  • Keep the secret out of code and logs. The platform's own logs carry the key id, the route and the status of a call, never a body and never a secret.

The exchange in three languages

JavaScript
const basic = Buffer.from(`${process.env.KEY_ID}:${process.env.KEY_SECRET}`).toString("base64");
const res = await fetch("https://auth.inorbit.hr/oauth2/token", {
  method: "POST",
  headers: { authorization: `Basic ${basic}`, "content-type": "application/x-www-form-urlencoded" },
  body: new URLSearchParams({
    grant_type: "client_credentials",
    audience: "tbd-api",
    scope: "identity:read account:read",
  }),
});
const { access_token } = await res.json();
Python
import os, requests

r = requests.post(
    "https://auth.inorbit.hr/oauth2/token",
    auth=(os.environ["KEY_ID"], os.environ["KEY_SECRET"]),
    data={"grant_type": "client_credentials", "audience": "tbd-api", "scope": "identity:read account:read"},
    timeout=10,
)
token = r.json()["access_token"]
Go
form := url.Values{"grant_type": {"client_credentials"}, "audience": {"tbd-api"}, "scope": {"identity:read account:read"}}
req, _ := http.NewRequest("POST", "https://auth.inorbit.hr/oauth2/token", strings.NewReader(form.Encode()))
req.SetBasicAuth(os.Getenv("KEY_ID"), os.Getenv("KEY_SECRET"))
req.Header.Set("Content-Type", "application/x-www-form-urlencoded")

An app that acts as a person

A key represents an account. An app that acts for a signed-in person (a mobile or desktop client) uses the authorization code grant with PKCE against the same identity provider, in the system browser, with audience=tbd-api on the authorization request, and then sends the resulting bearer token exactly as above. Registering such a client is a request to the operator today.

What the token carries

GET /v1/me shows it: subject (the key id for a key, the person's id for a person), kind (client for a key, person for a person), scopes, and the account the call counts against under org: the account a key belongs to, or a person's own. GET /v1/accounts/me answers the account itself, with its plan and, for a key, the key's name and when it was last used.