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

# Authentication

> Exchange a project API key for an OAuth 2.0 bearer token and authorize every request.

Every Infra API endpoint (except token issuance itself) is protected by an OAuth 2.0
**bearer token**. You obtain a token by presenting your project **API key** to
Auth-Edge using the `client_credentials` grant, then send the returned
`access_token` on each subsequent request.

## Getting an API key

Convexity Infra is a backend service with no self-service signup: API keys are issued
by the Convexity team. Email
[infra@withconvexity.com](mailto:infra@withconvexity.com) with your business details
to register. You'll receive a **test** key to integrate against and a **live** key
(`sk_live_...`) for production traffic.

<Note>
  Treat API keys like passwords: never embed them in client-side code or commit them to
  source control. If a key is exposed, email
  [infra@withconvexity.com](mailto:infra@withconvexity.com) to request a rotation
  immediately.
</Note>

<Steps>
  <Step title="Present your API key">
    Call `POST /v1/oauth/token` with `grant_type=client_credentials` and your API key.
  </Step>

  <Step title="Receive a short-lived token">
    Auth-Edge returns an `access_token` (a signed JWT) and its `expires_in` lifetime.
  </Step>

  <Step title="Authorize requests">
    Send `Authorization: Bearer <access_token>` on every API call until the token expires,
    then mint a new one.
  </Step>
</Steps>

## Obtaining a token

`POST /v1/oauth/token`

No `Authorization` header is required. Send your API key as `client_secret` in the JSON body:

```bash theme={null}
curl -X POST https://dev.api.withconvexity.com/v1/oauth/token \
  -H "Content-Type: application/json" \
  -d '{
    "grant_type": "client_credentials",
    "client_secret": "sk_live_<your_secret_key>"
  }'
```

### Request body

| Field           | Type     | Required | Description                                                                                                |
| --------------- | -------- | -------- | ---------------------------------------------------------------------------------------------------------- |
| `grant_type`    | `string` | Yes      | Must be `client_credentials`.                                                                              |
| `client_secret` | `string` | Yes      | Your API key.                                                                                              |
| `scope`         | `string` | No       | Space-separated scopes to narrow the token (RFC 6749 §3.3), up to 500 chars. Omit for a full-access token. |

### Response: `200 OK`

```json theme={null}
{
  "access_token": "eyJhbGciOiJSUzI1NiIsImtpZCI6...",
  "token_type": "Bearer",
  "expires_in": 600,
  "scope": "indexer:read"
}
```

| Field          | Type     | Description                                              |
| -------------- | -------- | -------------------------------------------------------- |
| `access_token` | `string` | Signed JWT to send as a bearer token.                    |
| `token_type`   | `string` | Always `Bearer`.                                         |
| `expires_in`   | `number` | Token lifetime in seconds (e.g. `600` = 10 minutes).     |
| `scope`        | `string` | Granted scope. Omitted for unscoped, full-access tokens. |

<Tip>
  **Token claims**

  The JWT encodes your `businessId`, `keyId`, the environment (`live` / `test`),
  the granted `scope`, and an `aud` (audience). Downstream services read these
  claims to enforce project context and capabilities; you do not need to send them
  yourself.
</Tip>

## Using the token

Send the access token in the `Authorization` header on every request:

```bash theme={null}
curl https://dev.api.withconvexity.com/v1/products \
  -H "Authorization: Bearer eyJhbGciOiJSUzI1NiIsImtpZCI6..." \
  -H "Accept: application/json"
```

## Token errors

Token issuance returns standard OAuth 2.0 error bodies (RFC 6749 §5.2):

```json theme={null}
{
  "error": "invalid_client",
  "error_description": "Missing API key. Supply client_secret in the body."
}
```

| Status | `error`                   | Cause                                               |
| ------ | ------------------------- | --------------------------------------------------- |
| `400`  | `invalid_request`         | Malformed body or missing `grant_type`.             |
| `401`  | `invalid_client`          | Missing, malformed, or unrecognized API key.        |
| `403`  | `unauthorized_client`     | The key is not allowed to mint the requested token. |
| `503`  | `temporarily_unavailable` | The signing/key service is temporarily unavailable. |
| `500`  | `server_error`            | Unexpected failure.                                 |

<Warning>
  **Live keys for production**

  Business endpoints reject **test** tokens with `403: Test tokens are not allowed,
      please use your live api key.` Mint tokens from your `sk_live_...` key for
  production traffic.
</Warning>

## Scopes & capabilities

Authorization is layered:

* **Scopes** narrow what a token may do at a coarse level (for example `indexer:read`).
* **Capabilities** are fine-grained permissions checked per endpoint. The Blockchain Events
  service, for example, gates each operation on a capability such as
  `indexer.subscribe.evm` or `indexer.history.read`. A token missing the required
  capability receives `403`. Capabilities are granted by the product/plan attached to
  your project. See [Products](/api-reference/products/list-products) and
  [Subscriptions](/api-reference/subscriptions/list-plans).

Rate limiting on the token endpoint is **30 requests per minute per IP**.
