Interlaken
Authentication

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_credentials

Add -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.

The routes

On this page