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"
}
}