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

# Deployments

> Turn any document or collection into a shareable, access-controlled completion endpoint

## What is a deployment?

A deployment is a permanent URL namespace for interacting with a document or collection. Upload once, deploy once, share the URL.

```
https://api.okrapdf.com/7km2x9p4ab/completion
```

The deployment ID is the first path segment -- like a Cloudinary cloud name. Everything for that deployment lives under `/{id}/...`.

## The object model

Three Durable Objects work together. Each runs on Cloudflare's edge with its own SQLite database.

```mermaid theme={null}
flowchart TD
    D[DeploymentAgent] -->|RPC| Doc[DocumentAgent]
    D -->|RPC| Col[CollectionAgent]
    Col -->|fan-out RPC| Doc1[DocumentAgent A]
    Col -->|fan-out RPC| Doc2[DocumentAgent B]

    style D fill:#FFE01B,color:#1e293b,stroke:#E6C800
    style Doc fill:#0ea5e9,color:#fff,stroke:#0284c7
    style Col fill:#7c3aed,color:#fff,stroke:#6d28d9
    style Doc1 fill:#0ea5e9,color:#fff,stroke:#0284c7
    style Doc2 fill:#0ea5e9,color:#fff,stroke:#0284c7
```

| Object              | What it owns                                                                  | Storage     |
| ------------------- | ----------------------------------------------------------------------------- | ----------- |
| **DocumentAgent**   | Extracted content (pages, tables, entities), chat history, vendor audit trail | SQLite + R2 |
| **CollectionAgent** | Document membership, cross-document queries                                   | SQLite + D1 |
| **DeploymentAgent** | Access tokens, guest sessions, redaction policy, frozen manifest              | SQLite      |

<Info>
  **DocumentAgent and CollectionAgent are internal.** They are never exposed to end users directly. All user-facing requests go through a DeploymentAgent, which handles auth, access control, and chat persistence.
</Info>

## Create a deployment

```bash theme={null}
curl -X POST https://api.okrapdf.com/v1/deployments \
  -H "Authorization: Bearer $OKRA_API_KEY" \
  -H "Content-Type: application/json" \
  -d '{
    "documentId": "doc-abc123",
    "guestAccess": "ask",
    "chatPersistence": "persisted"
  }'
```

```json theme={null}
{
  "deploymentId": "7km2x9p4ab",
  "completionUrl": "/7km2x9p4ab/completion",
  "config": {
    "documentId": "doc-abc123",
    "guestAccess": "ask",
    "chatPersistence": "persisted",
    "dataSource": "live"
  },
  "status": "initialized"
}
```

Deployment IDs are 10-character base36 strings that always start with a digit. This guarantees they never collide with named routes like `/v1/...` or `/document/...`.

## URL namespace

Every deployment endpoint lives under `/{deploymentId}/`:

| Endpoint                  | Method   | Description                            |
| ------------------------- | -------- | -------------------------------------- |
| `/{id}/completion`        | POST     | Ask a question, get an answer          |
| `/{id}/status`            | GET      | Deployment config and guest count      |
| `/{id}/tokens`            | POST/GET | Create or list access tokens           |
| `/{id}/tokens/{hint}`     | DELETE   | Revoke a token                         |
| `/{id}/events`            | GET      | Replay persisted chat events           |
| `/{id}/document-status`   | GET      | Underlying document processing status  |
| `/{id}/manifest`          | GET      | Frozen document manifest (collections) |
| `/{id}/pg_1.png`          | GET      | Page 1 image                           |
| `/{id}/pg_2.md`           | GET      | Page 2 markdown                        |
| `/{id}/pages/1/image.png` | GET      | Page image (legacy compat)             |

### Page resources

Clients only need the deployment ID to access page images and markdown. No document ID required.

```bash theme={null}
# Page image (immutable, CDN-cached)
curl https://api.okrapdf.com/7km2x9p4ab/pg_1.png -o page1.png

# Page markdown
curl https://api.okrapdf.com/7km2x9p4ab/pg_3.md

# With shimmer placeholder fallback
curl https://api.okrapdf.com/7km2x9p4ab/d_shimmer/pg_1.png
```

Images are served from R2 with `Cache-Control: public, max-age=31536000, immutable`. First request goes through the DO; CDN caches it after that.

## Access control

Deployments have three guest access levels:

| Level  | Can view status | Can chat | Default |
| ------ | --------------- | -------- | ------- |
| `none` | No              | No       | Yes     |
| `read` | Yes             | No       |         |
| `ask`  | Yes             | Yes      |         |

Owner access (via API key) always has full permissions.

### Access tokens

Create scoped tokens for guests:

