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
client_id and client_secret form fields.
Then send it on every call:
Token lifetime
One hour by default. Ask for another withduration_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
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
~/.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.
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. LegacyBASALTIC-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 a429, 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.
