Skip to main content

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.
ObjectWhat it ownsStorage
DocumentAgentExtracted content (pages, tables, entities), chat history, vendor audit trailSQLite + R2
CollectionAgentDocument membership, cross-document queriesSQLite + D1
DeploymentAgentAccess tokens, guest sessions, redaction policy, frozen manifestSQLite
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.

Create a deployment

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"
  }'
{
  "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}/:
EndpointMethodDescription
/{id}/completionPOSTAsk a question, get an answer
/{id}/statusGETDeployment config and guest count
/{id}/tokensPOST/GETCreate or list access tokens
/{id}/tokens/{hint}DELETERevoke a token
/{id}/eventsGETReplay persisted chat events
/{id}/document-statusGETUnderlying document processing status
/{id}/manifestGETFrozen document manifest (collections)
/{id}/pg_1.pngGETPage 1 image
/{id}/pg_2.mdGETPage 2 markdown
/{id}/pages/1/image.pngGETPage image (legacy compat)

Page resources

Clients only need the deployment ID to access page images and markdown. No document ID required.
# 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:
LevelCan view statusCan chatDefault
noneNoNoYes
readYesNo
askYesYes
Owner access (via API key) always has full permissions.

Access tokens

Create scoped tokens for guests:
# 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}'
{
  "token": "abc123...",
  "tokenHint": "abc123...",
  "role": "ask",
  "expiresAt": 1709312400000
}
Guests use the token as a Bearer token:
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:
curl -X DELETE https://api.okrapdf.com/7km2x9p4ab/tokens/abc123 \
  -H "Authorization: Bearer $OKRA_API_KEY"

Chat persistence

ModeBehavior
ephemeralMessages live only during the WebSocket connection
persistedMessages stored in SQLite, replayable via /events
With persisted chat, each guest gets isolated conversation history (scoped by guestId). Replay events with:
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:
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.
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"}'
{
  "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:
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"
  }'
RoleBehavior
adminNo redaction (default)
viewerPII masked per document’s redaction config
publicStrictest masking
The deployment decides the role (who’s looking), the document enforces it (what to mask). See Redaction for configuring what gets masked.

Data flow

Next steps