> For the full page index, fetch https://echonova.ai/llms.txt

# Auth connections

Save a basic, bearer or OAuth2 credential once, attach it to the tools that need it, and know exactly when EchoNova fetches a new token.

An auth connection is a saved credential with an authentication method attached.
Where a [workspace secret](/docs/agents/secrets) is one opaque value that you name a
header for, a connection knows how to authenticate on its own and always produces
the `Authorization` header itself. Save it once, then attach it to the tools that
call that API.

## What you need

- Membership of the workspace. The connection controls ask for membership and no
  further permission.
- The credential in a form you can paste, plus the non-secret half of it: a
  username, a client id, a token URL, depending on the kind.

## The four kinds

| Kind                        | Label in the portal           | What you enter                                                                                               | What your API receives                                   |
| --------------------------- | ----------------------------- | ------------------------------------------------------------------------------------------------------------ | -------------------------------------------------------- |
| `basic`                     | **Basic**                     | **Username**, **Password**                                                                                   | `Authorization: Basic <base64 of username and password>` |
| `bearer`                    | **Bearer token**              | **Token**                                                                                                    | `Authorization: Bearer <token>`                          |
| `oauth2_client_credentials` | **OAuth2 client credentials** | **Client ID**, **Token URL**, **Client secret**, optional **Scopes** and **Extra params**                    | `Authorization: Bearer <access token>`                   |
| `oauth2_jwt`                | **OAuth2 JWT**                | **Issuer**, **Subject**, **Audience**, **Token URL**, **Private key**, optional **Algorithm** and **Scopes** | `Authorization: Bearer <access token>`                   |

The secret half of each kind is sealed: the password, the token, the client secret,
the private key. Everything else is configuration and is shown back to you so you
can edit it: the username, the client id, the token URL, the issuer, the subject,
the audience, the algorithm, the scopes and the extra params.

**Algorithm** defaults to `RS256` when you leave it blank. **Private key** is
PEM-encoded. **Scopes** is a free string that is normalised before it is sent, with
commas and any run of whitespace collapsed into single spaces. **Extra params**
exists on the client credentials kind only, is entered as one `key=value` per line,
and is appended to the token request body.

## Steps

### Step 1: Open the settings page

Go to **Settings** in the agents navigation and switch to the auth connections tab,
under the line "Reusable auth methods referenced by tools and channels."

### Step 2: Press Add auth and choose a kind

**Add auth** opens a menu of the four kinds. Picking one opens **Add auth
connection** with that kind preselected, and the form redraws to the fields that kind
needs.

### Step 3: Name it

**Name** is required, trimmed, and must be unique in the workspace. The placeholder
is `My API auth`. The check covers every connection in the workspace, including the
managed integration credentials this page does not list, so a name can collide with
something you cannot see.

### Step 4: Fill the fields for the kind

Required fields carry a red asterisk. Every one of them is checked again when you
save, and a blank required field is rejected by name, for example
`clientId is required`.

### Step 5: Save

**Save** seals the secret half and stores the rest. The row appears in the list with
its name and a chip naming the kind.

## Get it right

A connection is the whole access control on every call the agent makes with it, so
give it its own identity at the API and only the access the agent needs.

**Recommended**

```text
Client: echonova-agent (machine user)
Scopes: orders:read shipments:read
Access: the three endpoints the imported API actually uses
```

**Less effective**

```text
Client: the admin API token already in use by our back office
Scopes: full access
Access: every endpoint, including the ones that refund and delete
```

## What happens next

The secret half is sealed the same way a workspace secret is: encrypted with
`AES-256-GCM` under a key issued by AWS KMS for that one connection and bound to
your workspace. Afterwards you get back the id, the name, the kind, the non-secret
configuration, the timestamps and a masked hint of the last four characters. The
sealed half is never returned.

The connection is now selectable wherever a tool asks for auth. Nothing filters the
list by kind, so any of the four works with any tool that offers the picker.

## Which tools can use one

| Place                    | Where it is picked                                         | Scope                         |
| ------------------------ | ---------------------------------------------------------- | ----------------------------- |
| A webhook tool           | **Auth connection** in the tool's **Authentication** block | The whole tool                |
| An imported OpenAPI tool | The auth picker in the import form                         | Every operation that inherits |
| One OpenAPI operation    | The operation's auth mode set to a named connection        | That operation only           |

