REST API
All API routes are under /api/v1/. Endpoints that require an API key expect it as a Bearer token: Authorization: Bearer cape_<key>.
Chat
POST /api/v1/chat
Stream a chat response grounded in the knowledge base. Responses are delivered as Server-Sent Events (SSE).
Auth: API key required
Request body:
{
"message": "How do I upload an asset?",
"source": "user_docs",
"conversationId": "clx...",
"language": "en",
"retrieval": "standard",
"stream": true
}
| Field | Type | Required | Description |
|---|---|---|---|
message | string | Yes | The user's question |
source | string | string[] | No | Namespace(s) to search. Defaults to all. |
conversationId | string | No | Resume an existing conversation |
language | string | No | BCP 47 code. Auto-detected if omitted. |
retrieval | standard | smart | No | Retrieval mode. Default: standard |
stream | boolean | No | Enable SSE streaming. Default: true |
SSE events:
data: {"type":"meta","conversationId":"clx...","sources":[{"title":"Upload guide","documentId":"..."}]}
data: {"type":"chunk","text":"To upload an asset, "}
data: {"type":"chunk","text":"navigate to the Assets section..."}
data: [DONE]
Conversations are persisted server-side. Pass conversationId from the meta event in subsequent requests to continue the same thread.
Search
POST /api/v1/search
Semantic search over the knowledge base. Returns ranked chunks without generating a response.
Auth: API key required
Request body:
{
"query": "how does role-based access work",
"source": ["tech_docs", "api_endpoints"],
"topK": 5
}
| Field | Type | Required | Description |
|---|---|---|---|
query | string | Yes | Search query |
source | string | string[] | No | Namespace filter |
topK | number | No | Max results (1–20). Default: 5 |
Response:
{
"results": [
{
"chunkId": "...",
"documentId": "...",
"documentTitle": "Access control",
"headingPath": "Role-based access > Permissions",
"content": "...",
"namespace": "tech_docs",
"score": 0.91
}
],
"count": 1
}
score is cosine similarity (0–1). Higher is more relevant.
FAQ
GET /api/v1/faq
Generate a structured FAQ document for a topic. Results are cached for 7 days.
Auth: None (public)
Query parameters:
| Param | Required | Description |
|---|---|---|
q | Yes | FAQ topic or question |
source | No | Namespace. Default: user_docs. Use all for all namespaces. |
lang | No | BCP 47 language code |
Example:
GET /api/v1/faq?q=asset+upload&source=user_docs&lang=en
Response:
{
"query": "asset upload",
"scope": "user_docs",
"language": "en",
"content": "# Asset Upload FAQ\n\n## How do I...",
"cached": true
}
content is a Markdown string with 5–10 Q&A pairs. cached: true means the response was served from the database cache.
Ingestion
POST /api/v1/ingest/md
Ingest raw Markdown content.
Auth: API key required
Request body:
{
"title": "Getting started",
"content": "# Getting started\n\nWelcome to Cape...",
"namespace": "user_docs",
"slug": "getting-started",
"metadata": { "version": "2.1" }
}
| Field | Type | Required | Description |
|---|---|---|---|
title | string | Yes | Document title |
content | string | Yes | Raw Markdown |
namespace | string | Yes | user_docs, tech_docs, api_endpoints, or confluence |
slug | string | No | Unique key for deduplication. Defaults to title. |
metadata | object | No | Arbitrary key-value metadata |
Response:
{ "documentId": "clx...", "skipped": false }
skipped: true means the content hash matched the existing document — no re-embedding was done.
POST /api/v1/ingest/url
Fetch a URL and ingest its content.
Auth: API key required
Request body:
{
"url": "https://docs.cape.io/getting-started",
"namespace": "user_docs",
"title": "Getting started"
}
Returns the same shape as /ingest/md. Returns 422 if the URL cannot be fetched.
POST /api/v1/ingest/openapi
Ingest an OpenAPI (Swagger) spec. Creates one document per operation.
Auth: API key required
Content-Type: application/json or application/yaml
Request body: The raw OpenAPI spec JSON or YAML.
Response:
{ "upserted": 12, "unchanged": 3, "total": 15 }
Operations are keyed by operationId. Re-posting the same spec will skip unchanged operations.
POST /api/v1/ingest/confluence
Bulk-ingest an entire Confluence space. This is an async operation.
Auth: API key required
Request body:
{
"spaceKey": "ENG",
"namespace": "confluence"
}
Response (202 Accepted):
{ "jobId": "clx...", "status": "pending" }
Monitor progress via GET /api/v1/ingest/jobs. Requires CONFLUENCE_BASE_URL, CONFLUENCE_EMAIL, and CONFLUENCE_API_TOKEN environment variables.
GET /api/v1/ingest/jobs
List the last 100 ingestion jobs.
Auth: Admin (session or API key)
Response:
{
"jobs": [
{
"id": "clx...",
"type": "confluence",
"status": "done",
"payload": { "spaceKey": "ENG" },
"completedAt": "2026-04-30T10:00:00Z"
}
]
}
Statuses: pending · running · done · failed. Failed jobs include an error string.
API Keys
GET /api/v1/keys
List all API keys.
Auth: Admin (session or API key)
Response:
{
"keys": [
{
"id": "clx...",
"label": "Production",
"requestCount": 1482,
"rateLimitRpm": null,
"rateLimitRpd": 5000,
"createdAt": "2026-01-15T09:00:00Z",
"lastUsedAt": "2026-04-30T08:43:00Z",
"revokedAt": null
}
]
}
POST /api/v1/keys
Create a new API key.
Auth: Admin (session or API key)
Request body:
{
"label": "CI/CD pipeline",
"rateLimitRpm": 60,
"rateLimitRpd": 10000
}
Response (201 Created):
{
"id": "clx...",
"label": "CI/CD pipeline",
"key": "cape_a3f1c9d2...",
"createdAt": "2026-04-30T10:00:00Z"
}
The key field is returned only once. Store it immediately — it cannot be retrieved again.
DELETE /api/v1/keys/:id
Revoke an API key.
Auth: Admin (session or API key)
Response:
{ "revoked": true, "id": "clx..." }
Returns 404 if the key does not exist, 409 if it is already revoked.
Conversations
GET /api/v1/conversations
List stored conversations with their messages.
Auth: Admin (session or API key)
Query parameters:
| Param | Description |
|---|---|
source | Filter by namespace |
language | Filter by language code |
limit | Results per page (max 200, default 50) |
offset | Pagination offset |
Response:
{
"conversations": [ { "id": "...", "messages": [...] } ],
"total": 312,
"take": 50,
"skip": 0
}