> ## Documentation Index
> Fetch the complete documentation index at: https://sidenet.ai/docs/llms.txt
> Use this file to discover all available pages before exploring further.

# Authenticating tool providers

> Static credentials, OAuth 2.0 grants, and per-user credentials passed at runtime — which to use and how each is stored.

This page is about authenticating *to your API and other services* when an
agent calls a tool. Authenticating *to Sidenet* is covered under
[Authentication](/docs/authentication).

## Choosing an auth type

A provider has one `auth_type`. Pick the row that matches how the upstream
expects to be called:

| `auth_type` | Credentials                                    | When                                                            |
| ----------- | ---------------------------------------------- | --------------------------------------------------------------- |
| `bearer`    | `token`                                        | The API takes `Authorization: Bearer …`.                        |
| `api-key`   | `key`, optional `header` (default `X-API-Key`) | A fixed key in a named header.                                  |
| `basic`     | `username`, `password`                         | HTTP Basic.                                                     |
| `oauth2`    | A grant, see below                             | The API issues short-lived access tokens from a token endpoint. |
| `custom`    | Arbitrary header names and values              | Anything else — several headers, unusual names.                 |
| none        | —                                              | Public endpoints. Leave `auth_type` unset.                      |

Static credentials — bearer, API key, basic, custom — are sent as-is on every
call. OAuth 2.0 providers exchange their credentials for an access token,
cache it until shortly before it expires, and refresh it for you.

## OAuth 2.0 grants

The grant is declared inside `auth_config` as `grant_type`. Three are
supported:

**Client credentials** — machine-to-machine, and the default when a token URL
and client secret are present. Use it for your own API.

```json theme={null}
{
  "auth_type": "oauth2",
  "auth_config": { "grant_type": "client_credentials", "token_url": "https://auth.example.com/oauth/token", "client_id": "…", "scope": "read write" },
  "auth_secret": { "client_secret": "…" }
}
```

**Password** — for APIs whose only machine access is a username and password
presented at the token endpoint. Must be selected explicitly.

```json theme={null}
{
  "auth_config": { "grant_type": "password", "token_url": "…", "username": "svc-account" },
  "auth_secret": { "password": "…" }
}
```

**Refresh token** — a long-lived refresh token obtained from a one-time
interactive sign-in, typically for MCP servers fronted by Google or a similar
identity provider. Nothing opens a browser at runtime: the platform exchanges
the refresh token for access tokens, and when the server rotates the refresh
token the new one is written back to the vault.

```json theme={null}
{
  "auth_config": { "grant_type": "refresh_token", "token_url": "…", "client_id": "…" },
  "auth_secret": { "refresh_token": "…", "client_secret": "…" }
}
```

<Warning>
  The Studio's provider form offers an **authorization code** option with an
  authorization URL. The runtime does not perform that redirect flow: a
  provider saved that way is treated as a static token provider and calls
  upstream with whatever `access_token` it holds. Obtain the refresh token
  once, outside Sidenet, and configure the refresh-token grant instead.
</Warning>

Access tokens are cached per credential set for their `expires_in` minus a
minute, concurrent calls share one token request, and a failed exchange
surfaces as the provider's error rather than a generic transport failure.

## Per-user credentials at runtime

Any provider, whatever its `auth_type`, can also take credentials **per end
user**. Your backend supplies them when it mints the user's session, keyed by
provider id:

```bash theme={null}
curl -X POST https://api.sidenet.ai/v1/token \
  -H "Authorization: Bearer YOUR_API_KEY" \
  -H "Content-Type: application/json" \
  -d '{
    "user_id": "END_USER_ID",
    "tools_auth": {
      "PROVIDER_ID": {
        "credentials": { "token": "their-upstream-token" },
        "base_url": "https://staging.api.example.com"
      }
    }
  }'
```

Runtime credentials are merged over the provider's stored configuration, so
a partial override works: send only the user's `client_secret` and the token
URL comes from the provider. A runtime `token` or `access_token` short-circuits
the OAuth exchange entirely and is sent as a bearer. The optional `base_url`
points that user's calls at a different host — staging versus production, or
a per-tenant instance.

`GET /v1/tools-auth` lists every provider with the credential fields it
expects, so you can build the map without reading provider configuration:

| `authType` | `authFields`                                                     |
| ---------- | ---------------------------------------------------------------- |
| `bearer`   | `token`                                                          |
| `api-key`  | `key`, `header`                                                  |
| `basic`    | `username`, `password`                                           |
| `oauth2`   | `username`, `password` for the password grant; otherwise `token` |
| `custom`   | `headers`                                                        |

Credentials persist on the **user**, not the session: a later mint inherits
them, and unattended workflow runs and schedules read the same store.
Rotate or remove them with `PATCH /v1/users/{userId}`. See
[Authentication](/docs/authentication#tool-credentials-tools-auth).

### How this composes with session tokens

Your backend fills `tools_auth` at mint time. The browser holds a session
token that *refers* to those credentials and cannot read, change or replace
them — anything credential-shaped a page sends alongside a session token is
ignored. Under the organization API key, a `runtimeAuth` map in the request
body is honoured and wins over the stored credentials, which is how a
server-side integration tests a user's access. See
[Embedding securely](/docs/session-tokens).

<Note>
  Runtime credentials also decide who shares a cached tool result. A walk
  fetched with organization credentials is shared across the org; one fetched
  with a user's credentials is private to that user unless your backend
  asserts a tenant-wide `cache_scope`. See [Working with large APIs](/docs/large-apis#caching).
</Note>

## Connected apps

Catalogue toolkits — Gmail, Slack, and the rest — authenticate through
connected accounts rather than credentials you hold:

* **Org-scoped** — one account connected by an admin, used by every user.
* **User-scoped** — each end user connects their own account. The agent gets
  `list_connections` to see what is connected and `connect_toolkit` to start
  the sign-in, which returns a redirect URL for the user.

Which scope a toolkit uses is part of the provider's configuration. A
user-scoped action for a user who has not connected fails with a clear
"connect first" result, and scheduled workflows that depend on it are paused
with the same reason. See [User workflows](/docs/user-workflows#credentials).

## Storage and masking

* **Secrets go to the vault.** `client_secret`, `password`, `refresh_token`,
  tokens and keys are stored encrypted in Supabase Vault; the provider row
  keeps only a reference. Non-secret settings — grant type, token URL, scope,
  client id, username, header names — stay on the row.
* **Never returned on read.** Every read returns `auth_config` with secret
  values replaced by `***ENCRYPTED***` and a `has_vault_secret` flag. Header
  names in `custom` configurations are visible; their values are masked.
* **Preserved on edit.** A PATCH merges `auth_config` rather than replacing
  it, and any value equal to `***ENCRYPTED***` is skipped, so a
  read → edit → save round-trip in your own tooling never blanks a secret.
  Omit `auth_secret` to keep the current vault entry. A plaintext value sent
  for a key that lives in the vault is moved into the vault, not left to be
  shadowed.
* **Masked in logs.** Outbound requests log the URL, method, body length, a
  200-character body preview and the headers with `Authorization` and any
  header whose name contains `token`, `secret`, `api-key` or ends in `-key`
  reduced to a length and an eight-character preview. Per-user credentials
  are never logged, never returned by any endpoint, and never part of a cache
  key.

<Warning>
  The URL is logged in full. If an upstream API takes its key as a query
  parameter, put it in a `custom` header configuration instead so it is
  masked.
</Warning>
