# SDKs

> The subsidia-sdk TypeScript client for the platform APIs, and how to use the official OpenAI and Anthropic SDKs, or plain fetch, for everything else.

There is no single Subsidia client for everything, and that is deliberate. The part of the API that most developers use, the Gateway, is OpenAI and Anthropic compatible, so the **official OpenAI and Anthropic SDKs are the SDKs** for it, in every language they support. A small TypeScript client, `subsidia-sdk`, covers the platform APIs (knowledge, agents, conversations, completions, webhooks). Everything else is plain HTTP.

| You want to | Use | Language |
| --- | --- | --- |
| Call a model, stream, use tools | The official **OpenAI** or **Anthropic** SDK pointed at the Gateway | Any language those SDKs support |
| Upload documents, query Cortex, read facts and health | `subsidia-sdk`, or `fetch` / `requests` | TypeScript, or any HTTP client |
| Manage agents, memory, conversations, webhooks | `subsidia-sdk`, or HTTP | TypeScript, or any HTTP client |
| Fetch and verify proof certificates, run the Vérificateur, read the Registre | HTTP (not in `subsidia-sdk`) | Any |

## Gateway: the OpenAI and Anthropic SDKs

Change the base URL and the key, keep everything else. To read the proof id, use the SDK's raw-response helper (`withResponse()` in TypeScript, `with_raw_response` in Python) and take the `x-pulse-proof` header. More on streaming, tools and Claude Code in [Gateway](https://dev.subsidia.protypa.fr/docs/gateway.md).

_OpenAI (TypeScript)_

```typescript
import OpenAI from 'openai'

const client = new OpenAI({
  baseURL: 'https://api.subsidia.protypa.fr/v1',
  apiKey: process.env.SUBSIDIA_API_KEY, // sk_live_...
})

const { data, response } = await client.chat.completions
  .create({ model: 'pulse-auto', messages: [{ role: 'user', content: 'Hello' }] })
  .withResponse()

console.log(data.choices[0].message.content, response.headers.get('x-pulse-proof'))
```

_OpenAI (Python)_

```python
import os
from openai import OpenAI

client = OpenAI(
    base_url="https://api.subsidia.protypa.fr/v1",
    api_key=os.environ["SUBSIDIA_API_KEY"],
)

raw = client.chat.completions.with_raw_response.create(
    model="pulse-auto",
    messages=[{"role": "user", "content": "Hello"}],
)
completion = raw.parse()
print(completion.choices[0].message.content, raw.headers.get("x-pulse-proof"))
```

_Anthropic (TypeScript)_

```typescript
import Anthropic from '@anthropic-ai/sdk'

const client = new Anthropic({
  baseURL: 'https://api.subsidia.protypa.fr', // host only: the SDK adds /v1/messages
  apiKey: process.env.SUBSIDIA_API_KEY,
})

const { data, response } = await client.messages
  .create({ model: 'pulse-auto', max_tokens: 1024, messages: [{ role: 'user', content: 'Hello' }] })
  .withResponse()

console.log(data.content, response.headers.get('x-pulse-proof'))
```

_Anthropic (Python)_

```python
import os
from anthropic import Anthropic

client = Anthropic(
    base_url="https://api.subsidia.protypa.fr",
    api_key=os.environ["SUBSIDIA_API_KEY"],
)

raw = client.messages.with_raw_response.create(
    model="pulse-auto",
    max_tokens=1024,
    messages=[{"role": "user", "content": "Hello"}],
)
message = raw.parse()
print(message.content, raw.headers.get("x-pulse-proof"))
```

