PlatformHTTP API

Authentication

Get a bearer token for HTTP calls, as a person at a terminal or as a workload such as a CI job.

Available

Every API call carries a short-lived access token, sent as Authorization: Bearer <token>. How you get one depends on who is calling. A person runs spiral login and approves the sign-in in a browser. A CI job or a service signs in as a workload, ideally with the identity token its platform already provides, such as GitHub Actions or Google Cloud, so there is no secret to store or rotate.

The authorization server at https://auth.spiraldb.com issues tokens, and its OAuth endpoints follow the OAuth 2.0 specifications. Clients can read its metadata from /.well-known/oauth-authorization-server (RFC 8414). The Authentication service describes identities and every endpoint.

Choose a method

CallerMethodToken endpoint
A person at a terminalDevice authorization grant (RFC 8628) through spiral loginauth.spiraldb.com
A workload with a registered key pairClient credentials with a JWT client assertion (RFC 7523 section 2.2)auth.spiraldb.com
A workload on GitHub Actions, Google Cloud, AWS, Modal, Kubernetes, or another OpenID Connect issuerJWT-bearer grant with the platform's identity token (RFC 7523 section 2.1)auth.spiraldb.com
A workload with a client secret (spw_...)Client credentials with a client secretapi.spiraldb.dev

Prefer a key pair or a platform identity for workloads. A client secret is a long-lived shared secret that anyone who copies it can use, and a workload that signs in with one receives twelve-hour storage tokens instead of fifteen-minute ones (see Security). Once the workload uses another method, revoke its secret with spiral workloads revoke-secret.

Sign in as a person

Sign in with the CLI. It uses the device flow: it prints a URL and a code, and waits until you approve the code in a browser:

spiral login --show-token

The last line of the output is the access token, so a script can keep just that line and use it for direct HTTP calls:

export SPIRAL_TOKEN="$(spiral login --show-token | tail -n 1)"
curl https://api.spiraldb.dev/v1/organizations/me \
  -H "Authorization: Bearer $SPIRAL_TOKEN"

The CLI caches the tokens and refreshes them when they expire. Use --org <ORG> to select an Organization. See spiral login.

Refresh tokens are bound to the client that signed in. Use the same client_id when refreshing or revoking one, and store each replacement token returned by the server. First-party refresh tokens also preserve the Organization selected at sign-in. To switch Organizations, include organization_id in the refresh request; the replacement token preserves the new Organization on later refreshes. Older first-party refresh tokens without an Organization are no longer accepted; run spiral login again if refreshing returns invalid_grant. Disabling a client prevents new authorizations and token grants.

Sign in as a workload with a key pair

Register the public key of the workload with spiral workloads keys. Then sign a short-lived JWT client assertion with the private key, and present it at the token endpoint:

  • iss and sub: the workload's client ID.
  • aud: https://auth.spiraldb.com/oauth/token.
  • jti: a unique value. exp: a short expiry, such as 60 seconds.
  • Header kid: the key ID that you registered.
curl -X POST https://auth.spiraldb.com/oauth/token \
  -d grant_type=client_credentials \
  -d client_id="$SPIRAL_CLIENT_ID" \
  -d client_assertion_type=urn:ietf:params:oauth:client-assertion-type:jwt-bearer \
  -d client_assertion="$CLIENT_ASSERTION"

The authorization server accepts only signed assertions from workloads. Client secrets go to the API host instead, as the next sections show.

Sign in with a platform identity

Add a trust policy to the workload with spiral workloads trust. The policy names the platform identity that can sign in as the workload. Then request an identity token from the platform with the audience https://auth.spiraldb.com/oauth/token, and present it as the grant:

curl -X POST https://auth.spiraldb.com/oauth/token \
  -d grant_type=urn:ietf:params:oauth:grant-type:jwt-bearer \
  -d client_id="$SPIRAL_WORKLOAD_ID" \
  -d assertion="$PLATFORM_TOKEN"

The JWT-bearer grant takes no client credentials. The platform token authenticates the request.

Sign in as a workload with a client secret

Client secrets (spw_...) are the older way for a workload to sign in. The API host still accepts them at its own token endpoint, until they are removed:

curl -X POST https://api.spiraldb.dev/oauth/token \
  -d grant_type=client_credentials \
  -d client_id="$SPIRAL_CLIENT_ID" \
  -d client_secret="$SPIRAL_CLIENT_SECRET"

The response contains access_token and token_type (Bearer). Request a new token when the token expires.

Exchange a token

The authorization server implements token exchange (RFC 8693) to narrow a token. A token names every service it is for in its aud claim: your client's sign-in or grant decides which, and Spiral registers ScanDAL and the telemetry collector for its own clients, Applications and Workloads. Before sending a token to one of those services, a client exchanges it for one that names only that service, so the service never holds your token for Spiral's API.

Send the token as subject_token and name each service you want as a resource. No client credentials are needed, because the token is the credential. The new token keeps every claim of the original, except that:

  • it names only the requested resources, each of which the original names;
  • it carries the requested scope, if any, which the original's scope must cover;
  • it expires no later than the original.

A request for anything else fails with invalid_target or invalid_scope. You can narrow a narrowed token again. The SDKs narrow tokens for ScanDAL and telemetry themselves, so most clients never call the exchange directly.

No token is exchanged for storage: the Storage service obtains storage access as its own client, once your permission on the resource is checked.

Call from a web page

A web page can call Spiral's API with its user's token directly, without a backend of its own, when the client the token was issued to registered the page's origin. An Application registers origins with allowedCorsOrigins, and the user tokens issued to it carry them, so the API answers the browser without looking the Application up. The same token reaches the authorization server's token endpoint and, through a capability, ScanDAL. Spiral Studio works this way.

The browser's preflight request always succeeds, because it carries no token. A request with a valid token from an origin its client did not register is refused with 403 before any work, and a request without a valid token is refused with 401; the page can read both refusals. Responses never allow cookies, so a page reaches the API only with a token it holds, and the scopes the user granted still limit what that token can do.

Revoke a token

Revoke a token at POST https://auth.spiraldb.com/oauth/revoke (RFC 7009). Revoking a person's access token ends the sign-in session, so it can no longer be refreshed, but the access token already issued keeps working until it expires, at most an hour later. spiral logout revokes the session and removes the cached tokens of the CLI.

Errors

The OAuth endpoints of the authorization server return OAuth error bodies: error and error_description, with status 400, or 401 for invalid_client. The token endpoint of the API host returns the message body.

Found a problem? Edit this page or report an issue.

On this page