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

# SDKs

> Typed clients for the Coval API in TypeScript and Python, generated from the public OpenAPI specs.

## Overview

Coval ships typed SDKs for TypeScript and Python. Both are generated from the public OpenAPI specs that power this [API reference](/api-reference/v1/introduction), so the surface area stays in sync with what the API accepts.

<CardGroup cols={2}>
  <Card title="TypeScript" icon="js" href="https://www.npmjs.com/package/@coval/sdk">
    `@coval/sdk` on npm.
  </Card>

  <Card title="Python" icon="python" href="https://pypi.org/project/coval-sdk/">
    `coval-sdk` on PyPI.
  </Card>
</CardGroup>

<Info>
  Both SDKs are MIT-licensed and live in [coval-ai/coval-examples](https://github.com/coval-ai/coval-examples).
</Info>

## Install

<CodeGroup>
  ```bash TypeScript theme={null}
  npm install @coval/sdk
  ```

  ```bash Python theme={null}
  pip install coval-sdk
  ```
</CodeGroup>

<Accordion title="Install from source">
  To install directly from the source repo:

  <CodeGroup>
    ```bash TypeScript theme={null}
    # npm has no native "install from a git subdirectory" syntax, so clone and install:
    git clone https://github.com/coval-ai/coval-examples.git
    cd coval-examples/typescript-sdk
    npm install
    npm run build
    npm link        # or: npm pack && npm install ../coval-examples/typescript-sdk/coval-sdk-*.tgz
    ```

    ```bash Python theme={null}
    pip install "git+https://github.com/coval-ai/coval-examples.git#subdirectory=python-sdk"
    ```
  </CodeGroup>
</Accordion>

## Quick start

<CodeGroup>
  ```ts TypeScript theme={null}
  import { CovalClient } from '@coval/sdk';

  const coval = new CovalClient({
    apiKey: process.env.COVAL_API_KEY!,
  });

  const page = await coval.agents.listAgents({ pageSize: 50 });
  for (const agent of page.agents ?? []) {
    console.log(agent.id, agent.display_name);
  }
  ```

  ```python Python theme={null}
  import os
  from coval_sdk import AgentsApi, ApiClient, Configuration

  config = Configuration(host="https://api.coval.dev")

  with ApiClient(config) as client:
      client.set_default_header("x-api-key", os.environ["COVAL_API_KEY"])
      agents = AgentsApi(client).list_agents(page_size=50)
      for a in agents.agents:
          print(a.id, a.display_name)
  ```
</CodeGroup>

The TypeScript client exposes every v1 API resource as a property on `CovalClient`: `coval.agents`, `coval.conversations`, `coval.simulations`, `coval.traces`, `coval.metrics`, and so on (22 resources total). The Python client is split into one `*Api` class per resource (`AgentsApi`, `ConversationsApi`, `SimulationsApi`, etc.) constructed against a shared `ApiClient`.

## Auth

The Coval API gateway requires the header `x-api-key` in **lowercase**. Uppercase `X-API-Key` is rejected with `Missing API Key`.

<CodeGroup>
  ```ts TypeScript theme={null}
  // Handled automatically. The CovalClient middleware sets the header for you.
  const coval = new CovalClient({ apiKey: process.env.COVAL_API_KEY! });
  ```

  ```python Python theme={null}
  # Set the header directly on the ApiClient. Do not rely on per-scheme
  # config.api_key["ApiKeyAuth"]; the bundled spec splits the security scheme
  # per tag (Coval_Agents_API_ApiKeyAuth, Coval_Conversations_API_ApiKeyAuth,
  # etc.), so setting a default header is the pattern that works
  # across all 22 resources.
  client.set_default_header("x-api-key", os.environ["COVAL_API_KEY"])
  ```
</CodeGroup>

<Warning>
  If you see `{"message": "Missing API Key"}` from the gateway, check that the header name is lowercase. The TypeScript SDK takes care of this for you; the Python SDK leaves it to your `set_default_header` call.
</Warning>

## Errors

<CodeGroup>
  ```ts TypeScript theme={null}
  import { CovalApiError, CovalNetworkError, CovalClient } from '@coval/sdk';

  const coval = new CovalClient({ apiKey: process.env.COVAL_API_KEY! });

  try {
    await coval.agents.getAgent({ agentId: 'ag_does_not_exist' });
  } catch (err) {
    if (err instanceof CovalApiError) {
      console.error(`HTTP ${err.status} ${err.code ?? ''}: ${err.message}`);
      console.error(`  request_id: ${err.requestId ?? '-'}`);
      console.error(`  body: ${JSON.stringify(err.body)}`);
    } else if (err instanceof CovalNetworkError) {
      console.error(`Network failure after ${err.attempts} attempt(s): ${err.message}`);
    } else {
      throw err;
    }
  }
  ```

  ```python Python theme={null}
  from coval_sdk.exceptions import ApiException

  try:
      AgentsApi(client).get_agent(agent_id="ag_does_not_exist")
  except ApiException as e:
      print(f"HTTP {e.status}: {e.reason}")
      print(f"  body: {e.body}")
  ```
</CodeGroup>

<Note>
  **`CovalApiError` shape**: `status`, `code`, `message`, `requestId`, `body`, `url`, `method`. The `requestId` is sourced from `x-request-id`, `x-amzn-requestid`, or the response body in that order. Include it when you file a support ticket.
</Note>

### Python: `--raw` fallback for legacy data

The Python client uses strict pydantic v2 models for every response, including regex patterns on IDs (e.g., `agent_id` must match `^[A-Za-z0-9]{22}$`). If your org has historical data that predates the current spec, for example an agent created when IDs had a different shape, pydantic will raise a `ValidationError` mid-stream and you will lose the rest of the page.

The fix is to call the `*_without_preload_content` variant of any method and parse the response yourself:

```python theme={null}
import json

resp = AgentsApi(client).list_agents_without_preload_content(page_size=50)
body = json.loads(resp.data.decode("utf-8"))
for a in body["agents"]:
    print(a.get("id"), a.get("display_name"))
```

Every generated method has a `_without_preload_content` sibling. The TypeScript SDK does not have this issue: its types are emitted as `interface` declarations with no runtime validation, so unknown fields pass through.

## Retries

The TypeScript SDK retries with exponential backoff and jitter on transient failures. Defaults:

| Setting            | Default                        | Notes                                                 |
| ------------------ | ------------------------------ | ----------------------------------------------------- |
| Max attempts       | `3`                            | Includes the first attempt.                           |
| Base delay         | `200ms`                        | Effective delay = `base * 2^(attempt-1)` plus jitter. |
| Max delay          | `5000ms`                       | Cap on any single backoff.                            |
| Retryable statuses | `408, 429, 500, 502, 503, 504` | Plus network and transport errors.                    |
| `Retry-After`      | Honored                        | Numeric seconds or HTTP-date both accepted.           |

```ts theme={null}
// Override
const coval = new CovalClient({
  apiKey: process.env.COVAL_API_KEY!,
  retry: { maxAttempts: 5, baseDelayMs: 100, maxDelayMs: 10_000 },
});

// Disable entirely
const noRetry = new CovalClient({
  apiKey: process.env.COVAL_API_KEY!,
  retry: false,
});
```

When all attempts are exhausted on a transport error, the client throws `CovalNetworkError`. When a non-retryable status (e.g., `400`, `404`) comes back, it falls through to the normal error path and raises `CovalApiError` immediately.

For Python, wrap your own calls with `tenacity` or a similar library.

## Pagination

The Coval v1 API uses `next_page_token`-style pagination. The TypeScript SDK ships an async iterator that handles the loop for you:

```ts theme={null}
import { CovalClient, paginate, collectAll } from '@coval/sdk';

const coval = new CovalClient({ apiKey: process.env.COVAL_API_KEY! });

// Iterate lazily across pages
for await (const agent of paginate({
  fetchPage: (pageToken) => coval.agents.listAgents({ pageToken, pageSize: 50 }),
  items: (page) => page.agents,
  nextToken: (page) => page.next_page_token,
})) {
  console.log(agent.id, agent.display_name);
}

// Or buffer everything into an array
const allAgents = await collectAll({
  fetchPage: (pageToken) => coval.agents.listAgents({ pageToken, pageSize: 50 }),
  items: (page) => page.agents,
  nextToken: (page) => page.next_page_token,
});
```

`paginate` and `collectAll` work with any list endpoint. Provide `fetchPage`, `items`, and `nextToken` callbacks for the method you are calling.

<Tip>
  Pass `maxPages` to cap iteration. Useful when you want a quick preview of a large list without hitting the full backlog.
</Tip>

For Python, use a `while` loop on `next_page_token`. See the [`list_agents.py` example](https://github.com/coval-ai/coval-examples/blob/main/python-sdk/examples/list_agents.py) for the shape.

## Customization

### Base URL

Point the client at staging, a self-hosted gateway, or a regional endpoint:

<CodeGroup>
  ```ts TypeScript theme={null}
  const coval = new CovalClient({
    apiKey: process.env.COVAL_API_KEY!,
    baseUrl: 'https://staging.api.coval.dev',
  });
  ```

  ```python Python theme={null}
  config = Configuration(host="https://staging.api.coval.dev")
  ```
</CodeGroup>

### Middleware (TypeScript)

Inject request and response middleware after the auth and error layers. Useful for logging, OpenTelemetry spans, or attaching request IDs you control:

```ts theme={null}
const coval = new CovalClient({
  apiKey: process.env.COVAL_API_KEY!,
  middleware: [
    {
      async pre(ctx) {
        console.log(`-> ${ctx.init.method} ${ctx.url}`);
      },
      async post(ctx) {
        console.log(`<- ${ctx.response.status} ${ctx.url}`);
      },
    },
  ],
});
```

### Custom fetch

Swap the underlying `fetch` implementation. For example, to use `undici` with keepalive in a long-running Node process, or to stub network calls in tests:

```ts theme={null}
import { fetch as undiciFetch, Agent } from 'undici';

const dispatcher = new Agent({ keepAliveTimeout: 60_000 });
const coval = new CovalClient({
  apiKey: process.env.COVAL_API_KEY!,
  fetch: ((url, init) => undiciFetch(url, { ...init, dispatcher })) as typeof fetch,
});
```

## Versioning

Both SDKs are generated from the same OpenAPI specs that power this API reference.

* **Minor version bumps** (`0.2.x` → `0.3.0`) reflect spec evolution: new endpoints, new optional fields, additional response shapes. Generally safe to upgrade.
* **Major version bumps** (`0.x` → `1.0`) signal breaking changes in the client surface (`CovalClient` options, error class shapes, pagination helper signatures).

To regenerate locally:

```bash theme={null}
git clone https://github.com/coval-ai/coval-examples.git
cd coval-examples
node scripts/bundle-spec.mjs       # pull and bundle the latest specs
bash scripts/generate-sdks.sh      # regenerate both clients
```

## Going deeper

<CardGroup cols={2}>
  <Card title="Source repo" icon="github" href="https://github.com/coval-ai/coval-examples">
    Full SDK source, examples folder, and the generation scripts. Open issues here.
  </Card>

  <Card title="TypeScript examples" icon="js" href="https://github.com/coval-ai/coval-examples/tree/main/typescript-sdk/examples">
    `list-agents.ts`, `submit-conversation.ts`. Runnable end-to-end samples.
  </Card>

  <Card title="Python examples" icon="python" href="https://github.com/coval-ai/coval-examples/tree/main/python-sdk/examples">
    `list_agents.py` with `--raw` fallback pattern for legacy data.
  </Card>

  <Card title="OpenAPI specs" icon="file-code" href="/api-reference/v1/introduction">
    The specs that power both SDKs are published at `https://api.coval.dev/v1/openapi`.
  </Card>

  <Card title="API keys" icon="key" href="/guides/api-keys">
    How to mint and rotate API keys from the dashboard.
  </Card>

  <Card title="CLI" icon="terminal" href="/cli/agents">
    Prefer the shell? `coval-cli` covers the same surface area.
  </Card>
</CardGroup>
