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

# Authentication

> Understand how API keys and OAuth access tokens authenticate Agreements API requests, how environments and scopes affect access, and why authentication fails.

For the complete documentation index, see [llms.txt](https://docs.shodai.network/llms.txt).

Credentials identify the Shodai account behind an Agreements API request. Use an API key when your integration manages a credential for one account. Use delegated OAuth when your application asks a Shodai user to connect their account without sharing an API key.

The account's entitlements determine which operations it may perform. OAuth scopes can further limit a delegated application's access.

## Choose a credential

| Credential | How you obtain it | Request header | Common use |
| - | - | - | - |
| API key | Create a testnet key in the Developer Portal or receive a provisioned production key. | `X-API-Key: cns_pk_...` | Service integrations and clients that manage a Shodai key directly. |
| OAuth access token | A signed-in Shodai user approves a delegated OAuth connection. | `Authorization: Bearer <access-token>` | Applications that connect a user's account, including OAuth-capable MCP clients. |

API keys and OAuth access tokens are credentials for a Shodai account, not separate resource containers. Credentials for the same account use that account's entitlements and can access agreements created by the account and webhook subscriptions owned by it, subject to credential scopes and resource-level access rules. Revoking one credential does not delete the account's agreements or webhook subscriptions.

## API keys

Use `X-API-Key` as the canonical header. `Authorization: Bearer cns_pk_...` is also supported for clients that cannot set a custom API-key header; it is an API-key compatibility representation, not an OAuth access token.

```text theme={"theme":{"light":"github-light","dark":"github-dark"}}
X-API-Key: cns_pk_...
```

or:

```text theme={"theme":{"light":"github-light","dark":"github-dark"}}
Authorization: Bearer cns_pk_...
```

Create testnet keys through the [Developer Portal](https://developers.shodai.network/portal). Production keys are provisioned for approved production access. Every key is bound to the environment where it was created, and revoking the key or disabling its account prevents further use.

<Warning>
  Store the plaintext key when it is issued. The API stores only the hashed key afterward.
</Warning>

Follow [Quickstart with TypeScript SDK](/sdks/quickstart-with-typescript-sdk) for an executable setup or use the [TypeScript client reference](/sdks/typescript-client) for constructor details.

## Delegated OAuth

Delegated OAuth lets your application act for a Shodai user after that user signs in and approves the requested access. Shodai supports public clients with no client secret, the authorization-code flow, mandatory S256 PKCE, short-lived access tokens, and rotating refresh tokens.

Send an access token as:

```text theme={"theme":{"light":"github-light","dark":"github-dark"}}
Authorization: Bearer <access-token>
```

The user journey is:

1. Register a public application with its redirect URIs and allowed scopes.
2. Send the user to Shodai sign-in and consent with S256 PKCE.
3. Let the user review the application, callback destination, and requested permissions, then approve or deny access.
4. Exchange the returned authorization code for an access token and refresh token.
5. Refresh and persist the rotated session until the application or user disconnects it.

### Register the application

The normal setup path is [OAuth apps in the Developer Portal](https://developers.shodai.network/oauth-apps). A registered public client ID begins with `cns_oa_...` and has no client secret. Register every callback URI and allow only the scopes the application needs.

Redirect URIs must match exactly. An HTTP loopback callback using `127.0.0.1`, `[::1]`, or `localhost` may use a different port when its hostname and path match and the callback has no query string.

An HTTPS Client ID Metadata Document is the advanced alternative to portal registration. The document URL itself is the `client_id`:

```json theme={"theme":{"light":"github-light","dark":"github-dark"}}
{
  "client_id": "https://client.example/oauth/client.json",
  "client_name": "Example application",
  "redirect_uris": ["https://client.example/oauth/callback"],
  "token_endpoint_auth_method": "none",
  "grant_types": ["authorization_code", "refresh_token"],
  "scope": "agreements.read agreements.write"
}
```

The document's `client_id` must exactly match its HTTPS URL. Non-loopback redirect URIs must use HTTPS and share the document URL's origin; HTTP loopback callbacks remain supported.

### Discover endpoints

Fetch `<issuer>/.well-known/oauth-authorization-server` and use the returned `authorization_endpoint`, `token_endpoint`, and `revocation_endpoint`. Do not derive or hard-code those endpoint paths.

### Authorize the user

Send these parameters to the discovered authorization endpoint:

* `client_id`: the registered client ID or metadata document URL
* `redirect_uri`: a matching registered callback
* `response_type=code`
* `state`: a random value your application validates on return
* `code_challenge`: the S256 challenge derived from the PKCE verifier
* `code_challenge_method=S256`
* `scope`: the smallest space-delimited set of permissions the application needs

Approval returns `code` and the original `state` to the callback. Denial returns `error=access_denied` and the original `state`. Validate `state` before accepting either callback.

Exchange an approved code at the discovered token endpoint with a form-encoded request containing `grant_type=authorization_code`, `client_id`, `code`, the identical `redirect_uri`, and the original `code_verifier`.

The response includes `access_token`, `expires_in`, `scope`, and a rotating `refresh_token`. Store both tokens using storage appropriate for your application; do not expose them in browser URLs or logs.

### Refresh the session

Before the access token expires, send a form-encoded request to the discovered token endpoint with `grant_type=refresh_token`, `client_id`, and the current `refresh_token`. You may include `scope` to narrow the access token's granted scope.

Every successful refresh returns a replacement `refresh_token`. Persist the replacement before using the session again, and never reuse the superseded token. Reuse detection revokes that refresh-token family.

### Disconnect access

An application can send its current refresh token in the form-encoded `token` field to the discovered revocation endpoint. This prevents future refresh for that token's rotation family, but does not revoke other families or pending authorization codes for the same user and client.

A user can disconnect the application from [OAuth sessions](https://developers.shodai.network/oauth-sessions). This revokes all refresh tokens and pending authorization codes for that user and client pair.

Already-issued access tokens remain usable until their short expiry after either disconnect path. Disabling a registered application prevents new authorization and refresh but does not extend or revoke those access tokens.

For a runnable Node.js CLI or desktop-style implementation, follow [Connect an installed TypeScript client with delegated OAuth](/sdks/delegated-oauth-with-typescript). OAuth-capable MCP hosts automate the browser journey described in [Quickstart with MCP](/sdks/quickstart-with-mcp).

## Match the credential to the environment

API keys work only in the environment where they were created. Use a testnet key with the testnet API and a production key with the production API.

OAuth access tokens carry the issuer of the environment that minted them. Each hosted Agreements API environment validates the issuer configured for that environment, so a token minted by one environment cannot authenticate to the other.

| Environment | OAuth issuer and discovery base | Agreements API origin |
| - | - | - |
| Testnet | `https://testnet.shodai.network/auth-api` | `https://test-api.shodai.network` |
| Production | `https://app.shodai.network/auth-api` | `https://api.shodai.network` |

Append `/.well-known/oauth-authorization-server` to the issuer to fetch metadata. Treat that metadata as the source of truth for OAuth endpoint URLs.

<Warning>
  The hosted MCP endpoint at `https://shodai.network/mcp` currently advertises only the testnet OAuth issuer. An OAuth connection discovered through hosted MCP therefore works only with MCP tools called using `environment: "testnet"`; production calls return `401`. Use a production API key for hosted MCP production calls until production MCP OAuth is enabled.
</Warning>

<Note>
  Testnet access is free and self-service. Production access is available by request. [Request production access](https://developers.shodai.network/support).
</Note>

## OAuth request scopes

OAuth authorization requests accept these scopes:

| Scope | Operations |
| - | - |
| `agreements.read` | Read agreement records, state, and input history. |
| `agreements.write` | Validate, deploy, and submit agreement inputs. |
| `webhooks.read` | List and inspect webhook subscriptions. |
| `webhooks.write` | Create, update, disable, and test webhook subscriptions. |

Wildcard values are not valid OAuth request scopes.

## Account entitlements

The authenticated account's entitlements apply to API keys and OAuth access tokens. Entitlement matching accepts the four exact scopes above plus these wildcard values:

* `agreements.*`
* `webhooks.*`
* `*`

OAuth token scopes additionally cap the operations available to that token. For example, an `agreements.read` token cannot perform an agreement write even if the account has an `agreements.write` entitlement.

Entitlement modes are:

| Mode | Result |
| - | - |
| `free_allowlist` | Allows the scoped operation. |
| `blocked` | Returns `403 Forbidden` for the scoped operation. |
| `paid_required` | Returns `402 Payment Required`; per-call settlement is not implemented. |

## Common authentication failures

| Status | Meaning | What to check |
| - | - | - |
| `401` | The credential is missing or malformed; the OAuth access token is invalid or expired; or the API key is invalid, revoked, disabled, or belongs to another environment. | Confirm the header shape, use the current credential, and verify that its environment matches the API. Refresh an expired OAuth access token when the connection still has a valid refresh grant. |
| `402` | The authenticated account has `paid_required` for the requested scope. Per-call settlement is not implemented. | Ask the API operator to review the account's entitlement for the requested scope. |
| `403` | The credential is valid, but the OAuth token scope, account entitlement, or resource access does not allow the operation. | Confirm the requested OAuth scope, the account's exact or wildcard entitlement, and the caller's access to the resource. |

## Header casing

Authentication header names are case-insensitive at the HTTP layer, but examples use `X-API-Key` and `Authorization` consistently.

## Related pages

* [Quickstart with TypeScript SDK](/sdks/quickstart-with-typescript-sdk)
* [Quickstart with MCP](/sdks/quickstart-with-mcp)
* [Connect an installed TypeScript client with delegated OAuth](/sdks/delegated-oauth-with-typescript)
* [TypeScript client reference](/sdks/typescript-client)
* [Errors and troubleshooting](/reference/errors-and-troubleshooting)
* Use the API Reference group in the sidebar for generated request and response details.


This documentation is built and hosted on [Mintlify](https://mintlify.com), a developer documentation platform.