Appearance
Zero-Knowledge Encryption API
Endpoints for managing encryption keys and storing/retrieving encrypted prompts and responses.
Authentication
| Endpoint group | Auth method | Header |
|---|---|---|
| Bootstrap, Shares, Company/Project settings | Session cookie | Cookie: next-auth.session-token=... |
| Encrypted Prompts, Encrypted Responses | Bearer API key | Authorization: Bearer <api-key> |
Key Bootstrap
POST /api/v1/pzk/bootstrap
Initialize, retrieve, or rotate encryption keys.
Auth: Session cookie
Request body:
json
{
"action": "initialize",
"repositoryIds": ["repo-id-1", "repo-id-2"],
"clientPublicKey": "<base64-encoded-public-key>"
}| Field | Type | Required | Description |
|---|---|---|---|
action | "initialize" | "get" | "rotate" | No (default: get) | initialize creates new keys, get retrieves existing, rotate generates new versions |
repositoryIds | string[] | No | Specific repositories to bootstrap. Omit for company-level only. |
clientPublicKey | string | Yes | Your client's base64-encoded public key for key wrapping |
Response (200):
json
{
"keys": [
{
"keyId": "key-uuid",
"encryptedKey": "<wrapped-key-material>",
"algorithm": "AES-GCM",
"expiresAt": "2026-06-05T12:00:00.000Z",
"repositoryId": "repo-id-1",
"serverPublicKey": "<base64-server-public-key>",
"handshakeAlgorithm": "ECDH"
}
],
"action": "initialized"
}The action field in the response reflects what was performed: "initialized", "retrieved", or "rotated".
GET /api/v1/pzk/bootstrap
Check zero-knowledge bootstrap status for the current company.
Auth: Session cookie
Response (200):
json
{
"initialized": true,
"companyKeyId": "key-ref-string",
"repositoriesWithZK": 3,
"enabled": true,
"features": {
"clientEncryption": true,
"keyRotation": true,
"secureSharing": true,
"auditLogging": true
}
}Error responses:
| Status | Condition |
|---|---|
| 401 | Not authenticated |
| 403 | Zero-knowledge not enabled for this company |
| 400 | Missing clientPublicKey (POST only) |
| 404 | No active company found |
Encrypted Prompts
POST /api/v1/pzk/encrypted-prompts
Store a client-side encrypted prompt.
Auth: Authorization: Bearer <api-key>
Request body:
json
{
"repositoryId": "repo-public-id",
"encryptedUserPrompt": {
"alg": "AES-GCM",
"iv": "<base64-iv>",
"ct": "<base64-ciphertext>",
"tag": "<base64-tag>"
},
"encryptedSystemPrompt": {
"alg": "AES-GCM",
"iv": "<base64-iv>",
"ct": "<base64-ciphertext>",
"tag": "<base64-tag>"
},
"encryptedName": {
"alg": "AES-GCM",
"iv": "<base64-iv>",
"ct": "<base64-ciphertext>",
"tag": "<base64-tag>"
},
"name": "Fallback plaintext name",
"encryptionAlg": "AES-GCM",
"encryptionKeyRef": "key-uuid",
"encryptedKeyMaterial": "<optional-wrapped-key>",
"keySalt": "<optional-salt>",
"promptHash": "<sha256-hex-of-plaintext>",
"tags": ["production"],
"category": "summarization",
"branch": "main",
"metadata": {}
}| Field | Type | Required | Description |
|---|---|---|---|
repositoryId | string | Yes | Public repository identifier |
encryptedUserPrompt | object | Yes | Encrypted user prompt {alg, iv, ct, tag?} |
encryptedSystemPrompt | object | No | Encrypted system prompt (same shape) |
encryptedName | object | No | Encrypted prompt name (same shape) |
name | string | No | Plaintext fallback name (prefer encryptedName) |
encryptionAlg | string | No (default: AES-GCM) | Encryption algorithm used |
encryptionKeyRef | string | Yes | Key ID used for encryption (from bootstrap) |
encryptedKeyMaterial | string | No | Client-wrapped key material |
keySalt | string | No | Key derivation salt |
promptHash | string | Yes | SHA-256 hex digest of the plaintext (min 32 chars) |
tags | string[] | No | Categorization tags |
category | string | No | Prompt category |
branch | string | No (default: main) | Version branch |
metadata | object | No | Additional metadata |
Response (201):
json
{
"ok": true,
"prompt": {
"id": "cuid",
"promptId": "prompt_1709654321_abc12345",
"name": "[ENCRYPTED]",
"tags": ["production"],
"category": "summarization",
"createdAt": "2026-03-05T12:00:00.000Z",
"encryptionAlg": "AES-GCM",
"encryptionKeyRef": "key-uuid",
"promptHash": "abc123...",
"encryptedName": { "alg": "AES-GCM", "iv": "...", "ct": "...", "tag": "..." },
"encryptedSystemPrompt": null,
"encryptedUserPrompt": { "alg": "AES-GCM", "iv": "...", "ct": "...", "tag": "..." },
"encryptionKeyId": "key-uuid",
"repository": { "repositoryId": "repo-public-id", "name": "My Repo" },
"metadata": { ... },
"encryptedPayload": { ... }
}
}GET /api/v1/pzk/encrypted-prompts
Retrieve an encrypted prompt by its public prompt ID.
Auth: Authorization: Bearer <api-key>
Query parameters:
| Parameter | Type | Required | Description |
|---|---|---|---|
promptId | string | Yes | The public prompt ID (e.g. prompt_1709654321_abc12345) |
Response (200):
json
{
"ok": true,
"prompt": {
"id": "cuid",
"promptId": "prompt_1709654321_abc12345",
"tags": ["production"],
"category": "summarization",
"repository": { "repositoryId": "repo-public-id" },
"createdAt": "2026-03-05T12:00:00.000Z",
"promptHash": "abc123...",
"encryptionAlg": "AES-GCM",
"encryptionKeyRef": "key-uuid",
"encryptedName": null,
"encryptedSystemPrompt": null,
"encryptedUserPrompt": { "alg": "AES-GCM", "iv": "...", "ct": "...", "tag": "..." },
"metadata": { ... },
"encryptedPayload": { ... }
}
}Encrypted Responses
POST /api/v1/pzk/encrypted-responses
Store an encrypted AI response with execution metadata.
Auth: Authorization: Bearer <api-key>
Request body:
json
{
"executionId": "exec-uuid",
"promptId": "prompt_1709654321_abc12345",
"provider": "openai",
"model": "gpt-4",
"inputTokens": 150,
"outputTokens": 300,
"totalTokens": 450,
"costCents": 12,
"executionTime": 2500,
"encryptionAlg": "AES-GCM",
"encryptedContent": {
"alg": "AES-GCM",
"iv": "<base64-iv>",
"ct": "<base64-ciphertext>",
"tag": "<base64-tag>"
},
"encryptionKeyRef": "key-uuid",
"keySalt": "<optional-salt>"
}| Field | Type | Required | Description |
|---|---|---|---|
executionId | string | Yes | Unique execution identifier |
promptId | string | No | Associated prompt's public ID |
provider | string | Yes | AI provider name |
model | string | Yes | Model identifier |
inputTokens | integer | Yes | Input token count |
outputTokens | integer | Yes | Output token count |
totalTokens | integer | Yes | Total token count |
costCents | integer | Yes | Cost in cents |
executionTime | integer | Yes | Execution time in milliseconds |
encryptionAlg | string | No (default: AES-GCM) | Encryption algorithm |
encryptedContent | object | Yes | Encrypted response {alg, iv, ct, tag?} |
encryptionKeyRef | string | Yes | Key ID used for encryption |
keySalt | string | No | Key derivation salt |
Response (201):
json
{
"ok": true,
"response": {
"executionId": "exec-uuid",
"promptId": "prompt_1709654321_abc12345",
"createdAt": "2026-03-05T12:00:00.000Z"
}
}Key Sharing
GET /api/v1/pzk/shares
List encryption key shares.
Auth: Session cookie
Query parameters:
| Parameter | Type | Default | Description |
|---|---|---|---|
role | "granted" | "received" | received | View shares you granted or received |
status | string | active | Filter by share status |
Response (200):
json
{
"shares": [
{
"id": "share-uuid",
"encryptionKeyId": "key-uuid",
"recipientUserId": "user-uuid",
"grantedByUserId": "user-uuid",
"scope": "read",
"envelope": { ... },
"status": "active",
"expiresAt": "2026-12-31T23:59:59.000Z",
"createdAt": "2026-03-05T12:00:00.000Z",
"updatedAt": "2026-03-05T12:00:00.000Z",
"notes": "Sharing for Q1 review",
"encryptionKey": {
"id": "key-uuid",
"keyRef": "key-ref-string",
"companyId": "company-uuid",
"repositoryId": "repo-uuid",
"isActive": true,
"createdAt": "2026-01-01T00:00:00.000Z"
},
"grantedBy": {
"id": "user-uuid",
"email": "alice@example.com",
"firstName": "Alice",
"lastName": "Smith"
},
"recipient": {
"id": "user-uuid",
"email": "bob@example.com",
"firstName": "Bob",
"lastName": "Jones"
}
}
]
}POST /api/v1/pzk/shares
Share an encryption key with a team member.
Auth: Session cookie
Request body:
json
{
"encryptionKeyId": "key-uuid",
"recipientUserId": "user-uuid",
"scope": "read",
"envelope": { ... },
"expiresAt": "2026-12-31T23:59:59Z",
"notes": "Sharing for Q1 review"
}| Field | Type | Required | Description |
|---|---|---|---|
encryptionKeyId | string | Yes | ID of the encryption key to share |
recipientUserId | string | Yes | Target user ID |
scope | string | No (default: read) | Access scope |
envelope | any | No | Pre-wrapped key envelope. If omitted, server wraps using recipient's public key. |
expiresAt | ISO 8601 string | No | Expiration date |
notes | string | No | Optional note (max 500 chars) |
Response (201):
json
{
"share": {
"id": "share-uuid",
"encryptionKeyId": "key-uuid",
"recipientUserId": "user-uuid",
"grantedByUserId": "user-uuid",
"scope": "read",
"envelope": { ... },
"status": "active",
"expiresAt": "2026-12-31T23:59:59.000Z",
"createdAt": "2026-03-05T12:00:00.000Z",
"updatedAt": "2026-03-05T12:00:00.000Z",
"notes": "Sharing for Q1 review",
"encryptionKey": { ... },
"grantedBy": { ... },
"recipient": { ... }
}
}Error responses:
| Status | Condition |
|---|---|
| 404 | Key not found, inactive, or recipient not found |
| 403 | Caller is not a member of the key's company |
| 422 | No envelope provided and recipient has no registered public key |
Company ZK Settings
GET /api/v1/zero-knowledge/company
Get zero-knowledge status for the current company.
Auth: Session cookie (Account Owner, Admin, or Super Admin)
Response (200):
json
{
"companyId": "company-uuid",
"globalEnabled": true,
"zeroKnowledgeEnabled": true
}PUT /api/v1/zero-knowledge/company
Enable or disable zero-knowledge encryption.
Auth: Session cookie (Account Owner, Admin, or Super Admin)
Request body:
json
{
"enabled": true
}Project ZK Settings
GET /api/v1/zero-knowledge/projects/{projectId}
Get zero-knowledge status for a specific project.
Auth: Session cookie
PUT /api/v1/zero-knowledge/projects/{projectId}
Enable or disable zero-knowledge for a project.
Auth: Session cookie (requires project edit permissions)
Request body:
json
{
"enabled": true
}Dual-Mode Prompt API
POST /api/v1/prompts/zk
Store a prompt in either encrypted or plaintext mode depending on the company's ZK setting.
Auth: Session cookie or API key
When ZK is enabled, the endpoint expects encrypted fields. When disabled, it works as a standard prompt storage endpoint.
Encrypted Object Format
All encrypted fields use a consistent structure:
json
{
"alg": "AES-GCM",
"iv": "<base64-encoded-12-byte-IV>",
"ct": "<base64-encoded-ciphertext>",
"tag": "<base64-encoded-16-byte-auth-tag>"
}| Field | Description |
|---|---|
alg | Algorithm identifier (currently AES-GCM) |
iv | Base64-encoded initialization vector (12 bytes / 96 bits) |
ct | Base64-encoded ciphertext |
tag | Base64-encoded authentication tag (16 bytes / 128 bits). Optional if tag is appended to ct. |
Error Codes
| Status | Code | Meaning |
|---|---|---|
| 401 | Invalid or missing API key | Bearer token invalid or not provided |
| 403 | Zero-knowledge is disabled | ZK not enabled at company or project level |
| 400 | invalid_request | Zod validation failed (details in response) |
| 400 | clientPublicKey is required | Bootstrap requires client public key |
| 404 | Not found / Repository not found | Resource does not exist or not in caller's company |
| 422 | Recipient is missing encryption public key | Cannot auto-wrap key without recipient's public key |
| 503 | api_auth_unavailable | External API auth service temporarily unavailable |
