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

# Diameter API Authentication with OAuth2 Client Credentials

> Diameter uses OAuth2 Client Credentials to issue short-lived Bearer tokens. Learn how to obtain tokens, manage scopes, and handle token expiry.

Every request to the Diameter API must be authenticated with a valid Bearer token. Diameter implements the **OAuth 2.0 Client Credentials** flow — your server exchanges a `client_id` and `client_secret` for a short-lived access token, then attaches that token to downstream API calls. Tokens are valid for **600 seconds (10 minutes)** and cannot be refreshed; request a new one using the same flow when the current one expires.

## Getting Your Credentials

To obtain a `client_id` and `client_secret`, email [support@diameterpay.com](mailto:support@diameterpay.com). Include your company name and a brief description of your use case. The Diameter team will provision a Tenant for you and reply with sandbox credentials.

> [!WARNING]
>
> Store your `client_secret` securely. It is shown only once during provisioning. Never embed it in client-side code, commit it to version control, or share it in plaintext. Use environment variables or a secrets manager in all environments.

## Token Endpoints

Use the sandbox endpoint during development and testing. Switch to the production endpoint only when you are ready to go live.

| Environment | Token Endpoint |
| - | - |
| **Sandbox** | `POST https://auth.sandbox.diameterpay.com/oauth2/token` |
| **Production** | `POST https://auth.prod.diameterpay.com/oauth2/token` |

## Requesting a Token

Send a `POST` request to the token endpoint with a body encoded as `application/x-www-form-urlencoded`. Include the four fields below.

**grant\_type** `string` *required*

Must be `client_credentials`.

**client\_id** `string` *required*

The Client ID issued to your Tenant.

**client\_secret** `string` *required*

The Client Secret issued to your Tenant.

**scope** `string`

A space-separated list of OAuth scopes your token needs. Omit this field to request all scopes granted to your Tenant, or list only the scopes you need (recommended).

```bash cURL theme={null}
curl -X POST https://auth.sandbox.diameterpay.com/oauth2/token \
  -H "Content-Type: application/x-www-form-urlencoded" \
  -d "grant_type=client_credentials" \
  -d "client_id=YOUR_CLIENT_ID" \
  -d "client_secret=YOUR_CLIENT_SECRET" \
  -d "scope=com.diameterpay.api/end-users.read com.diameterpay.api/end-users.write com.diameterpay.api/payments.read com.diameterpay.api/payments.write"
```

```python Python theme={null}
import requests

response = requests.post(
    "https://auth.sandbox.diameterpay.com/oauth2/token",
    data={
        "grant_type": "client_credentials",
        "client_id": "YOUR_CLIENT_ID",
        "client_secret": "YOUR_CLIENT_SECRET",
        "scope": (
            "com.diameterpay.api/end-users.read "
            "com.diameterpay.api/end-users.write "
            "com.diameterpay.api/payments.read "
            "com.diameterpay.api/payments.write"
        ),
    },
)
response.raise_for_status()
data = response.json()
access_token = data["access_token"]
```

```javascript Node.js theme={null}
const params = new URLSearchParams({
  grant_type: "client_credentials",
  client_id: process.env.DIAMETER_CLIENT_ID,
  client_secret: process.env.DIAMETER_CLIENT_SECRET,
  scope: [
    "com.diameterpay.api/end-users.read",
    "com.diameterpay.api/end-users.write",
    "com.diameterpay.api/payments.read",
    "com.diameterpay.api/payments.write",
  ].join(" "),
});

const res = await fetch(
  "https://auth.sandbox.diameterpay.com/oauth2/token",
  {
    method: "POST",
    headers: { "Content-Type": "application/x-www-form-urlencoded" },
    body: params.toString(),
  }
);

if (!res.ok) throw new Error(`Token request failed: ${res.status}`);
const { access_token, expires_in } = await res.json();
```

## Token Response

A successful request returns a JSON object with three fields.

```json theme={null}
{
  "access_token": "eyJraWQiOiJzYW5kYm94LTIwMjQiLCJhbGciOiJSUzI1NiJ9.eyJzdWIiOiJZT1VSX0NMSUVOVF9JRCIsInNjb3BlIjoiY29tLmRpYW1ldGVycGF5LmFwaVwvZW5kLXVzZXJzLndyaXRlIiwiZXhwIjoxNjk5ODMxMjAwfQ.SIGNATURE",
  "token_type": "bearer",
  "expires_in": 600
}
```

| Field | Type | Description |
| - | - | - |
| `access_token` | string | The Bearer token to include in subsequent API requests. |
| `token_type` | string | Always `bearer`. |
| `expires_in` | integer | Seconds until the token expires. Always `600`. |

## Using the Token

Attach the token to every API request in the `Authorization` header using the `Bearer` scheme.

```bash cURL theme={null}
curl -X GET https://api.sandbox.diameterpay.com/v1/tenants/self \
  -H "Authorization: Bearer YOUR_ACCESS_TOKEN"
```