An OpenAPI operation can also be set to use no auth at all, which overrides the
tool's connection. [OpenAPI tool operations](/docs/agents/openapi-tool-operations)
has the full table of modes.

A connection named by a tool must exist when the tool is saved. A missing one is
refused with `Referenced credential not found`, or
`Referenced credential not found for operation "<name>"` for a per-operation
override during an import.

Channels hold their credentials separately, and the Channels pages cover them.

## How the OAuth2 kinds get a token

Neither OAuth2 kind uses a refresh token, and neither asks a person to authorise
anything. Both are machine-to-machine grants, so everything needed to mint a new
token is already saved.

**Client credentials.** EchoNova posts `grant_type=client_credentials` to your
**Token URL**, authenticating with HTTP Basic built from the client id and client
secret, and adds the scopes and extra params when you set them.

**JWT.** EchoNova signs a JWT assertion with your private key using the algorithm
you chose, stamped with the issuer, subject and audience you entered and valid for
300 seconds, then posts it to your **Token URL** as
`grant_type=urn:ietf:params:oauth:grant-type:jwt-bearer`.

An access token is reused until shortly before it expires. EchoNova keeps it for the
lifetime your endpoint reported minus 60 seconds, never less than 30 seconds, with
up to 9 seconds of variation so that many connections do not renew in lockstep. An
endpoint that reports no lifetime is treated as 300 seconds. Calls that need a token
at the same moment share one request to your token endpoint rather than sending
several.

EchoNova also fetches a fresh token when your API answers `401`. That retry runs
once, and only for the two OAuth2 kinds. A basic or bearer connection that gets a
`401` is reported as it is, because there is nothing to refresh.

The token URL is called over HTTPS only, must resolve to a public host, redirects
are not followed, and the request is abandoned after 10 seconds.

## Rotating a credential

Rotation is an edit, not a replacement, so tools keep pointing at the same connection
and nothing needs repointing.

1. Open the row menu and press **Edit**.
2. Leave every secret field blank to keep the current value, or type the new one to
   replace it. The placeholder shows eight bullets where a value is already stored.
3. Adjust the non-secret configuration if it changed too.
4. Press **Save**.

The kind cannot change. The select is locked on an existing connection and the form
says "Connection type cannot be changed after creation." A credential that moves
from bearer to OAuth2 is a new connection and a repoint.

Supplying a new secret re-checks the whole field set for that kind, so a rotation
that leaves out a now-required configuration field is rejected rather than
half-applied. On success the next call uses the new credential; there is no wait.

An opened credential is held in memory for at most five minutes and is not written
anywhere. Editing a connection makes the older value unreachable at once.

## Deleting one

**Delete** on the row menu asks to confirm, naming the connection and warning that
"Tools or channels that reference it will break."

Before the connection is removed, EchoNova checks whether any tool in this workspace
still points at it, either as a webhook tool's connection or as an imported OpenAPI
tool's connection. If one does, the delete is refused with a `409` and the message
`Auth connection is in use by a tool`, which the portal shows as "In use by a tool
or channel. Remove the reference first."

> [!WARNING]
> Deleting a connection cannot be undone, and the sealed credential goes with it. A
> tool that still references a deleted connection is not switched off: no
> `Authorization` header is sent, so the call goes out unauthenticated and your API
> sees an anonymous request rather than a failure. The in-use check covers a tool's
> own connection only, so check the per-operation auth overrides of your imported
> OpenAPI tools yourself before you delete.

## Credentials EchoNova manages for you

Connecting Slack, Meta, Google Calendar or Zoho saves that integration's token
alongside these, flagged as managed. Managed credentials are left out of the list on
this page, cannot be edited here, and are exempt from the in-use check, because
disconnecting the integration removes its own tools.

These are the only credentials that hold a refresh token, and they are renewed for
you. If renewal fails, for instance because the provider revoked the refresh token,
calls keep going out with the token already held and the provider rejects them.
Reconnect the integration to fix it; rotating a connection on this page cannot,
because a managed credential is not one of them.

## Limits

