API clients
The credential a program uses. Creating one, scoping it, rotating it, and taking it away.
An API client belongs to your tenant, not to a person. It survives someone leaving the company, it carries only the permissions you tick, and it can be revoked without touching anyone's account.
Creating one
curl -sX POST https://api.eu-par-1.interlaken.ai/api/v1/api-clients \
-H "Authorization: Bearer $TOKEN" \
-H 'Content-Type: application/json' \
-d '{
"name": "deploy-bot",
"description": "Creates and rolls VMs from CI",
"scopes": ["vms:read", "vms:create", "vms:delete"],
"expires_in_days": 365
}'name and scopes are required. expires_in_days is optional; leave it out and the
client does not expire on its own.
The response is the only time you will see the secret:
{
"id": "0f7c1e1e-…",
"tenant_id": "b21a-…",
"client_id": "ic_7Qa…",
"client_secret": "ics_9dF…",
"secret_hint": "…a91f",
"token_endpoint": "https://api.eu-par-1.interlaken.ai/oauth/token",
"name": "deploy-bot",
"scopes": ["vms:read", "vms:create", "vms:delete"],
"expires_at": "2027-09-11T00:00:00Z"
}The platform stores only a hash of the secret. Every later read of the client returns
secret_hint, the last few characters, which is enough to tell two credentials apart
and not enough to use.
Put the secret straight into a secret store. If it is lost, rotate rather than recreate, so nothing that references the client id has to change.
You cannot grant what you do not hold
Each scope you request is checked against the permissions of the caller creating the client. Ask for something you lack and the whole request fails rather than quietly granting a subset.
Two consequences worth planning around. A platform: permission is never grantable
to an API client, whoever asks, so operator routes stay out of reach of any machine
credential. And an API client cannot call the account self-service routes at all,
because it has no account to manage: changing a password, enrolling an authenticator
app or listing remembered browsers are for users only.
GET /api/v1/api-clients/scopes returns the catalogue already filtered to what you
may grant, grouped the way the console shows it. Build your picker from that rather
than a hard-coded list.
Getting a token
curl -s https://api.eu-par-1.interlaken.ai/oauth/token \
-u "$CLIENT_ID:$CLIENT_SECRET" \
-d grant_type=client_credentialsAdd -d scope="vms:read" to ask for less than the client holds. This is worth doing
when one credential serves several jobs: a token that can only read is a smaller
problem if it leaks. Asking for more than the client holds fails with
invalid_scope.
Rotating a secret
curl -sX POST https://api.eu-par-1.interlaken.ai/api/v1/api-clients/$ID/rotate \
-H "Authorization: Bearer $TOKEN"The response carries a new client_secret. The old secret stops working
immediately, with no overlap, so write the new one to your secret store before you
call this, and be ready for in-flight jobs to fail if they are mid-token-fetch.
Tokens already issued keep working until they expire, unless you also revoke them.
Taking access away
# Kill every token this client has out, keep the client itself
curl -sX POST https://api.eu-par-1.interlaken.ai/api/v1/api-clients/$ID/revoke-tokens \
-H "Authorization: Bearer $TOKEN"
# Remove the client entirely
curl -sX DELETE https://api.eu-par-1.interlaken.ai/api/v1/api-clients/$ID \
-H "Authorization: Bearer $TOKEN"Both take effect on the next request anywhere, because revocation is checked at verification time rather than trusted to expiry.
Use revoke-tokens when a token may have leaked but the credential is still good,
and rotate when the secret itself may have leaked. Delete when the job is over.
Narrowing the blast radius
Some habits that pay for themselves:
- One client per job, not one per team. Revoking then costs one pipeline, not all of them.
- Read-only where you can. Most automation reports rather than changes.
- Set
expires_in_days. A credential nobody has thought about in a year is a credential nobody is watching. - Watch
last_used_at. It is on every client, and a client that has not been used in months is either dead or someone else's.