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

# OAuth Clients — Access the VoiceInfra API

> VoiceInfra is an OAuth 2.1 authorization server. Server-side apps get a client by requesting one, and users approve access per workspace.

VoiceInfra runs an **OAuth 2.1 authorization server**. If you are building an application that acts on behalf of VoiceInfra customers — reading their agents, placing calls on their behalf, syncing their call data — this is how your app gets access.

An OAuth client is **not** the same as an API key:

| | API key | OAuth client |
| - | - | - |
| **Belongs to** | A single workspace | Your application |
| **Acts as** | Your own account | Each customer, in the workspace they choose |
| **Best for** | Your own scripts and internal tools | A product other people connect to |

If you only need to call the API from your own systems, an [API key](https://docs.voiceinfra.ai/docs/api/authentication.md) is simpler and is probably what you want.

## Two ways to get a client

| | Self-registration | Trusted client |
| - | - | - |
| **How** | Your app registers itself against the registration endpoint | You request one from VoiceInfra |
| **Secret** | None — public client | Yes — a `client_secret` |
| **Client type** | Public | Confidential |
| **Used by** | MCP clients such as Claude and ChatGPT | Server-side apps that can keep a secret |
| **Setup** | Immediate | Requires us to create it for you |

> [!NOTE]
>
> Self-registration is deliberately open — that is what lets Claude and ChatGPT connect without you creating credentials first — so its clients are treated as **unverified**. A trusted client is one VoiceInfra has created for a named app.

## Requesting a trusted client

A trusted client is created for you by VoiceInfra, so that a server-side app gets a secret it can hold safely. Contact [hello@voiceinfra.ai](mailto:hello@voiceinfra.ai) with:

* **Your app's name**
* **The redirect URIs** the app will use — at least one, up to five, and all `https` apart from `localhost` during development
* **The permissions you need**, and why
* Any **logo, policy, and terms URLs** you want shown on the consent screen, so customers can see who they are approving

We create the client and return a **`client_id`** and **`client_secret`**.

> [!WARNING]
>
> The `client_secret` is shown **once**. Only a hash is stored, so we cannot recover it — if it is lost, the client has to be registered again. Keep it server-side; never ship it in a mobile app or browser JavaScript.

## The authorization flow

Your app sends the customer to VoiceInfra, they approve, and you get back an authorization code to exchange for a token.

```text theme={null}
1. Send the user to /oauth2/authorize  (with PKCE)
2. They sign in, choose a workspace, and approve or deny
3. They return to your redirect URI — with a code, or an error
4. Exchange the code at /oauth2/token
5. Call the API with the access token
```

### 1. Start the authorization request

Send the user to the authorization endpoint with your client ID, redirect URI, requested scopes, a `state`, and a PKCE challenge:

```text theme={null}
GET {issuer}/api/v1/oauth2/authorize
  ?client_id=your_client_id
  &redirect_uri=https://yourapp.example.com/callback
  &response_type=code
  &scope=agents:read call-logs:read
  &state=your_csrf_value
  &code_challenge=your_pkce_challenge
  &code_challenge_method=S256
  &resource={resource_indicator}
```

**code\_challenge\_method** `string` *required*

Must be `S256`. PKCE is **required** — the plain method was removed in OAuth 2.1.

**resource** `string`

The resource you want a token for. Clients must name it (RFC 8707), which prevents a token issued for one resource being replayed against another.

### 2. The user decides

VoiceInfra shows your app's name, links, and the permissions you asked for — grouped into readable areas, with anything that **spends credit** called out separately. The customer picks which workspace the app may act in, and can allow change access to some areas and not others.

Your app's ability to change anything is still bounded by the customer's **role in that workspace**.

### 3. Receive the decision

The customer returns to your redirect URI with either a `code` or an `error` (`access_denied` if they refused). The response carries an `iss` parameter (RFC 9207) — verify it matches the issuer to protect against mix-up attacks.

### 4. Exchange the code for a token

```bash theme={null}
curl -X POST "{issuer}/api/v1/oauth2/token" \
  -H "Content-Type: application/x-www-form-urlencoded" \
  -d "grant_type=authorization_code" \
  -d "code=THE_CODE" \
  -d "redirect_uri=https://yourapp.example.com/callback" \
  -d "client_id=your_client_id" \
  -d "client_secret=your_client_secret" \
  -d "code_verifier=your_pkce_verifier"
```

The response contains an `access_token`, a `refresh_token`, `expires_in`, and the granted `scope`.

> [!WARNING]
>
> **Compare the granted scope to what you asked for.** A customer can approve some areas and not others, so a request for five permissions may come back with two. Handle the narrower grant rather than assuming full access.

### 5. Refresh

Access tokens expire. Exchange the refresh token at the same endpoint with `grant_type=refresh_token`. Refresh tokens rotate — store the new one each time.

## Revoking

```text theme={null}
POST {issuer}/api/v1/oauth2/revoke
```

Revoking invalidates the token. Customers can also disconnect your app themselves from **Connected apps**, which revokes the whole grant immediately — see [Connected Apps](https://docs.voiceinfra.ai/docs/api/connected-apps.md).

> [!NOTE]
>
> A customer disconnecting your app takes effect straight away, including on tokens that have not yet expired. Your app must handle a `401` by sending the user through the authorization flow again, not by retrying.

## Discovery

The server publishes its configuration, so clients do not need it hard-coded:

| Document | Endpoint |
| - | - |
| Authorization server metadata | `{issuer}/.well-known/oauth-authorization-server` |
| Protected resource metadata | `{issuer}/.well-known/oauth-protected-resource` |

| Property | Value |
| - | - |
| Response types | `code` |
| Grant types | `authorization_code`, `refresh_token` |
| PKCE methods | `S256` |
| Token endpoint auth | `none` (public clients), `client_secret_post` (confidential) |

## Building an MCP client instead?

If your app speaks MCP, you do not need to implement any of the above. Point it at the customer's MCP address, choose OAuth, and the registration and approval flow happens for you — see [MCP Server](https://docs.voiceinfra.ai/docs/api/mcp-server.md).


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