| Limit                                           | Value                                                                 | Applies to                         |
| ----------------------------------------------- | --------------------------------------------------------------------- | ---------------------------------- |
| Kinds                                           | `basic`, `bearer`, `oauth2_client_credentials`, `oauth2_jwt`          | Create                             |
| Name                                            | Required, trimmed, unique in the workspace                            | Create                             |
| Kind after creation                             | Cannot be changed                                                     | Edit                               |
| Reading the credential back                     | Not returned anywhere                                                 | The whole lifetime of a connection |
| JWT assertion lifetime                          | 300 seconds                                                           | The two OAuth2 kinds               |
| Access token reuse                              | Reported lifetime minus 60 s, floor 30 s, plus up to 9 s of variation | The two OAuth2 kinds               |
| Assumed lifetime when the endpoint reports none | 300 seconds                                                           | The two OAuth2 kinds               |
| Token request timeout                           | 10000 ms                                                              | The two OAuth2 kinds               |
| Token URL                                       | HTTPS only, public hosts only, redirects not followed                 | The two OAuth2 kinds               |
| Retry on `401`                                  | Once, OAuth2 kinds only                                               | Tool calls                         |
| Opened credential held in memory                | 5 minutes, 2000 values                                                | Tool calls                         |
| Permission                                      | Workspace membership, nothing further                                 | Every connection control           |

## Troubleshooting

### "Duplicate value for name"

A connection in this workspace already uses that name. Pick a different one. The
check covers every connection in the workspace, including the managed integration
credentials this page does not list, so the collision is not always visible to you.

### "OAuth token request failed"

Your token endpoint could not be reached, or the request timed out after 10 seconds.
Check that the **Token URL** is reachable from the public internet and answers
within that window.

### "OAuth token request rejected"

Your token endpoint answered, but not with a success status. The usual causes are a
wrong client secret, an expired signing key, a scope the client is not entitled to,
or a JWT assertion whose issuer, subject or audience does not match what the
endpoint expects.

### "OAuth token endpoint returned no access_token"

The endpoint answered with a success status and a body that has no `access_token`
field. Check that the **Token URL** is the token endpoint itself and not an
authorization or discovery URL.

### "Target URL must use https"

The **Token URL** is not HTTPS. It is checked when the token is fetched rather than
when you save, so this surfaces on the first tool call rather than in the dialog.

### "Target URL must be a public host"

The **Token URL** points at localhost, a private range or a link-local address. Only
public hosts are called.

### "Connection is not an OAuth2 type"

Something asked for an access token from a basic or bearer connection. Check the
kind on the connection the tool is using.

### Your API answers 401 on every call

Confirm the tool is pointing at the connection you think it is, then check the
credential itself by rotating it. Remember that a deleted connection produces no
`Authorization` header at all rather than an error, so an unauthenticated request
reaching your API usually means the reference is dangling.

## FAQ

### Can I see a credential again after saving it?

No. It is not returned anywhere and the portal has no reveal control. You get a
masked hint of the last four characters and the non-secret configuration.

### Do I have to re-authorise an OAuth2 connection when its token expires?

No. Neither OAuth2 kind uses a refresh token or an authorisation prompt. A new
access token is minted from the stored credentials whenever the current one runs
out.

### Can one connection serve several tools?

Yes. A connection belongs to the workspace, and any number of tools can point at it.
That is the reason it exists.

### Can a webhook tool use an OAuth2 connection?

Yes. Every kind produces an `Authorization` header, and no picker filters by kind.

### What wins when a tool also sets a header of the same name?

The connection. Auth is written last on both the webhook path and the imported API
path, so its `Authorization` header replaces a header parameter or a fixed value
that used the same name.

### Where did my Slack or Google credential go?

It is saved as a managed credential and is not listed on this page. Manage it from
the integration that created it.

## Related

- [Workspace secrets](/docs/agents/secrets): the simpler option for a single API key header
- [Webhook tools](/docs/agents/webhook-tools): attach a connection to one endpoint
- [OpenAPI tools](/docs/agents/openapi-tools): attach a connection to a whole imported API
- [OpenAPI tool operations](/docs/agents/openapi-tool-operations): override the credential for one operation
- [Tools overview](/docs/agents/tools-overview): how the four tool kinds differ
