Connected apps (OAuth)
A token that acts as a person, with their permission, and can never do more than they can.
When what you are building works for a person, what you want is not a workspace key but that person's authorization. The result is a token that sees what they see. For an integration with no person behind it, use an API key instead.
The rule that governs everything
A connection can never do more than the person who authorized it. That is enforced at two moments, not one:
- At consent. The screen only offers the scopes that person could actually grant. No seat in Swarm means Swarm's scopes are not there to tick.
- On every call. Each app re-checks the token against the role and memberships that person holds today. The token does not bypass them.
So if that person loses an access tomorrow, the connection loses it too, without anyone having to remember to revoke it.
The flow
Standard OAuth 2.1, authorization code with PKCE. The result authorizes
access to data; it does not sign anyone into your product, so there is no
id_token, no /userinfo, and no nonce.
| Step | Endpoint |
|---|---|
| Discovery | GET /.well-known/oauth-authorization-server |
| Consent | GET https://auth.eel.software/oauth/authorize |
| Exchange and refresh | POST https://auth.eel.software/api/oauth/token |
| Revocation | POST https://auth.eel.software/api/oauth/revoke |
A client that speaks the protocol needs none of this written down: it hits the
MCP endpoint with no credential, gets a 401 whose WWW-Authenticate points at
the metadata, and finds everything from there.
What to know before writing code
- PKCE with S256 is mandatory, and it is the only client proof.
plainis not offered. - There are no client secrets. Every client is public. Do not go looking for where to paste one.
- Your
client_idcan be a URL. If it starts withhttps://, Eel reads it as a metadata document and remembers it for an hour. If you would rather register, dynamic registration lives atPOST /api/oauth/register. redirect_uriis matched character for character. The one exception is loopback, where the port is free because a desktop app cannot know in advance which one it will get.127.0.0.1,[::1], andlocalhostall count as loopback.- Every authorization response carries
iss, error responses included. A compliant client must reject one that does not.
Lifetimes
| Piece | Lasts |
|---|---|
| Authorization code | 60 seconds, single use |
Access token (eel_at_) |
1 hour |
Refresh token (eel_rt_) |
60 days, rotating |
Each refresh issues a new pair and consumes the old one. Reusing a spent refresh token revokes the whole family. Refresh from one thread only: if your client refreshes twice in parallel the result is a disconnection, which is better than somebody else quietly holding access. The new pair always inherits the family's scopes; refreshing cannot narrow them.
There is no introspection endpoint. A token cannot be queried ahead of time: you use it and see whether it answers.
A connection can authorize several workspaces
The consent screen offers a checkbox for each workspace you can grant, not a single choice: you can tick more than one at once. The first one you tick becomes the token's default workspace; the rest are just as authorized, each with its own scopes, narrowed to what you hold in that particular workspace. That is why one connection can legitimately be read-write in one workspace and read-only in another.
An MCP tool can ask to act in any of the authorized workspaces with the
workspace argument (id or slug); without it, it acts in the default workspace.
The connector URL also accepts ?workspace=<id> to preselect a checkbox. For the
same procedure told from the assistant's side, see Workspaces in
MCP and Connecting an
assistant.
Changing workspaces without reconnecting
Widening or narrowing an already-authorized connection's workspaces happens
in Nest, under Connected apps, and needs no fresh authorization from the
assistant's side: the token stays the same, with the same client_id and the
same refresh chain. The change lands within the same 60-second window that
governs revocation.
Removing the default workspace promotes the oldest surviving one. Removing the last one is the same as revoking the whole connection.
Widening the scopes without reconnecting
A connection carries the scopes signed into its consent. A scope Eel published afterwards does not reach it on its own, and its tools do not appear in the assistant's catalogue.
Whoever authorized the connection approves them in Nest, under Connected apps. That approval reaches only the applications the connection already uses, trimmed to what that person can grant today in each workspace. A connection holding no write scope in an application receives only new reads of it. The token is the same and so is the refresh chain: the change applies inside the same 60 second window. The assistant shows the new tools the next time it connects. If you want them sooner, disable and re-enable the connector: no new authorization needed.
When reconnecting is still required
Only in these cases:
- The first connection.
- A connection that has been fully revoked.
- A refresh chain that has already expired.
- Reaching an application the connection has never touched.
Narrowing scopes, changing workspaces, and widening scopes inside the applications the connection already uses never require it.
Revoking
Two places, two reaches:
- Each person, in Nest under Your account → Connected apps, revokes their own entirely, across every workspace it was authorized in.
- An admin, under Workspace → Connected apps, sees the connections authorized in THEIR workspace and can cut any of them, but only there: if that same connection is also authorized in another workspace, it stays alive there. It only dies for good when the workspace being cut was the last one it had left.
The cut takes up to 60 seconds to propagate.