# Quickstart

> Create a key, make your first attested Gateway call, upload a document, query it, and read the proof certificate.

Four short steps take you from nothing to a signed answer grounded in your own document. Every example works as written once `SUBSIDIA_API_KEY` is set. You need a Subsidia account with an active plan or trial; the questions you ask here count against it (see [Rate limits and quotas](https://dev.subsidia.protypa.fr/docs/rate-limits.md)).

## 1. Create an API key

Open the Gateway page of the Subsidia console at `https://subsidia.protypa.fr/console/gateway` and create a key. For this guide give it the `engine` scope (Gateway and proofs) and the `knowledge` scope (documents and queries). The full key, starting with `sk_live_`, is shown **once**: copy it immediately.

A key that carries any of these capability scopes can reach only the route families it names, which is what you want for anything that leaves your laptop. Scopes, ceilings and read-only keys are covered in [Authentication](https://dev.subsidia.protypa.fr/docs/authentication.md).

_bash_

```bash
export SUBSIDIA_API_KEY="sk_live_..."
```

_PowerShell_

```powershell
$env:SUBSIDIA_API_KEY = "sk_live_..."
```

> **WARNING: Keep the key out of source control**
> Subsidia stores only a hash of the key, so a lost key cannot be recovered, only replaced. Load it from an environment variable or a secret manager, never from a committed file.

## 2. Make your first Gateway call

The Gateway is OpenAI compatible, so the simplest call is a chat completion. `pulse-auto` lets Synapse choose the model for the task. The response carries an `x-pulse-proof` header: the id of the signed certificate for this exact exchange. Keep it.

_curl_

```bash
curl -i https://api.subsidia.protypa.fr/v1/chat/completions \
  -H "Authorization: Bearer $SUBSIDIA_API_KEY" \
  -H "Content-Type: application/json" \
  -d '{
    "model": "pulse-auto",
    "messages": [
      {"role": "user", "content": "In one sentence, what is a hash chain?"}
    ]
  }'
```

_TypeScript_

```typescript
const res = await fetch('https://api.subsidia.protypa.fr/v1/chat/completions', {
  method: 'POST',
  headers: {
    Authorization: 'Bearer ' + process.env.SUBSIDIA_API_KEY,
    'Content-Type': 'application/json',
  },
  body: JSON.stringify({
    model: 'pulse-auto',
    messages: [{ role: 'user', content: 'In one sentence, what is a hash chain?' }],
  }),
})

const proofId = res.headers.get('x-pulse-proof')
const completion = await res.json()
console.log(completion.choices[0].message.content)
console.log('proof:', proofId)
```

_Python_

```python
import os, requests

res = requests.post(
    "https://api.subsidia.protypa.fr/v1/chat/completions",
    headers={"Authorization": "Bearer " + os.environ["SUBSIDIA_API_KEY"]},
    json={
        "model": "pulse-auto",
        "messages": [{"role": "user", "content": "In one sentence, what is a hash chain?"}],
    },
)
proof_id = res.headers["x-pulse-proof"]
print(res.json()["choices"][0]["message"]["content"])
print("proof:", proof_id)
```

> **TIP: Already using an SDK?**
> You do not need `fetch`. Point the official OpenAI or Anthropic SDK at the Gateway and keep the rest of your code: see [SDKs](https://dev.subsidia.protypa.fr/docs/sdk.md) and [Gateway](https://dev.subsidia.protypa.fr/docs/gateway.md).

## 3. Upload a document and query it

Cortex is the knowledge side of the engine. Upload a file (PDF, DOCX, XLSX, PPTX, CSV, EML, or an image for OCR, up to 25 MB) as `multipart/form-data`. Ingestion is asynchronous: the upload returns a source `id` and a `status`, and the document is searchable once the status is `ready`. Uploading the same content twice returns `duplicate` instead of ingesting it again. Details in [Knowledge sources](https://dev.subsidia.protypa.fr/docs/knowledge-sources.md).

**Upload**

_curl_

```bash
curl https://api.subsidia.protypa.fr/v1/knowledge-sources/upload \
  -H "Authorization: Bearer $SUBSIDIA_API_KEY" \
  -F "file=@./bail-commercial.pdf" \
  -F "name=Bail commercial 2024"
```

_TypeScript_

```typescript
import { readFile } from 'node:fs/promises'

const form = new FormData()
form.append('file', new Blob([await readFile('./bail-commercial.pdf')]), 'bail-commercial.pdf')
form.append('name', 'Bail commercial 2024')

const res = await fetch('https://api.subsidia.protypa.fr/v1/knowledge-sources/upload', {
  method: 'POST',
  headers: { Authorization: 'Bearer ' + process.env.SUBSIDIA_API_KEY },
  body: form, // do not set Content-Type yourself: fetch adds the multipart boundary
})
const source = await res.json() // { id, status }
console.log(source)
```

_Python_

```python
import os, requests

with open("bail-commercial.pdf", "rb") as f:
    res = requests.post(
        "https://api.subsidia.protypa.fr/v1/knowledge-sources/upload",
        headers={"Authorization": "Bearer " + os.environ["SUBSIDIA_API_KEY"]},
        files={"file": ("bail-commercial.pdf", f)},
        data={"name": "Bail commercial 2024"},
    )
source = res.json()  # {"id": "...", "status": "..."}
print(source)
```

**Wait until the source is ready**

_curl_

```bash
curl https://api.subsidia.protypa.fr/v1/knowledge-sources/$SOURCE_ID \
  -H "Authorization: Bearer $SUBSIDIA_API_KEY"
# repeat every few seconds until "status" is "ready" ("failed" means ingestion did not complete)
```

_TypeScript_

```typescript
async function waitReady(id: string) {
  for (let i = 0; i < 120; i++) {
    const res = await fetch('https://api.subsidia.protypa.fr/v1/knowledge-sources/' + id, {
      headers: { Authorization: 'Bearer ' + process.env.SUBSIDIA_API_KEY },
    })
    const body = await res.json()
    const status = body.status ?? body.source?.status
    if (status === 'ready') return
    if (status === 'failed') throw new Error('ingestion failed')
    await new Promise((r) => setTimeout(r, 3000))
  }
  throw new Error('still ingesting')
}
```

_Python_

```python
import os, time, requests

def wait_ready(source_id):
    for _ in range(120):
        body = requests.get(
            "https://api.subsidia.protypa.fr/v1/knowledge-sources/" + source_id,
            headers={"Authorization": "Bearer " + os.environ["SUBSIDIA_API_KEY"]},
        ).json()
        status = body.get("status") or body.get("source", {}).get("status")
        if status == "ready":
            return
        if status == "failed":
            raise RuntimeError("ingestion failed")
        time.sleep(3)
    raise RuntimeError("still ingesting")
```

Now ask a question. `POST /v1/knowledge/query` runs the full retrieval pipeline and returns `matches` (the passages) and a `trace` that explains why each one matched. It does not generate prose: it is the retrieval half, for you to feed into your own prompt or to display as citations. See [Knowledge query](https://dev.subsidia.protypa.fr/docs/knowledge-query.md).

**Query**

_curl_

```bash
curl https://api.subsidia.protypa.fr/v1/knowledge/query \
  -H "Authorization: Bearer $SUBSIDIA_API_KEY" \
  -H "Content-Type: application/json" \
  -d '{"query": "What is the notice period to terminate the lease?", "topK": 5}'
```

_TypeScript_

```typescript
const res = await fetch('https://api.subsidia.protypa.fr/v1/knowledge/query', {
  method: 'POST',
  headers: {
    Authorization: 'Bearer ' + process.env.SUBSIDIA_API_KEY,
    'Content-Type': 'application/json',
  },
  body: JSON.stringify({ query: 'What is the notice period to terminate the lease?', topK: 5 }),
})
const { matches, trace } = await res.json()
console.log(matches.length, 'passages')
```

_Python_

```python
import os, requests

res = requests.post(
    "https://api.subsidia.protypa.fr/v1/knowledge/query",
    headers={"Authorization": "Bearer " + os.environ["SUBSIDIA_API_KEY"]},
    json={"query": "What is the notice period to terminate the lease?", "topK": 5},
)
data = res.json()
print(len(data["matches"]), "passages")
```

To get a **generated, grounded answer with a certificate** instead of raw passages, add `+cortex` to the model address. The Gateway retrieves the passages, writes the answer, and hashes the exact passages used into the certificate.

**A grounded, attested answer**

_curl_

```bash
curl -i https://api.subsidia.protypa.fr/v1/chat/completions \
  -H "Authorization: Bearer $SUBSIDIA_API_KEY" \
  -H "Content-Type: application/json" \
  -d '{
    "model": "pulse-auto+cortex",
    "messages": [{"role": "user", "content": "What is the notice period to terminate the lease?"}]
  }'
```

_TypeScript_

```typescript
const res = await fetch('https://api.subsidia.protypa.fr/v1/chat/completions', {
  method: 'POST',
  headers: {
    Authorization: 'Bearer ' + process.env.SUBSIDIA_API_KEY,
    'Content-Type': 'application/json',
  },
  body: JSON.stringify({
    model: 'pulse-auto+cortex',
    messages: [{ role: 'user', content: 'What is the notice period to terminate the lease?' }],
  }),
})
const proofId = res.headers.get('x-pulse-proof')
const completion = await res.json()
console.log(completion.choices[0].message.content, proofId)
```

_Python_

```python
import os, requests

res = requests.post(
    "https://api.subsidia.protypa.fr/v1/chat/completions",
    headers={"Authorization": "Bearer " + os.environ["SUBSIDIA_API_KEY"]},
    json={
        "model": "pulse-auto+cortex",
        "messages": [{"role": "user", "content": "What is the notice period to terminate the lease?"}],
    },
)
proof_id = res.headers["x-pulse-proof"]
print(res.json()["choices"][0]["message"]["content"], proof_id)
```

## 4. Read and verify the proof

The certificate commits, by SHA-256, to your request, to what actually left toward the model provider after masking, and to the response. For a `+cortex` call it also lists the passages the answer drew on, in a `grounding` array. It never contains the content itself. Fetch it with the id from `x-pulse-proof`, then ask the server to verify the signature and the chain link.

_curl_

```bash
# the certificate
curl https://api.subsidia.protypa.fr/v1/proofs/$PROOF_ID \
  -H "Authorization: Bearer $SUBSIDIA_API_KEY"

# server-side verification: signature_valid, chain_valid, valid
curl https://api.subsidia.protypa.fr/v1/proofs/$PROOF_ID/verify \
  -H "Authorization: Bearer $SUBSIDIA_API_KEY"
```

_TypeScript_

```typescript
const base = 'https://api.subsidia.protypa.fr/v1/proofs/' + proofId
const headers = { Authorization: 'Bearer ' + process.env.SUBSIDIA_API_KEY }

const certificate = await fetch(base, { headers }).then((r) => r.json())
const check = await fetch(base + '/verify', { headers }).then((r) => r.json())
console.log(check.valid, certificate.payload.grounding)
```

_Python_

```python
import os, requests

base = "https://api.subsidia.protypa.fr/v1/proofs/" + proof_id
headers = {"Authorization": "Bearer " + os.environ["SUBSIDIA_API_KEY"]}

certificate = requests.get(base, headers=headers).json()
check = requests.get(base + "/verify", headers=headers).json()
print(check["valid"], certificate["payload"].get("grounding"))
```

> **INFO: Verify without trusting Subsidia**
> `GET /v1/gateway/public-key` needs no credentials. Hand an auditor the certificate and that key, and they can check the Ed25519 signature offline. The code is in [Gateway](https://dev.subsidia.protypa.fr/docs/gateway.md#verify-offline) and [Proof certificates](https://dev.subsidia.protypa.fr/docs/proofs.md).

## What next

- **[Use Claude Code or Cursor through Subsidia](https://dev.subsidia.protypa.fr/docs/gateway.md)**: Three environment variables put every turn of your coding agent behind the Gateway.
- **[Reach your own agents by model name](https://dev.subsidia.protypa.fr/docs/model-addressing.md)**: agent:slug, +cortex and +sensitive turn the model field into a capability address.
- **[Check a text against your pieces](https://dev.subsidia.protypa.fr/docs/verify.md)**: POST /v1/verify gives every claim a verdict, with no model call.
- **[Lock your key down](https://dev.subsidia.protypa.fr/docs/authentication.md)**: Scopes, monthly ceilings, per-minute limits, expiry and read-only keys.
- **[Know what can go wrong](https://dev.subsidia.protypa.fr/docs/errors.md)**: Every status code and what to do about it.
