Skip to main content

Authentication

Every authenticated request to the Orunbase API carries a single header:

Authorization: Bearer <token>

The transport is identical for every credential kind — what differs is how the token was minted and what it can do. This page covers the API mechanics; for obtaining credentials, see the identity guides linked below.

Credential kinds

CredentialToken shapeMinted byTypical use
Session tokensps_ses_<id>.<secret>Console login (/v1/auth/login/*, OAuth)Browsers, interactive tooling
API keyWorkspace-scoped secret (service principal)Console or API key endpointsServers, automation
CLI access JWTShort-lived JWT (~15 min)CLI device flow; refreshed with a rotating single-use refresh tokenorun-cloud CLI, local dev
Workflow tokenActor token bound to (workspace, project)POST /v1/auth/oidc/exchange from a GitHub Actions OIDC tokenCI/CD, keyless deploys
curl https://api.orunbase.com/v1/organizations/ws_a1b2c3d4/projects \
-H "Authorization: Bearer $ORUN_CLOUD_API_KEY"
import { OrunCloud } from "@saas/sdk";

const client = new OrunCloud({
baseUrl: "https://api.orunbase.com",
auth: { kind: "bearer", token: process.env.ORUN_CLOUD_API_KEY! },
});
warning

CLI refresh tokens are single-use: reusing one revokes the whole token family. This is deliberate — a replayed refresh token is treated as theft.

How the edge resolves the actor

The API edge does not interpret the token itself. It forwards the bearer to the identity service, which classifies the credential and returns an actor — the subject id and type (user, service principal, CLI session, or workflow actor) plus any bindings the token carries (workspace for API keys, workspace + project for workflow tokens, workspace list for CLI JWTs). The edge then injects that actor context into the downstream service; authorization decisions (RBAC) happen against the resolved actor, never the raw token.

Successful resolutions are cached briefly at the edge (keyed by a hash of the token) to keep the hot path fast. Failed resolutions are never cached.

Unauthenticated routes

A small set of routes deliberately accepts no bearer, because the request body or flow itself carries the proof:

RouteWhy it is public
POST /v1/auth/login/start, POST /v1/auth/login/completeThe login flow is how you get a token
GET /v1/auth/oauth/providers, GET /v1/auth/oauth/{provider}/start, …/callbackBrowser-redirect sign-in; CSRF-protected by a state cookie
POST /v1/auth/cli/start, /v1/auth/cli/device/start, /v1/auth/cli/device/poll, /v1/auth/cli/token, /v1/auth/cli/revokeCLI device flow — the device code / refresh token is the credential
POST /v1/auth/oidc/exchangeThe GitHub Actions OIDC token in the body is the credential
GET /healthService health probe
Provider webhook ingress (billing provider webhooks, integration install callbacks)Authenticated by provider signatures / signed single-use state, not a bearer

Everything else returns 401 unauthenticated without a valid bearer. Public auth routes sit in the tightest rate-limit family (10 requests/min per identity) to blunt brute force.

401 vs 403

The two failure modes are distinct and consistent:

  • 401 unauthenticated — the request carried no Authorization header, or the token is missing, malformed, expired, or revoked. Fix the credential and retry.
  • 403 forbidden — the credential is valid, but the resolved actor lacks the required permission on the target resource (RBAC is deny-by-default). Retrying will not help; the actor needs a role grant.
{
"error": {
"code": "unauthenticated",
"message": "Missing or invalid Authorization header",
"details": {},
"requestId": "req_9c8b7a6f5e4d3c2b1a09f8e7"
}
}