Open the console

Concepts

Authentication

Every call carries a credential, and which kind you hold decides what you can reach. There are four, and they are not interchangeable.

The four kinds

  1. A person. You, signed in to the console or through elan login. Carries your identity and your role, and no project: a person administers every project their tenant owns.
  2. A service user. Your backend. A client id and secret you exchange for a token — scoped to one project, with or without the admin role. This is what you create on a project's API access page.
  3. A ticket. Short-lived and pinned to one web origin, issued for one of your end users — or for yourself, so a page can watch your own runs. It opens the event stream and nothing else.
  4. A pod. An agent calling the platform from inside the cluster. You never hold one of these; the cluster mints it, and the namespace it names is the one thing it cannot lie about.

A client id and secret is not an API key

You exchange it for a token that lasts about an hour, and send the token. The difference matters: a captured request — a log, a proxy, a support ticket with headers pasted in — carries something that expires rather than your credential, and no service has to look your key up on the path of every call. So the id and secret should reach nothing but the token endpoint. There are no personal access tokens, deliberately: a long-lived bearer carrying a person's whole authority ends up in a shell history and outlives their employment.

The exchange is a standard OAuth client_credentials grant at POST /v1/token, on the same host as every other route, so any OAuth library can point at it and handle expiry for you. The platform does not judge your secret itself: it forwards it to the identity provider, adds the one scope that addresses the token to this platform, and returns the provider's answer untouched. You never send a scope, and one you do send is ignored — what a credential may do was decided when it was created and travels inside the token. Steps are on call it from your code, and the routes on the authentication API.

What a token carries

Three claims decide what happens to your calls. Every service reads them out of the token itself, against the issuer's published keys, so nothing is looked up per call.

ClaimWhat it decides
tenant_idYour organization, and every route is scoped to it. It is never read from a request, so no call can name another tenant. A token without one is refused everywhere.
roleuser, agent-engineer, data-engineer or admin. Only admin grants anything; the two engineer roles change what the console shows and nothing a route allows. A value the platform does not recognize is read as user.
projectThe one project a service user was issued for. Absent on a person's token, which means every project the tenant owns.

A token whose subject is * is refused too. That value means the tenant scope in the policy store, and no principal may claim it.

What a credential may do

Reads need any valid token for the tenant. A specific set of actions needs admin — the role a service user gets when Can write is on: crediting or correcting a wallet, minting a ticket for one of your users, invoking an agent for one of your users, reading or deleting their conversations, setting the tenant's autonomy cap, and managing service users. Two things that act for a user do not: reading or changing one of your users' own autonomy policy, and their files, are open to any credential for the project. Deploying is not on the admin list either — the deploy route checks the project scope and not the role.

The project scope is the second axis. A service user issued for agents that names tools in a path or a request body is refused with 403, naming the project it is for. That holds on both services behind the API host, including invoking an agent and minting a ticket, because the scope travels in the token rather than being checked in one place.

When something is refused

You seeIt means
401 invalid_client from the token endpointThe id or secret was not accepted. Which one is deliberately not said, so the endpoint cannot be used to discover which credentials exist. If you just rotated, the old secret stopped at that moment.
502 or 503 from the token endpointYour credential was never judged — the identity provider could not be reached. Retry; do not rotate anything.
401 anywhere elseA missing, expired or malformed token. Mint a new one.
403A valid token without the authority: a sibling project, or an action that needs admin. The message says which.
404Nothing by that name for you — a project your tenant does not have, or an id that belongs to a project your credential cannot see. Answering 404 rather than 403 there is deliberate: a guessable id must not confirm that something exists.

Rotating and revoking

A secret is shown once; nothing can read it back, so the only way back from losing one is to rotate. Rotating issues a new secret and the old one stops working immediately. Creating a service user under a name that already exists rotates it rather than adding a second identity — two credentials with one name is an audit log nobody can read — so a careless repeat invalidates the secret the first one is still using. Nothing expires a secret for you; rotate on a schedule you choose.

Deleting stops new tokens, not the ones already issued

Deleting a service user is what revokes it: no new token can be minted for it. But every service checks a token's signature and expiry rather than asking anyone whether it is still wanted, so a token minted before the delete keeps working until it expires — up to about an hour. A change to a credential's scope behaves the same way: it applies to the next token.

Signing in as a person

Through the console, or elan login, which runs the device flow in your browser and keeps the refresh token in your operating system's keyring rather than in a file — a token under your home directory is readable by every process running as you and is backed up wherever that directory goes. Neither asks you for a scope, an issuer or an audience.

Do not run a backend on your own token. It carries your whole authority, it is not scoped to a project, and nothing revokes it when you leave.

Letting your users' browsers connect

Your end users are not accounts on this platform, and should not be — there may be millions of them and they are your customers, not ours. When one of their browsers needs to receive a run's events, your backend exchanges its own admin credential for a ticket scoped to that one person and that one project, and hands it to the page.

  • It opens the event stream and nothing else. Every other route refuses it with 403: it cannot invoke, read a transcript, upload a file, decide an approval, spend a balance or mint another ticket.
  • It is pinned to an origin, matched exactly against the browser's own Origin header, which a page cannot change — so a ticket that leaks into another site is useless there.
  • It lasts five minutes and is checked when the socket opens, so fetch a fresh one for every reconnect.

Never put a client secret in a page. Charging your users walks through the whole flow, and TypeScript and React covers the two credentials that may reach a browser.

Where to go next

Call it from your code creates a service user and puts it to work. The authentication API covers the token exchange and the routes that manage service users, and the platform overview explains the project a credential is scoped to.

Reading this as a model? llms.txt indexes every page, and each page has a “Copy for AI” control.