```bash theme={null}
# Create a guest token (5 uses, 1 hour)
curl -X POST https://api.okrapdf.com/7km2x9p4ab/tokens \
  -H "Authorization: Bearer $OKRA_API_KEY" \
  -H "Content-Type: application/json" \
  -d '{"role": "ask", "maxUses": 5, "expiresInMs": 3600000}'
```

```json theme={null}
{
  "token": "abc123...",
  "tokenHint": "abc123...",
  "role": "ask",
  "expiresAt": 1709312400000
}
```

Guests use the token as a Bearer token:

```bash theme={null}
curl -X POST https://api.okrapdf.com/7km2x9p4ab/completion \
  -H "Authorization: Bearer abc123..." \
  -H "Content-Type: application/json" \
  -d '{"prompt": "What is total revenue?", "guestId": "user-42"}'
```

Revoke a token and it stops working instantly:

```bash theme={null}
curl -X DELETE https://api.okrapdf.com/7km2x9p4ab/tokens/abc123 \
  -H "Authorization: Bearer $OKRA_API_KEY"
```

## Chat persistence

| Mode        | Behavior                                            |
| ----------- | --------------------------------------------------- |
| `ephemeral` | Messages live only during the WebSocket connection  |
| `persisted` | Messages stored in SQLite, replayable via `/events` |

With persisted chat, each guest gets isolated conversation history (scoped by `guestId`). Replay events with:

```bash theme={null}
curl https://api.okrapdf.com/7km2x9p4ab/events?guestId=user-42 \
  -H "Authorization: Bearer $OKRA_API_KEY"
```

## Collection deployments

Deploy over multiple documents by passing a `collectionId` instead of `documentId`:

```bash theme={null}
curl -X POST https://api.okrapdf.com/v1/deployments \
  -H "Authorization: Bearer $OKRA_API_KEY" \
  -H "Content-Type: application/json" \
  -d '{
    "collectionId": "col-q4-earnings",
    "guestAccess": "ask"
  }'
```

Collection deployments freeze a manifest at creation time. Queries fan out to all documents in the manifest and return aggregated results.

```bash theme={null}
curl -X POST https://api.okrapdf.com/9abc123def/completion \
  -H "Authorization: Bearer $OKRA_API_KEY" \
  -H "Content-Type: application/json" \
  -d '{"prompt": "Compare revenue across all filings"}'
```

```json theme={null}
{
  "results": [
    { "docId": "doc-nvidia", "answer": "Revenue was $26.9B..." },
    { "docId": "doc-amd", "answer": "Revenue was $5.6B..." }
  ],
  "completed": 2,
  "failed": 0,
  "totalCost": 0.0042
}
```

## Redaction

Set `redactionRole` at deployment creation to control PII visibility:

```bash theme={null}
curl -X POST https://api.okrapdf.com/v1/deployments \
  -H "Authorization: Bearer $OKRA_API_KEY" \
  -H "Content-Type: application/json" \
  -d '{
    "documentId": "doc-abc123",
    "guestAccess": "ask",
    "redactionRole": "viewer"
  }'
```

| Role     | Behavior                                   |
| -------- | ------------------------------------------ |
| `admin`  | No redaction (default)                     |
| `viewer` | PII masked per document's redaction config |
| `public` | Strictest masking                          |

The deployment decides the role (who's looking), the document enforces it (what to mask). See [Redaction](/features/redaction) for configuring what gets masked.

## Data flow

```mermaid theme={null}
sequenceDiagram
  participant Client
  participant Worker as CF Worker
  participant Dep as DeploymentAgent
  participant Doc as DocumentAgent

  Client->>Worker: POST /7km2x9p4ab/completion
  Worker->>Dep: proxy (inject userId if API key)
  Dep->>Dep: validate access token
  Dep->>Doc: RPC rpcRunCompletion()
  Doc->>Doc: query SQLite, call LLM
  Doc-->>Dep: {answer, usage, toolCalls}
  Dep->>Dep: persist chat (if persisted)
  Dep-->>Worker: response
  Worker-->>Client: {answer, usage}
```

## Next steps

<CardGroup cols={2}>
  <Card title="Public Completion" icon="globe" href="/features/public-completion">
    Tokenless public endpoints via share links
  </Card>

  <Card title="Collections" icon="folder-open" href="/features/collections">
    Group documents for multi-doc analysis
  </Card>

  <Card title="Redaction" icon="eye-slash" href="/features/redaction">
    Configure PII masking policies
  </Card>

  <Card title="SDK Reference" icon="code" href="/developers/sdk">
    TypeScript SDK for programmatic access
  </Card>
</CardGroup>