> **TIP: Frameworks follow the same rule**
> LangChain, LlamaIndex, the Vercel AI SDK, n8n and similar tools all accept a base URL on their OpenAI or Anthropic provider. Set it to `https://api.subsidia.protypa.fr/v1` (OpenAI style) or `https://api.subsidia.protypa.fr` (Anthropic style) and use `pulse-auto`, `agent:<slug>` or any [address](https://dev.subsidia.protypa.fr/docs/model-addressing.md) as the model name. Your agents appear in model pickers through `GET /v1/models`.

## The TypeScript client: subsidia-sdk

`subsidia-sdk` is a thin, zero-dependency TypeScript client built on the global `fetch` (Node 18 or later, Deno, Bun, modern browsers). It lives in the `sdk/` folder of the Subsidia repository, version `0.1.0`, and is installed with `npm install subsidia-sdk`. If your registry does not resolve it yet, build it from the `sdk/` folder (`npm install && npm run build`) and `npm link` it. It sends the key in the `x-api-key` header.

> **WARNING: Coverage**
> The client is hand-written and covers the namespaces below only. It does **not** wrap the Gateway, the proof routes, `/v1/verify`, the Registre, `/v1/tools` or Reflex; call those with the official SDKs or HTTP. Its `workspaceId` option sends an `x-workspace-id` header that API-key authentication ignores: a key already belongs to one workspace.

**Configure and use**

```typescript
import { SubsidiaSDK, SubsidiaSDKError } from 'subsidia-sdk'

const subsidia = new SubsidiaSDK({
  apiKey: process.env.SUBSIDIA_API_KEY!, // required
  // baseUrl: 'https://api.subsidia.protypa.fr', // default; override for an on-premise host
})

// Ingest text, wait until it is ready, then query it
const { id } = await subsidia.knowledge.ingest({
  name: 'Lease summary',
  content: 'The tenant may terminate the lease with six months written notice.',
})

let status = 'processing'
while (status !== 'ready' && status !== 'failed') {
  await new Promise((r) => setTimeout(r, 3000))
  status = (await subsidia.knowledge.getSource(id)).source.status
}

const { matches, trace } = await subsidia.knowledge.query({ query: 'Notice period to terminate?', topK: 5 })

try {
  await subsidia.agents.list()
} catch (err) {
  if (err instanceof SubsidiaSDKError) console.error(err.status, err.body)
  else throw err
}
```

| Namespace | Methods | Routes |
| --- | --- | --- |
| `knowledge` | `ingest`, `listSources`, `getSource`, `deleteSource`, `query`, `listFacts`, `listContradictions`, `resolveContradiction`, `health`, `replayHealth` | `/v1/knowledge-sources*`, `/v1/knowledge/*`. See [Knowledge sources](https://dev.subsidia.protypa.fr/docs/knowledge-sources.md), [Knowledge query](https://dev.subsidia.protypa.fr/docs/knowledge-query.md), [Knowledge insights](https://dev.subsidia.protypa.fr/docs/knowledge-insights.md). |
| `agents`, `agents.memory` | `list`, `create`, `update`, `delete`; memory `list`, `create`, `delete` | `/v1/agents*`. See [Agents](https://dev.subsidia.protypa.fr/docs/agents.md) and [Agent memory](https://dev.subsidia.protypa.fr/docs/agent-memory.md). |
| `conversations` | `list`, `create`, `get`, `sendMessage`, `delete` | `/v1/conversations*`. See [Conversations](https://dev.subsidia.protypa.fr/docs/conversations.md). |
| `ai` | `complete`, `listExtractedData`, `getExtractedData`, `deleteExtractedData` | `/v1/ai/*`. See [Completions](https://dev.subsidia.protypa.fr/docs/completions.md). |
| `webhooks` | `list`, `create`, `get`, `update`, `delete`, `deliveries`, `test` | `/v1/webhooks*`. See [Webhooks](https://dev.subsidia.protypa.fr/docs/webhooks.md). |

**Errors.** Every non-2xx response throws `SubsidiaSDKError`, with `status` (the HTTP status) and `body` (the parsed JSON body, or `null`). The `body` keeps the `code` field, so you can branch on `err.body.code` exactly as described in [Errors](https://dev.subsidia.protypa.fr/docs/errors.md). The client does not retry; add a retry policy around 429 and 5xx yourself.

## Plain HTTP

The API is ordinary JSON over HTTPS, so a few lines of `fetch` or `requests` are enough for any route, and for any language without an SDK. A minimal typed wrapper that works for every route, including the ones the SDK does not cover:

_TypeScript_

```typescript
const BASE = 'https://api.subsidia.protypa.fr'

export async function subsidia<T>(method: string, path: string, body?: unknown): Promise<{ data: T; headers: Headers }> {
  const res = await fetch(BASE + path, {
    method,
    headers: {
      Authorization: 'Bearer ' + process.env.SUBSIDIA_API_KEY,
      ...(body ? { 'Content-Type': 'application/json' } : {}),
    },
    body: body ? JSON.stringify(body) : undefined,
  })
  if (!res.ok) throw Object.assign(new Error('Subsidia ' + res.status), { status: res.status, body: await res.json().catch(() => null) })
  return { data: (await res.json()) as T, headers: res.headers }
}

// e.g. verify a certificate
const { data } = await subsidia<{ valid: boolean }>('GET', '/v1/proofs/' + proofId + '/verify')
```

_Python_

```python
import os, requests

BASE = "https://api.subsidia.protypa.fr"
session = requests.Session()
session.headers["Authorization"] = "Bearer " + os.environ["SUBSIDIA_API_KEY"]

def subsidia(method, path, **kwargs):
    res = session.request(method, BASE + path, **kwargs)
    res.raise_for_status()
    return res

# e.g. verify a certificate
print(subsidia("GET", "/v1/proofs/" + proof_id + "/verify").json()["valid"])
```

## OpenAPI

A machine-readable snapshot of the routes is served at `https://dev.subsidia.protypa.fr/openapi.json`. It is generated from the server and can be fed to a code generator for languages the SDK does not cover. Treat the written pages as the reference for behaviour and error handling, since a schema does not capture those.

**Is there a Python or Go SDK?**

Not from Subsidia. For the Gateway use the official OpenAI or Anthropic SDK in your language; for the platform APIs use `requests` or `net/http`, optionally with a client generated from the OpenAPI snapshot.

**Will the OpenAI SDK break on the extra fields in responses?**

No. Responses carry an additional `pulse` block next to the standard fields, and the SDKs ignore fields they do not know. The block holds the proof id, the provider and the number of values masked.