```python Python theme={null}
import requests

headers = {"Authorization": f"Bearer {access_token}"}
resp = requests.get(
    "https://api.sandbox.diameterpay.com/v1/tenants/self",
    headers=headers,
)
resp.raise_for_status()
print(resp.json())
```

```javascript Node.js theme={null}
const resp = await fetch(
  "https://api.sandbox.diameterpay.com/v1/tenants/self",
  {
    headers: {
      Authorization: `Bearer ${access_token}`,
    },
  }
);
const tenant = await resp.json();
```

## Scopes

Scopes control which API resources a token can access. Request only the scopes your integration actually needs — this limits the blast radius if a token is ever leaked.

> [!NOTE]
>
> Your Tenant may not have all scopes enabled by default. Contact [support@diameterpay.com](mailto:support@diameterpay.com) if you receive a `403 Forbidden` response indicating a missing scope that you expect to have access to.

| Scope | Description |
| - | - |
| `com.diameterpay.api/accounts.read` | Read account information |
| `com.diameterpay.api/accounts.write` | Create or update accounts |
| `com.diameterpay.api/end-users.read` | Retrieve end-user data |
| `com.diameterpay.api/end-users.write` | Create or update end users |
| `com.diameterpay.api/payments.read` | Retrieve payment details |
| `com.diameterpay.api/payments.write` | Create or modify payments |
| `com.diameterpay.api/counter-parties.read` | Read counterparty information |
| `com.diameterpay.api/counter-parties.write` | Create or update counterparties |
| `com.diameterpay.api/products.read` | View available products |
| `com.diameterpay.api/webhooks.read` | Read webhook configurations |
| `com.diameterpay.api/webhooks.write` | Register or update webhooks |
| `com.diameterpay.api/webhooks.delete` | Delete webhook subscriptions |
| `com.diameterpay.api/tenants.read` | Read tenant configuration |

> [!TIP]
>
> Follow the **principle of least privilege**: a service that only reads payment status only needs `payments.read`. A service that creates end users needs `end-users.write`. Separate long-running background services from write-intensive services and issue each a token with the minimum required scopes.

## Token Expiry and Rotation

Tokens expire after **10 minutes**. Diameter does not support refresh tokens in the Client Credentials flow. To maintain uninterrupted access, proactively request a new token before the current one expires.

The recommended pattern is to fetch a fresh token at startup and cache it in memory with a timer that triggers a re-fetch at the 9-minute mark (giving a 60-second buffer before the 10-minute expiry):

```python Python — token caching with proactive refresh theme={null}
import time
import threading
import requests

class DiameterTokenClient:
    def __init__(self, client_id: str, client_secret: str, scopes: list[str]):
        self._client_id = client_id
        self._client_secret = client_secret
        self._scopes = scopes
        self._token: str | None = None
        self._lock = threading.Lock()
        self._refresh()

    def _refresh(self):
        resp = requests.post(
            "https://auth.sandbox.diameterpay.com/oauth2/token",
            data={
                "grant_type": "client_credentials",
                "client_id": self._client_id,
                "client_secret": self._client_secret,
                "scope": " ".join(self._scopes),
            },
        )
        resp.raise_for_status()
        data = resp.json()
        with self._lock:
            self._token = data["access_token"]
        # Schedule refresh 60 seconds before expiry
        refresh_in = data["expires_in"] - 60
        threading.Timer(refresh_in, self._refresh).start()

    @property
    def token(self) -> str:
        with self._lock:
            return self._token
```

## Deprecated JWT Authentication

Diameter previously supported direct JWT-based authentication where tokens were signed and passed in a custom header. That mechanism still functions for tenants that have not yet migrated, but **all new integrations must use OAuth 2.0 Client Credentials**. The legacy JWT method will be removed in a future major version. If you are currently using it, contact [support@diameterpay.com](mailto:support@diameterpay.com) to plan your migration.

## Error Handling

Authentication errors return standard HTTP status codes with a JSON error body.

| Status | Error | Cause | Resolution |
| - | - | - | - |
| `401 Unauthorized` | `invalid_token` | The Bearer token is missing, malformed, or expired. | Request a new token and retry the request. |
| `401 Unauthorized` | `invalid_client` | The `client_id` or `client_secret` is incorrect. | Verify your credentials and ensure you are hitting the correct environment endpoint. |
| `403 Forbidden` | `insufficient_scope` | The token does not include the scope required for this endpoint. | Request a new token with the required scope, or contact support to have it enabled on your Tenant. |

```json 401 Unauthorized — expired token theme={null}
{
  "error": "invalid_token",
  "error_description": "The access token expired at 2024-11-01T10:43:00Z."
}
```

```json 403 Forbidden — missing scope theme={null}
{
  "error": "insufficient_scope",
  "error_description": "Token missing required scope: com.diameterpay.api/payments.write"
}
```

> [!WARNING]
>
> Never retry a `403 Forbidden` response in a tight loop. A missing scope cannot be fixed by requesting a new token alone — the scope must either be included when requesting the token or enabled on your Tenant by support.


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