Skip to main content
Every request to the Basaltic API carries a bearer token. There are two ways to get one, and which you want depends on who is calling. A program authenticates as a service account: it holds an access key pair and exchanges it for a short-lived token. A person signs in with basaltic login, which opens a browser and returns a token that acts as them. Both use Authorization: Bearer. Personal organization access and account role access are separate: all humans assume an assigned account role before making account service requests. The difference is not cosmetic. A service account cannot create an organization, accept an invitation, or switch organization — those need a person. If the CLI is telling you an operation requires a user, that is what it means. The same key pair is also your AWS Signature Version 4 credential for the S3-compatible object endpoint, which speaks nothing else. One credential, two presentations, and no choice to make at request time: token for this API, key pair for S3.

Getting credentials

Access keys belong to a service account, never to a person. Create one, give it a policy, then create a credential on it — see the quickstart. The secret is shown once, at creation, and never again. For short-lived credentials, see temporary credentials for a role below.

Getting a token

This is the OAuth 2.0 client-credentials grant, unmodified, so any OAuth-aware library will perform it and refresh for you. Credentials may go in an HTTP Basic header, as above, or as client_id and client_secret form fields. Then send it on every call:
A service account token is bound to its owning account. The header must agree with that account for account operations. It cannot move the token to a different account. For that, assume a trusted role in the target account. Organization APIs evaluate the identity’s explicit organization grants instead.

Token lifetime

One hour by default. Ask for another with duration_seconds, between 900 and 43200. A value outside that range is clamped into it rather than refused, so asking for a day gives you the longest token allowed. Treat the token as opaque. Do not parse it, and do not use the token string as a key in a cache, a deny-list or a deduplication table — a signature has several valid encodings, so one token can appear as several different strings.

When something is refused

Two answers look alike and have opposite remedies: An unknown access key and a wrong secret answer identically, so the endpoint cannot be used to discover which keys exist. Errors here use the OAuth 2.0 shape — {"error": ..., "error_description": ...} — rather than this API’s usual envelope, because that is what OAuth client libraries parse.

Revoking a token

This ends the session behind the token, so everything it issued stops working on the next request rather than at expiry. It answers 200 whether or not anything was revoked — an endpoint that told you the difference would tell anyone holding a token whether it still works.

Signing in as yourself

It prints a URL. Open it, approve the CLI, pick an organization, and the page gives you a code; paste that back into the terminal and you are signed in. No long-lived credential is created — the CLI keeps a short-lived token and a refresh token in ~/.config/basaltic/credentials.yaml, mode 0600, separate from config.yaml so your config stays safe to share. The browser does not have to be on the same machine. That is the whole reason it is a code and not a redirect: open the URL on your laptop, carry the code to the terminal on the server you are working on. A redirect would have to land somewhere, and “somewhere” is only ever the machine running the browser. Use this when you are a person at a terminal. Use an access key when the caller is a program that has to run without you. Under the hood it is OAuth 2.0 authorization code with PKCE, delivered out of band (redirect_uri=urn:ietf:wg:oauth:2.0:oob). A few consequences are worth knowing:
  • The CLI holds no client secret. It ships to laptops, so any secret it held would be readable by anyone holding the binary. What protects the flow is PKCE: redeeming the code requires a verifier the CLI generated when the flow started and does not send until the moment it spends the code. Seeing the code is not enough to use it.
  • The code is single use and lasts five minutes. It is spent by the attempt, not by the success, so an intercepted code cannot be retried against guessed verifiers.
  • Paste it only into the terminal you started. A code is worth nothing without the verifier held by the process that asked for it, so pasting one into some other program achieves nothing — but a program that asks you to is not doing anything it needs you for.
Your session lasts as long as a console session. The CLI refreshes it in the background; basaltic auth status shows who you are, which organization you are in, and when the credential expires. basaltic auth logout revokes it.
Unattended machines — a CI runner, a cron job — should use a service account access key rather than this flow. It needs a person to approve it, and a token that acts as a person is the wrong thing for unattended work anyway.

Temporary credentials for a role

POST /v1/assume-role on iam.basaltic.sh issues credentials for a role in your organization. Cross-account assumption needs both a source iam:AssumeRole permission and target trust. Human account role assignments supply the source permission; they do not change the trust policy. The response carries both forms from one session: an access_token for this API, and the SigV4 four for S3. They share an expiry, and revoking the session stops both. Instances get the same pair from the metadata service — the AWS-compatible path for S3 credentials, and /latest/basaltic/iam/access-token for the token. The response also includes account_id, account_handle, and role_id. Verify these against the target you selected. A session uses the target role’s permissions, without unioning permissions from its source. The console keeps a personal token for Workspace pages and a separate selected account role token. Integrations should preserve the same distinction. See account access and roles.

Request signing (retired)

Control-plane APIs accept bearer tokens. Legacy BASALTIC-HMAC-SHA256 request signatures are not accepted. Exchange service account credentials through /v1/oauth/token, or use an AssumeRole response’s access_token. The S3-compatible object endpoint uses standard AWS Signature Version 4. When using temporary credentials, supply the session token through your AWS SDK’s session-token option. Control-plane requests do not use S3 signing.

Rate limits

There is no global request budget. A limit applies only where an operation documents a 429, and each such endpoint counts its own fixed window. Everything else is bounded by quota rather than by request rate. The limited endpoints here are the two public, unauthenticated ones — GET /v1/regions and the price catalogue GET /v1/prices — which are counted per client IP because there is no principal to count. Responses — 429 and 2xx alike — carry X-RateLimit-Limit, X-RateLimit-Remaining and X-RateLimit-Reset so you can pace yourself instead of discovering the ceiling by hitting it. Read X-RateLimit-Limit rather than hard-coding a number. On a 429, honor Retry-After. Retrying sooner is refused and extends the window, because the refused attempt is itself counted. Some resources meter the work rather than the request — sending a message is capped per identity per second — and document their own 429 on the operation.