API Reference¶
Base URL: http://localhost:3000 (default)
Most endpoints require authentication via Bearer token or API key:
OpenAPI Specification¶
DocBrain auto-generates an OpenAPI 3.1 specification from all API routes. The spec includes request/response schemas, authentication requirements, and error codes for every endpoint.
| Resource | URL |
|---|---|
| Swagger UI | GET /api/docs |
| OpenAPI JSON | GET /api/docs/openapi.json |
The Swagger UI is publicly accessible (no authentication required) and provides interactive API exploration with "Try it out" functionality. Use the OpenAPI JSON endpoint for client code generation with tools like openapi-generator, oapi-codegen, or swagger-codegen.
The specification is versioned — the info.version field tracks the DocBrain release version.
Authentication¶
Login¶
Exchange email + password for a session token. The token is a db_sk_... API key with a TTL set by LOGIN_SESSION_TTL_HOURS.
Request Body:
Response:
Logout¶
Revokes the current session token. Requires authentication.
Response: 200 OK with {"ok": true}
Verify Auth / Whoami¶
Returns the identity of the current token (API key or session key). Useful for verifying an API key is valid.
Response:
{
"key_id": "uuid",
"name": "Platform Team Key",
"role": "editor",
"allowed_spaces": ["PLATFORM", "SRE"],
"created_at": "2025-12-01T00:00:00Z"
}
Core Endpoints¶
Health Check¶
Returns 200 OK with {"status": "ok"}. Does not require authentication — used by load balancers and container health probes.
Ask a Question¶
Request Body:
{
"question": "How do I deploy to production?",
"session_id": "optional-uuid-for-conversation-continuity",
"space": "PLATFORM",
"spaces": ["PLATFORM", "SRE"],
"stream": true
}
session_id— optional UUID to continue a conversation across turnsspace— soft boost: results from this space get a 1.5× score multiplier but other spaces still appear. Use when you want cross-space results with your team's docs ranked first.spaces— hard filter: only return results from these spaces for this request. If combined with an API key'sallowed_spaces, the intersection is used (most restrictive wins). Omit to search all spaces.stream— iftrue, returns SSE; iffalse(default), returns JSON
Response (non-streaming):
{
"answer": "To deploy to production, follow these steps...",
"sources": [
{
"title": "Deployment Guide",
"heading": "Production Deployment",
"content": "...",
"source_url": "https://...",
"score": 0.92,
"freshness_score": 82.0,
"freshness_status": "fresh"
}
],
"session_id": "uuid",
"episode_id": "uuid",
"turn": 1,
"intent": "procedural"
}
Streaming Response (stream: true):
Returns Server-Sent Events (SSE):
event: phase
data: {"status": "started", "phase": "retrieval", "description": "Searching documents..."}
event: phase
data: {"status": "completed", "phase": "retrieval", "duration_ms": 145, "result_count": 5}
event: token
data: {"text": "To deploy"}
event: token
data: {"text": " to production"}
event: answer
data: {"answer": "...", "sources": [...], "session_id": "...", "episode_id": "...", "intent": "procedural"}
Submit Feedback¶
Request Body:
feedback: 1 (helpful) or -1 (not helpful). Negative feedback seeds the Autopilot gap detection pipeline.
Freshness Report¶
Query Parameters:
- space (optional) — Filter by document space
Response:
{
"space": "DOCS",
"summary": {
"total_docs": 142,
"fresh": 98,
"review": 27,
"stale": 12,
"outdated": 5,
"avg_score": 76.3
},
"documents": [
{
"document_id": "123",
"title": "API Guide",
"space": "DOCS",
"source_url": "https://...",
"total_score": 45.2,
"status": "stale",
"freshness_badge": "🟡 Review",
"time_decay_score": 30,
"engagement_score": 50,
"content_currency_score": 40,
"link_health_score": 60,
"contradiction_score": 80
}
]
}
Analytics¶
Query Parameters:
- days (optional, default: 30) — Reporting period in days
- space (optional) — Filter most-retrieved docs by Confluence space
- user_id (optional) — Filter query statistics by a specific user UUID
Response:
{
"period_days": 30,
"total_queries": 1247,
"unique_users": 45,
"avg_feedback": 0.82,
"unanswered_rate": 0.12,
"queries_by_day": [
{"date": "2025-01-15", "count": 42}
],
"top_intents": [
{"intent": "procedural", "count": 456}
],
"top_queries": [
{"query": "How do I deploy?", "count": 18}
],
"most_retrieved_docs": [
{
"title": "Deploy Guide",
"source_url": "https://...",
"space": "PLATFORM",
"retrieval_count": 94,
"freshness_score": 45.0,
"freshness_status": "stale"
}
]
}
CSV export:
Returns a CSV file of all query episodes in the period. Useful for external BI tools.
Analytics CSV Export¶
Returns a .csv file of all query episodes in the requested period. Columns: episode_id, created_at, user_id, query, intent, feedback, space.
Server Configuration¶
No authentication required. Returns enabled features and server version.
{
"version": "0.6.0",
"features": {
"freshness": true,
"analytics": true,
"slack": true,
"autopilot": true,
"incident_mode": true
}
}
Incident Mode¶
Request Body:
Activates incident mode, which prioritizes retrieval of runbooks and incident playbooks.
Admin Dashboard¶
Single-request admin overview. Returns all key health metrics in one parallel-fetched payload — designed for dashboards that need to avoid multiple round-trips.
Response:
{
"health": {
"total_documents": 342,
"overall_health_score": 67.3,
"freshness_distribution": {
"fresh": 120,
"review": 89,
"stale": 72,
"outdated": 41,
"archive": 20
},
"top_stale_cited_docs": [
{
"title": "Deploy Guide",
"freshness_score": 23.0,
"citations_last_7d": 47,
"contradiction_score": 45.0
}
],
"coverage_gaps": 15
},
"autopilot": {
"total_gaps": 20,
"open_gaps": 14,
"critical_gaps": 3,
"drafts_generated": 8,
"drafts_published": 2,
"last_analysis_at": "2025-02-20T14:30:00Z"
},
"forecast": {
"current_open_gaps": 14,
"projected_new_critical_30d": 5,
"projected_total_30d": 19,
"avg_weekly_new_gaps": 2.5,
"avg_weekly_resolved": 1.0,
"trend": "worsening"
},
"freshness_distribution": {
"fresh": 120,
"review": 89,
"stale": 72,
"outdated": 41,
"archive": 20
},
"top_gaps": [...],
"top_docs": [...],
"recent_audit": [
{
"action": "gap_dismissed",
"entity_id": "uuid",
"actor": "admin@example.com",
"created_at": "2026-02-25T10:00:00Z"
}
]
}
Autopilot Endpoints¶
Requires AUTOPILOT_ENABLED=true. All endpoints require authentication.
Autopilot Summary¶
Response:
{
"total_gaps": 20,
"open_gaps": 14,
"critical_gaps": 3,
"drafts_generated": 8,
"drafts_published": 2,
"last_analysis_at": "2025-02-20T14:30:00Z"
}
Gap Growth Forecast¶
Returns a 30-day projection of gap cluster growth based on the last 4 weeks of creation and resolution rates (linear extrapolation).
Response:
{
"current_open_gaps": 14,
"projected_new_critical_30d": 5,
"projected_total_30d": 19,
"avg_weekly_new_gaps": 2.5,
"avg_weekly_resolved": 1.0,
"trend": "worsening"
}
trend is one of:
- "improving" — resolution rate ≥ 75% of creation rate
- "stable" — resolution rate ≥ 40% of creation rate
- "worsening" — resolution rate < 40% of creation rate
List Gap Clusters¶
Query Parameters:
- limit (optional, default: 20) — Maximum clusters to return
- status (optional) — Filter by status: open, dismissed, resolved
- severity (optional) — Filter by severity: low, medium, high, critical
Response:
[
{
"id": "uuid",
"label": "Production Deployment Process",
"description": "Multiple questions about deploying services to production went unanswered.",
"query_count": 47,
"sample_queries": [
"How do I deploy to prod?",
"What's the canary process?",
"Where are the deployment configs?"
],
"avg_confidence": 0.28,
"severity": "critical",
"status": "open",
"unique_users": 12,
"negative_ratio": 0.68,
"trend": "recurring",
"assignee_id": null,
"assigned_at": null,
"created_at": "2025-02-15T10:00:00Z",
"updated_at": "2025-02-20T14:30:00Z"
}
]
New fields vs. earlier versions:
- unique_users — distinct users who hit this gap
- negative_ratio — fraction of queries on this topic with negative feedback
- trend — "new" (appeared in last 7 days) or "recurring" (open > 7 days)
- assignee_id — UUID of the user assigned to resolve this gap, or null
- assigned_at — ISO timestamp when the gap was assigned, or null
Trigger Gap Analysis¶
Runs gap analysis immediately (normally runs on the AUTOPILOT_GAP_ANALYSIS_INTERVAL_HOURS schedule). Returns the number of new clusters created.
Response:
Dismiss a Gap¶
Marks a gap cluster as dismissed (not worth addressing). Requires admin or editor role.
Assign a Gap¶
Assigns a gap cluster to a user for resolution.
Request Body:
Response: 200 OK with the updated gap cluster object.
Gap Related Documents¶
Returns documents semantically related to this gap cluster — these are the docs that users were trying to get answers from when the gap was detected. Useful for identifying which authors to notify or which content needs updating.
Response:
[
{
"source_id": "doc-123",
"title": "Production Deployment Guide",
"source_url": "https://confluence.example.com/...",
"space": "PLATFORM",
"freshness_score": 23.5,
"author": "bhanu@example.com"
}
]
List Drafts¶
Query Parameters:
- status (optional) — Filter by status: pending_review, approved, published, rejected
- limit (optional, default: 20) — Maximum drafts to return
Response:
[
{
"id": "uuid",
"cluster_id": "uuid",
"title": "Production Deployment Runbook",
"content": "# Production Deployment\n\n## Prerequisites\n...",
"content_type": "runbook",
"source_queries": ["How do I deploy to prod?", "..."],
"source_doc_ids": ["doc-uuid-1", "doc-uuid-2"],
"quality_score": 0.87,
"status": "pending_review",
"created_at": "2025-02-20T15:00:00Z"
}
]
Get a Draft¶
Returns full draft content for review.
Generate Draft for a Gap¶
Generates a draft document for the specified gap cluster. Uses existing docs as context. Also DMs Slack authors of related docs if SLACK_BOT_TOKEN is configured.
Response:
{
"draft_id": "uuid",
"title": "Production Deployment Runbook",
"content_type": "runbook",
"quality_score": 0.87
}
Update Draft Status¶
Request Body:
Valid statuses: approved, published, rejected
Weekly Digest Preview¶
Returns the current weekly digest data (without sending it to Slack).
Response:
{
"period_start": "2025-02-13T00:00:00Z",
"period_end": "2025-02-20T00:00:00Z",
"total_queries": 152,
"unanswered_queries": 18,
"top_gaps": [
{
"id": "uuid",
"label": "Production Deployment Process",
"query_count": 47,
"severity": "critical",
"trend": "recurring"
}
],
"new_drafts": [...],
"stale_doc_count": 7,
"top_docs_by_queries": [
{
"title": "API Rate Limits",
"source_url": "https://...",
"space": "PLATFORM",
"retrieval_count": 94,
"author": "bhanu@example.com"
}
]
}
top_docs_by_queries — the 10 documents most frequently retrieved during the period, with author attribution. Gives doc owners insight into which content is being searched most heavily.
Knowledge Health¶
Health Report¶
Full knowledge base health overview.
Response:
{
"total_documents": 342,
"overall_health_score": 67.3,
"freshness_distribution": {
"fresh": 120,
"review": 89,
"stale": 72,
"outdated": 41,
"archive": 20
},
"top_stale_cited_docs": [
{
"title": "Deploy Guide",
"freshness_score": 23.0,
"citations_last_7d": 47,
"contradiction_score": 45.0
}
],
"coverage_gaps": 15
}
Admin Endpoints¶
All admin endpoints require an admin-role API key.
List API Keys¶
Create API Key¶
Request Body:
role: viewer, editor, analyst, admin
viewer— ask questions, browse answers, give feedback, and access all intelligence dashboards (Documentation Analytics, Predictive Gaps, Autonomous Document Maintenance, Knowledge Stream)editor— everything viewer can + manage spaces and capturesanalyst— everything editor can; reserved for future role-based scoping, currently equivalent toeditoradmin— full access including user management, RBAC config, and ingest triggers
allowed_spaces: hard-filters all queries and ingestion to the listed spaces. Empty array = no restriction.
Revoke API Key¶
Onboarding Mode¶
Returns an AI-curated reading list for a new team member.
Query Parameters:
- role — Job role or persona (e.g. platform-engineer, sre, backend-developer)
- days (optional, default: 7) — Onboarding period in days
Response:
{
"role": "platform-engineer",
"reading_list": [
{
"title": "Platform Onboarding Guide",
"source_url": "https://...",
"freshness_score": 85.0,
"reason": "Direct onboarding guide covering role-specific processes and expectations."
}
]
}
Knowledge Graph¶
GET /api/v1/graph/entity/{name}¶
Disambiguate an entity by name and return its subgraph (neighbors and edges).
Response:
{
"ranked": [
{
"entity": { "id": "uuid", "name": "payments-service", "entity_type": "service" },
"score": 0.95,
"reason": "direct_connections=2, degree=15"
}
],
"subgraph": {
"nodes": [
{ "id": "uuid", "name": "payments-service", "entity_type": "service" }
],
"edges": [
{ "from_entity_id": "uuid1", "to_entity_id": "uuid2", "relation_type": "DEPENDS_ON" }
]
}
}
GET /api/v1/graph/dependencies/{entity_id}¶
Multi-hop dependency traversal from a given entity.
Query params: depth (default 2, max 5), direction (downstream | upstream | both), relation_types (comma-separated, optional)
Response:
[
{
"entity": { "id": "uuid", "name": "auth-service", "entity_type": "service" },
"depth": 1,
"path": ["uuid1", "uuid2"]
}
]
GET /api/v1/graph/blast-radius/{entity_id}¶
Determine what is affected if an entity changes or goes down.
Query params: depth (default 3, max 5)
Response:
{
"entity": { "id": "uuid", "name": "payments-service", "entity_type": "service" },
"affected": [{ "entity": { "id": "uuid", "name": "checkout-service", "entity_type": "service" }, "depth": 1, "path": [] }],
"by_type": { "service": [{ "id": "uuid", "name": "checkout-service", "entity_type": "service" }] },
"by_depth": { "1": [{ "id": "uuid", "name": "checkout-service", "entity_type": "service" }] }
}
GET /api/v1/graph/path¶
Find the shortest path between two entities.
Query params: from (UUID, required), to (UUID, required), depth (max hops, default 5)
Response: Array of GraphEdge objects, or null if no path found within depth.
GET /api/v1/graph/experts/{topic}¶
Route to domain experts via the entity-to-team-to-person chain.
Response:
[
{
"person": { "id": "uuid", "name": "Alice Smith", "entity_type": "person" },
"team": { "id": "uuid", "name": "platform-team", "entity_type": "team" },
"confidence": 0.85,
"route": []
}
]
Documentation Analytics¶
GET /api/v1/analytics/velocity¶
Org-wide documentation analytics metrics over a configurable time window.
Query params: days (default 30)
Response:
{
"current_velocity": 2.5,
"velocity_trend": "accelerating",
"grade": "B",
"knowledge_half_life_days": 45,
"tribal_knowledge_pct": 0.15,
"documentation_roi": {
"queries_deflected": 340,
"estimated_hours_saved": 85
},
"weekly_snapshots": [
{ "week": "2026-03-09", "velocity": 2.3, "docs_created": 4, "gaps_resolved": 2 }
],
"per_team": [{ "team": "platform", "velocity": 3.2, "grade": "A" }]
}
GET /api/v1/analytics/velocity/teams¶
Per-team velocity breakdown.
Query params: days (default 30)
GET /api/v1/analytics/velocity/roi¶
Org-wide ROI summary: total queries deflected, hours saved, and cost saved in USD over the selected time window.
Query params: days (default 30)
Response:
Predictive Gap Detection¶
POST /api/v1/predictive/code-change¶
Detect documentation that may be stale after a code change.
Request:
Response: Array of PredictedGap objects with doc_id, title, reason, confidence, trigger.
GET /api/v1/predictive/cascade¶
Detect cascade staleness — documents that reference recently-updated documents and may now be inconsistent.
GET /api/v1/predictive/seasonal¶
Detect seasonal query patterns approaching their predicted peak for proactive refresh.
GET /api/v1/predictive/onboarding¶
Detect onboarding gaps — common questions from new hires that are poorly covered or missing from documentation.
Doc Maintenance¶
GET /api/v1/maintenance/fixes¶
List auto-detected fix proposals (contradictions, broken links, version bumps).
Query params: doc_id (optional), status (pending | approved | applied | rejected), limit (default 50)
POST /api/v1/maintenance/fixes/{id}/apply¶
Apply a fix proposal. Requires authentication.
Response: 200 OK
POST /api/v1/maintenance/fixes/{id}/reject¶
Reject a fix proposal. Requires authentication.
Response: 200 OK
GET /api/v1/maintenance/stats¶
Aggregate fix proposal statistics.
Response:
Knowledge Stream¶
GET /api/v1/stream/events¶
List recent stream events (incidents, decay alerts, expertise gaps, doc updates).
Query params: since (RFC3339), type (incident_warning | decay_alert | expertise_gap | doc_updated), limit (default 50)
GET /api/v1/stream/events/user/{user_id}¶
Personalized event stream filtered by the user's active context.
POST /api/v1/stream/context¶
Update user context (services and topics) for personalized stream delivery.
Request:
GET /api/v1/stream/stats¶
Event count statistics broken down by time window (24h, 7d, 30d) and event type.
Event Bus¶
GET /api/v1/events¶
Query the persistent event log. Requires admin role.
Query params:
| Param | Type | Default | Description |
|---|---|---|---|
type |
string | — | Filter by event type (e.g. gap.detected, document.ingested) |
since |
string | — | RFC3339 datetime or YYYY-MM-DD — only events after this time |
limit |
integer | 100 |
Max results (1–1000) |
offset |
integer | 0 |
Pagination offset |
Response:
{
"events": [
{
"id": "uuid",
"event_type": "gap.detected",
"payload": { "cluster_id": "uuid", "severity": "critical", "label": "...", "query_count": 15, "unique_users": 8 },
"emitted_at": "2026-03-21T14:30:00Z",
"processed_by": ["event_logger"]
}
],
"count": 1,
"limit": 100,
"offset": 0
}
Event types: document.ingested, document.updated, document.deleted, freshness.changed, quality.scored, fragment.captured, fragment.indexed, fragment.promoted, gap.detected, gap.assigned, gap.resolved, draft.generated, draft.review_requested, draft.published, draft.rejected, query.answered, feedback.received, sla.breached, maintenance.fix_proposed
GET /api/v1/events/stream¶
SSE stream of real-time events. Requires admin role. Max 10 concurrent connections.
Each SSE message includes:
- event: — the event type (e.g. gap.detected)
- id: — unique event UUID (for Last-Event-ID reconnection)
- data: — JSON payload with the full EventEnvelope (id, event, emitted_at)
Knowledge Fragments¶
POST /api/v1/fragments¶
Create a new knowledge fragment. Requires editor role.
Fragments are routed by confidence: >= auto_index_threshold (default 0.7) → auto-indexed into search; >= review_threshold (default 0.4) → queued for review; below → auto-discarded.
Request body:
{
"fragment_type": "decision",
"summary": "Switched from Redis pub/sub to PG LISTEN/NOTIFY",
"content": "Redis cluster mode doesn't support pub/sub across shards...",
"source_type": "pr_merge",
"source_ref": "https://github.com/acme/platform/pull/1234",
"source_id": "github:acme/platform#1234",
"confidence": 0.85,
"space": "PLATFORM",
"related_doc_ids": ["550e8400-e29b-41d4-a716-446655440000"],
"code_location": "src/events/publisher.rs:42"
}
| Field | Type | Required | Description |
|---|---|---|---|
fragment_type |
string | yes | decision, fact, caveat, procedure, context |
summary |
string | yes | Short description |
content |
string | yes | Full content (max FRAGMENT_MAX_CONTENT_LENGTH) |
source_type |
string | yes | pr_merge, commit, ide_annotation, conversation_distill, deploy, incident, manual, ci_analyze |
source_ref |
string | no | URL or reference to the source |
source_id |
string | no | Dedup key (unique per source_type) |
confidence |
float | no | 0.0–1.0, default 0.5 |
space |
string | no | Space for routing/filtering |
related_doc_ids |
UUID[] | no | Related document IDs |
code_location |
string | no | File path and line (e.g. src/foo.rs:42) |
Response: 201 Created
GET /api/v1/fragments¶
List fragments with optional filters.
| Param | Type | Default | Description |
|---|---|---|---|
status |
string | — | Filter by status: pending, indexed, promoted, discarded, review_queued |
space |
string | — | Filter by space |
source_type |
string | — | Filter by source type |
limit |
integer | 50 |
Max results (1–1000) |
offset |
integer | 0 |
Pagination offset |
GET /api/v1/fragments/:id¶
Get a single fragment by ID.
PATCH /api/v1/fragments/:id¶
Update a fragment. Requires editor role. Only provided fields are updated.
DELETE /api/v1/fragments/:id¶
Delete a fragment. Requires admin role. Also removes from search index.
GET /api/v1/fragments/review-queue¶
List fragments with review_queued status. Requires analyst role. Supports same filters as list.
POST /api/v1/fragments/:id/approve¶
Approve a fragment — sets status to indexed and embeds/indexes into OpenSearch. Requires analyst role.
POST /api/v1/fragments/:id/discard¶
Discard a fragment with optional reason. Requires analyst role.
Request body:
GET /api/v1/fragments/stats¶
Fragment statistics — counts by status, source type, and space. Supports ?space= filter.
GET /api/v1/fragments/clusters¶
Discover semantic clusters of related fragments. Uses embedding similarity to group fragments by topic. Requires analyst role.
Returns clusters sorted by composability score (highest first). Clusters with composable: true meet the composition threshold (5+ fragments, diverse sources, 500+ words).
Response:
[
{
"topic": "Redis Cluster Mode Limitations",
"fragment_ids": ["uuid1", "uuid2", "uuid3", "uuid4", "uuid5"],
"source_diversity": 3,
"author_diversity": 4,
"total_content_words": 1250,
"composability_score": 0.85,
"sample_summaries": [
"Redis cluster mode doesn't support pub/sub across shards",
"MULTI/EXEC transactions limited to single slot"
],
"composable": true
}
]
| Status | Meaning |
|---|---|
| 200 | Clusters returned |
| 403 | Insufficient role (requires analyst) |
| 409 | Clustering is disabled via configuration |
POST /api/v1/fragments/clusters/compose¶
Compose a composable cluster into a documentation draft. Checks for existing doc coverage before composing — if a document already covers the topic (>70% similarity), composition is skipped. Requires admin role.
Request Body:
{
"cluster": {
"topic": "Redis Cluster Mode Limitations",
"fragment_ids": ["uuid1", "uuid2", "uuid3", "uuid4", "uuid5"],
"source_diversity": 3,
"author_diversity": 4,
"total_content_words": 1250,
"composability_score": 0.85,
"sample_summaries": ["..."],
"composable": true
}
}
Response:
| Status | Meaning |
|---|---|
| 200 | Draft created successfully |
| 400 | Cluster not composable or invalid request |
| 403 | Insufficient role (requires admin) |
| 409 | Clustering disabled, or existing doc already covers this topic |
Space Ownership & Governance¶
Explicit knowledge ownership — spaces get owners, maintainers, and contributors. Topics get stewards who are auto-assigned when matching gaps are detected. This is the accountability layer that ensures gaps get resolved and drafts get reviewed.
Governance is configured via API, not environment variables.
GET /api/v1/governance/spaces¶
List all spaces with ownership summary (owner/maintainer/contributor counts). Requires viewer role.
Response:
{
"spaces": [
{ "space": "PLATFORM", "owner_count": 1, "maintainer_count": 2, "contributor_count": 5 },
{ "space": "INFRA", "owner_count": 0, "maintainer_count": 0, "contributor_count": 0 }
]
}
GET /api/v1/governance/spaces/:space/owners¶
List owners, maintainers, and contributors for a specific space. Requires viewer role.
Response:
{
"owners": [
{
"id": "uuid",
"space": "PLATFORM",
"user_id": "uuid",
"role": "owner",
"notifications_enabled": true,
"user_email": "alice@acme.com",
"user_display_name": "Alice"
}
]
}
POST /api/v1/governance/spaces/:space/owners¶
Add a user as owner, maintainer, or contributor of a space. Requires admin role.
Request body:
Valid roles: owner, maintainer, contributor.
Status codes: 201 Created, 409 if user already assigned, 400 if user not found.
DELETE /api/v1/governance/spaces/:space/owners/:user_id¶
Remove a user from a space's ownership. Requires admin role.
Status codes: 204 No Content, 404 if not found.
PATCH /api/v1/governance/spaces/:space/owners/:user_id¶
Update a space owner's role or notification preference. Requires admin role.
Request body:
At least one field must be provided.
GET /api/v1/governance/stewards¶
List all topic stewards with their regex patterns and auto-assign settings. Requires viewer role.
Response:
{
"stewards": [
{
"id": "uuid",
"topic_pattern": "kubernetes|k8s|eks",
"display_name": "Kubernetes Infrastructure",
"user_id": "uuid",
"auto_assign_gaps": true,
"auto_assign_fragments": true,
"user_email": "carol@acme.com",
"user_display_name": "Carol"
}
]
}
POST /api/v1/governance/stewards¶
Create a topic steward. The topic_pattern is a regex matched against gap labels and fragment content for auto-assignment. Requires admin role.
Request body:
{
"topic_pattern": "kubernetes|k8s|eks",
"display_name": "Kubernetes Infrastructure",
"user_id": "uuid",
"auto_assign_gaps": true,
"auto_assign_fragments": true
}
Pattern validation: max 500 characters, must be valid regex. Status codes: 201 Created, 400 invalid pattern or user not found.
GET /api/v1/governance/stewards/:id¶
Get a single topic steward by ID. Requires viewer role.
DELETE /api/v1/governance/stewards/:id¶
Remove a topic steward. Requires admin role. Returns 204 or 404.
PATCH /api/v1/governance/stewards/:id¶
Update a topic steward's pattern, display name, or auto-assign settings. Requires admin role.
Request body:
At least one field must be provided. New patterns are validated before saving.
GET /api/v1/governance/my-spaces¶
List spaces the current user owns or maintains. Requires an API key with an associated user_id.
GET /api/v1/governance/my-stewardships¶
List topics the current user stewards. Requires an API key with an associated user_id.
GET /api/v1/governance/coverage¶
Ownership coverage report across all spaces. Requires viewer role.
Response:
{
"total_spaces": 12,
"owned_spaces": 9,
"coverage_pct": 75.0,
"unowned_spaces": ["INFRA", "SECURITY", "ONBOARDING"]
}
Total spaces are derived from the documents table (distinct space values), not from governance tables — ensuring unowned spaces are visible.
GET /api/v1/governance/dashboard¶
Aggregated governance overview combining ownership coverage, SLA compliance, quality scores, fragment stats, review queue, velocity, and top contributors. Requires analyst role.
Each section is independently fetched with a 5-second timeout. If a section fails, it returns null and the failure is recorded in errors. The endpoint always returns 200 with partial data.
Response:
{
"coverage": {
"total_spaces": 12,
"owned_spaces": 9,
"coverage_pct": 75.0,
"unowned_spaces": ["INFRA"]
},
"sla_breaches": {
"total_open": 3,
"total_acknowledged": 12,
"by_type": [{ "sla_type": "gap_acknowledgment", "count": 2 }],
"by_space": [{ "space": "INFRA", "count": 2 }]
},
"quality": {
"overall_avg": 68.5,
"by_space": [{ "space": "PAYMENTS", "avg_score": 72.3, "doc_count": 15 }],
"worst_docs": [{ "document_id": "uuid", "title": "Old Guide", "composite_score": 23.4 }]
},
"fragments": {
"total": 147,
"by_status": [{ "status": "approved", "count": 89 }],
"by_source_type": [{ "source_type": "pr_merge", "count": 68 }],
"by_space": [{ "space": "PAYMENTS", "count": 45 }]
},
"review_queue": [
{
"draft_id": "uuid",
"draft_title": "Deployment Runbook",
"current_stage": "sme_review",
"stage_display_name": "SME Review",
"workflow_name": "Default",
"space": "INFRA",
"entered_stage_at": "2024-01-15T10:00:00Z"
}
],
"velocity": {
"current_velocity": 1.2,
"velocity_trend": "accelerating",
"grade": "B",
"weekly_snapshots": [
{ "week_start": "2024-01-08", "docs_created": 5, "docs_updated": 12, "gaps_opened": 3, "gaps_resolved": 4, "velocity": 1.1 }
]
},
"top_contributors": [
{ "author_id": "alice", "author_name": "Alice Smith", "fragment_count": 23, "approved_count": 18 }
],
"errors": [],
"generated_at": "2024-01-15T12:00:00Z"
}
Sections that fail to load return null with an entry in errors:
Governance SLAs¶
SLA policies define maximum acceptable times for gap acknowledgment, gap resolution, draft review, and document freshness. Policies can be set per-space or org-wide (default). A periodic background checker detects breaches and emits SlaBreached events.
GET /api/v1/governance/slas¶
List all SLA policies. Requires viewer role.
Response:
{
"policies": [
{
"id": "uuid",
"space": null,
"gap_acknowledgment_hours": 48,
"gap_resolution_days": 14,
"draft_review_hours": 72,
"freshness_review_days": 30,
"created_at": "2025-01-01T00:00:00Z",
"updated_at": "2025-01-01T00:00:00Z"
}
]
}
The space: null row is the org-wide default (always present). Space-specific overrides appear with their space name.
PUT /api/v1/governance/slas/default¶
Upsert the org-wide default SLA policy. Requires admin role.
Request body (all fields optional — omitted fields keep current values):
{
"gap_acknowledgment_hours": 48,
"gap_resolution_days": 14,
"draft_review_hours": 72,
"freshness_review_days": 30
}
Validation: Hours must be 1–8760, days must be 1–365.
PUT /api/v1/governance/slas/:space¶
Upsert a space-specific SLA policy override. Requires admin role. Same request body as above.
DELETE /api/v1/governance/slas/:space¶
Delete a space-specific override (space falls back to org default). Requires admin role. Returns 204 No Content on success, 404 if no override exists for the space.
GET /api/v1/governance/breaches¶
List SLA breaches with optional filters. Requires viewer role.
Query parameters:
| Parameter | Type | Default | Description |
|---|---|---|---|
space |
string | — | Filter by space |
sla_type |
string | — | Filter: acknowledgment, resolution, review, freshness |
open_only |
bool | false |
Only show unacknowledged breaches |
limit |
int | 50 |
Max results (up to 200) |
offset |
int | 0 |
Pagination offset |
Response:
{
"breaches": [
{
"id": "uuid",
"entity_type": "gap",
"entity_id": "uuid",
"sla_type": "acknowledgment",
"space": "ENGINEERING",
"owner_id": null,
"hours_overdue": 12.5,
"acknowledged_at": null,
"acknowledged_by": null,
"created_at": "2025-01-01T00:00:00Z",
"updated_at": "2025-01-01T00:00:00Z"
}
]
}
GET /api/v1/governance/breaches/summary¶
Aggregate breach statistics for dashboards. Requires viewer role.
Response:
{
"total_open": 5,
"total_acknowledged": 12,
"by_type": [
{ "sla_type": "acknowledgment", "count": 3 },
{ "sla_type": "freshness", "count": 2 }
],
"by_space": [
{ "space": "ENGINEERING", "count": 4 },
{ "space": "(org-wide)", "count": 1 }
]
}
POST /api/v1/governance/breaches/:id/acknowledge¶
Mark a breach as acknowledged. Requires editor role. The acknowledging user is recorded. Returns { "acknowledged": true } on success, 404 if the breach doesn't exist or was already acknowledged.
Review Workflows¶
Configurable multi-stage review pipelines for autopilot drafts. Each space can have a workflow that defines approval stages (e.g., SME Review → Writer Review → Publish Approval). Reviewers approve, request changes, or reject drafts at each stage.
GET /api/v1/governance/workflows¶
List all review workflows. Requires viewer role.
Response:
{
"workflows": [
{
"id": "uuid",
"space": "ENGINEERING",
"name": "Standard Review",
"stages": [
{ "name": "sme_review", "required_role": "maintainer", "approvals_needed": 1 },
{ "name": "writer_review", "required_role": "contributor", "approvals_needed": 1 }
],
"is_default": false,
"created_at": "2026-03-22T10:00:00Z",
"updated_at": "2026-03-22T10:00:00Z"
}
]
}
POST /api/v1/governance/workflows¶
Create a review workflow for a space. Each space can have at most one workflow. Requires admin role.
Request body:
{
"space": "ENGINEERING",
"name": "Standard Review",
"stages": [
{ "name": "sme_review", "required_role": "maintainer", "approvals_needed": 1 },
{ "name": "writer_review", "required_role": "contributor", "approvals_needed": 1 }
],
"is_default": false
}
Validation rules:
- Maximum 10 stages per workflow
- Stage names must be unique within a workflow
- required_role must be one of: owner, maintainer, contributor
- approvals_needed must be between 1 and 20
PATCH /api/v1/governance/workflows/:id¶
Update a workflow's name, stages, or default flag. Requires admin role.
DELETE /api/v1/governance/workflows/:id¶
Delete a workflow. Drafts assigned to this workflow retain their current stage but lose the workflow reference (ON DELETE SET NULL). Requires admin role.
POST /api/v1/drafts/:id/review¶
Submit a review action (approve, request changes, or reject) for a draft at its current stage. Requires editor role. The reviewer must also hold the stage's required_role in the draft's space governance.
Request body:
Valid actions: approve, request_changes, reject.
- approve: Counts toward the stage's
approvals_neededthreshold. When the threshold is met, the draft advances to the next stage (or becomesreviewedif it was the final stage). - request_changes: Recorded but does not block approvals. The draft stays at the current stage.
- reject: Immediately sets the draft status to
rejected.
Each reviewer can submit one action per stage. Submitting again updates the existing action (upsert).
Response: 200 OK with {"ok": true, "advanced": true} if the draft advanced to the next stage.
POST /api/v1/drafts/:id/skip-review¶
Admin bypass: skip all remaining review stages and move the draft directly to reviewed status. Requires admin role.
Use this when a draft doesn't need gatekeeping — e.g., trusted content sources, low-risk updates, or time-sensitive documentation.
Response: 200 OK with {"ok": true, "status": "reviewed"}
Returns 404 if the draft is not found or not in a reviewable state (must be status = 'draft').
GET /api/v1/drafts/:id/reviews¶
List all review actions for a draft. Requires viewer role.
GET /api/v1/drafts/:id/stage¶
Get the current review stage progress for a draft, including who has reviewed and how many approvals remain.
Response:
{
"draft_id": "uuid",
"current_stage": "sme_review",
"workflow_id": "uuid",
"progress": {
"stage_name": "sme_review",
"approvals_needed": 2,
"approvals_received": 1,
"actions": [
{
"actor_id": "uuid",
"action": "approve",
"note": "LGTM",
"created_at": "2026-03-22T11:00:00Z"
}
]
}
}
POST /api/v1/drafts/:id/comments¶
Add a review comment to a draft. Requires editor role.
Request body:
Comment body is limited to 10,000 characters. parent_id enables threaded replies.
GET /api/v1/drafts/:id/comments¶
List all comments on a draft, ordered by creation time. Requires viewer role.
PATCH /api/v1/comments/:id¶
Update a comment's body. Only the comment author or an admin can edit. Requires editor role.
POST /api/v1/comments/:id/resolve¶
Mark a comment as resolved. Requires editor role.
GET /api/v1/reviews/my-queue¶
List drafts awaiting the current user's review, based on their space governance roles. Returns drafts where the user holds the required role for the current stage.
Response:
{
"items": [
{
"draft_id": "uuid",
"title": "Getting Started with Kubernetes",
"space": "ENGINEERING",
"current_stage": "sme_review",
"workflow_name": "Standard Review",
"created_at": "2026-03-22T09:00:00Z"
}
]
}
Content Quality Scoring¶
Deterministic structural quality scores for documents and fragments. Each item receives a composite score (0-100) built from 7 sub-scores, with content-type-aware templates defining completeness expectations.
List Quality Scores¶
Paginated list of quality scores with optional filters. Requires viewer role.
Query Parameters:
| Parameter | Type | Default | Description |
|---|---|---|---|
space |
string | — | Filter by space |
min_score |
float | — | Minimum composite score (0-100) |
max_score |
float | — | Maximum composite score (0-100) |
content_type |
string | — | Filter by content type |
limit |
integer | 50 | Max results (capped at 200) |
offset |
integer | 0 | Pagination offset |
Response:
{
"scores": [
{
"id": "uuid",
"document_id": "uuid",
"fragment_id": null,
"heading_structure": 15.0,
"section_completeness": 20.0,
"code_presence": 10.0,
"link_density": 7.0,
"content_length": 8.0,
"readability": 12.0,
"metadata_quality": 10.0,
"composite_score": 82.0,
"scored_at": "2025-01-15T10:30:00Z"
}
]
}
Get Document Score¶
Quality score for a specific document. Requires viewer role.
Response:
{
"score": {
"id": "uuid",
"document_id": "uuid",
"heading_structure": 15.0,
"section_completeness": 20.0,
"code_presence": 10.0,
"link_density": 7.0,
"content_length": 8.0,
"readability": 12.0,
"metadata_quality": 10.0,
"composite_score": 82.0,
"scored_at": "2025-01-15T10:30:00Z"
},
"status": "high"
}
Status values: high (80+), acceptable (60+), needs_improvement (40+), poor (<40).
Trigger Rescore¶
Triggers a rescore of all documents. Returns immediately — rescoring happens asynchronously during the next ingest cycle. Requires admin role.
Response:
{
"status": "accepted",
"documents_to_score": 1234,
"message": "Rescoring will happen during the next ingest cycle"
}
Quality Report¶
Aggregate quality report with per-space breakdown and worst-scoring documents. Requires analyst role.
Response:
{
"overall_avg": 72.5,
"total_scored": 1234,
"by_space": [
{
"space": "ENGINEERING",
"avg_score": 78.3,
"document_count": 450,
"worst_docs": [
{
"document_id": "uuid",
"title": "Legacy Migration Guide",
"composite_score": 23.5
}
]
}
]
}
List Content Type Templates¶
Returns the built-in content type templates that define section completeness expectations. Requires viewer role.
Response:
{
"templates": [
{
"content_type": "runbook",
"required_sections": ["overview", "prerequisites", "steps", "rollback", "escalation"],
"optional_sections": ["monitoring", "troubleshooting"],
"min_word_count": 200,
"max_word_count": 5000,
"expect_code_blocks": true
},
{
"content_type": "guide",
"required_sections": ["introduction", "prerequisites", "steps"],
"optional_sections": ["examples", "faq", "next steps"],
"min_word_count": 300,
"max_word_count": 10000,
"expect_code_blocks": true
}
]
}
Available content types: runbook, guide, troubleshooting, faq, reference.
Sub-Score Breakdown¶
| Sub-Score | Range | What It Measures |
|---|---|---|
heading_structure |
0-20 | Presence of headings, proper hierarchy (H1→H2→H3), no skipped levels |
section_completeness |
0-25 | Required sections present per content type template |
code_presence |
0-10 | Code blocks present when expected by content type |
link_density |
0-10 | Internal/external links for cross-referencing |
content_length |
0-10 | Word count within template-defined min/max range |
readability |
0-15 | Sentence length variation, no wall-of-text paragraphs, manageable sentence lengths |
metadata_quality |
0-10 | Author, source URL, and space metadata present |
Semantic Quality (LLM-Assessed)¶
When semantic quality scoring is enabled, documents are also assessed by an LLM on four dimensions:
| Dimension | Range | Description |
|---|---|---|
accuracy |
0-25 | Claims grounded in cited sources |
completeness |
0-25 | Covers all aspects a reader needs |
clarity |
0-25 | Understandable without external help |
actionability |
0-25 | Provides concrete steps, commands, and examples |
The semantic score (0-100) is stored in the semantic_score field with per-dimension details in semantic_details. The composite_score becomes a 50/50 blend of structural and semantic scores once both are available.
Semantic scoring runs as a background sweep (configurable interval, default 24h) and only evaluates documents with structural_total >= 40 to avoid wasting LLM calls on obviously poor content. Newly generated drafts are scored immediately after creation.
Style Rules Engine¶
Configurable linting rules for documentation consistency. Rules are scoped globally or per-space, with space-specific rules overriding global rules of the same type and name.
List Style Rules¶
Requires viewer role.
Query Parameters:
| Parameter | Type | Default | Description |
|---|---|---|---|
space |
string | — | Filter by space |
rule_type |
string | — | Filter by type: terminology, formatting, structure, custom_pattern |
include_inactive |
boolean | false |
Include inactive rules (admin only) |
Response:
{
"rules": [
{
"id": "uuid",
"space": null,
"rule_type": "terminology",
"name": "avoid-simple",
"description": "Avoid the word 'simple' — it dismisses reader difficulty",
"config": { "term": "simple", "suggestion": "straightforward" },
"severity": "warning",
"is_active": true,
"created_at": "2026-03-22T00:00:00Z",
"updated_at": "2026-03-22T00:00:00Z"
}
]
}
Create Style Rule¶
Requires admin role.
Request Body:
{
"space": null,
"rule_type": "terminology",
"name": "avoid-simple",
"description": "Avoid the word 'simple'",
"config": { "term": "simple", "suggestion": "straightforward" },
"severity": "warning"
}
| Field | Type | Required | Description |
|---|---|---|---|
space |
string | no | Space scope (null = global) |
rule_type |
string | yes | terminology, formatting, structure, custom_pattern |
name |
string | yes | Unique name (1-200 chars) |
description |
string | no | Human-readable description (max 2000 chars) |
config |
object | yes | Rule-type-specific configuration (see below) |
severity |
string | no | error, warning, info (default: warning) |
Config by rule type:
| Rule Type | Config Schema |
|---|---|
terminology |
{ "term": "string", "suggestion": "string" } |
formatting |
{ "max_heading_depth": number } or { "max_sentence_length": number } |
structure |
{ "require_intro": true } |
custom_pattern |
{ "pattern": "regex", "message": "string" } |
Response: 201 Created
Status codes: 400 invalid input, 409 duplicate (space + type + name), 422 rule limit reached.
Update Style Rule¶
Requires admin role. Only provided fields are updated.
Request Body:
{
"description": "Updated description",
"config": { "term": "simple", "suggestion": "clear" },
"severity": "error",
"is_active": false
}
Delete Style Rule¶
Requires admin role. Returns 204 No Content or 404.
Import Rules from YAML¶
Requires admin role. Upserts rules from a YAML string (max 100 rules per import). Existing rules with the same (space, type, name) are updated.
Request Body:
{
"yaml": "- rule_type: terminology\n name: avoid-simple\n config:\n term: simple\n suggestion: straightforward\n severity: warning\n"
}
Response:
Export Rules to YAML¶
Requires admin role. Returns all rules as a YAML document (Content-Type: application/x-yaml).
Lint Content¶
Requires analyst role. Runs all active rules against the provided content.
Request Body:
| Field | Type | Required | Description |
|---|---|---|---|
content |
string | yes | Text to lint (max 500KB) |
space |
string | no | Space for rule scoping (global + space rules apply) |
Response:
{
"violations": [
{
"rule_name": "avoid-simple",
"rule_type": "terminology",
"severity": "warning",
"message": "Avoid 'simple' — consider 'straightforward' instead",
"line": 1,
"column": 11,
"span": "simple"
}
],
"style_score": 95.0,
"summary": {
"errors": 0,
"warnings": 1,
"infos": 0,
"total": 1
},
"truncated": false
}
Style score formula: max(0, 100 - (errors × 15 + warnings × 5 + infos × 1)), clamped to [0, 100].
Limits¶
| Limit | Value |
|---|---|
| Max rules per space | 200 |
| Max total rules | 1000 |
| Max import batch | 100 |
| Max lint content | 500 KB |
| Max violations per lint | 100 |
| Max regex pattern length | 500 chars |
Webhooks¶
Outbound webhook subscriptions for pushing DocBrain events to external systems. All endpoints require admin role.
DocBrain signs every delivery with HMAC-SHA256 (X-DocBrain-Signature header), retries failed deliveries with exponential backoff, and automatically disables subscriptions after repeated failures (circuit breaker).
List Webhook Subscriptions¶
Response:
[
{
"id": "uuid",
"name": "Slack Pipeline",
"url": "https://hooks.example.com/docbrain",
"events": ["document.ingested", "gap.detected"],
"headers": { "X-Custom": "value" },
"is_active": true,
"created_by": "uuid",
"failure_count": 0,
"last_failure_at": null,
"last_success_at": "2026-03-22T10:00:00Z",
"disabled_reason": null,
"created_at": "2026-03-20T08:00:00Z",
"updated_at": "2026-03-22T10:00:00Z"
}
]
Note: The
secretfield is never returned in API responses.
Create Webhook Subscription¶
Request Body:
{
"name": "Slack Pipeline",
"url": "https://hooks.example.com/docbrain",
"secret": "your-secret-at-least-16-chars",
"events": ["document.ingested", "gap.detected"],
"headers": { "X-Custom": "value" }
}
| Field | Type | Required | Description |
|---|---|---|---|
name |
string | yes | Human-readable subscription name |
url |
string | yes | HTTPS endpoint to deliver events to. Must not be a private/internal IP unless ALLOW_INTERNAL_WEBHOOKS=true. |
secret |
string | yes | HMAC signing secret (minimum 16 characters) |
events |
string[] | yes | Event types to subscribe to (see list below) |
headers |
object | no | Extra HTTP headers to include in deliveries |
Response: 201 Created with the WebhookSubscription object.
Validation:
- secret must be at least 16 characters
- events must contain only valid event types
- url must not resolve to a private/internal IP address (unless ALLOW_INTERNAL_WEBHOOKS=true)
Update Webhook Subscription¶
Request Body: (all fields optional)
{
"name": "Updated Name",
"url": "https://new-endpoint.example.com/hook",
"secret": "new-secret-at-least-16-chars",
"events": ["document.ingested"],
"headers": { "X-Custom": "new-value" },
"is_active": true
}
Response: 200 OK with the updated WebhookSubscription object.
Delete Webhook Subscription¶
Response:
Send Test Event¶
Sends a webhook.test event to the subscription's URL. Useful for verifying connectivity and signature validation.
Response:
List Delivery Log¶
Returns recent delivery attempts for a subscription.
Query Parameters:
| Param | Type | Default | Description |
|---|---|---|---|
limit |
integer | 50 |
Max results |
Response: Array of WebhookDelivery objects with delivery ID, event type, HTTP status, response body, attempt number, and timestamps.
Reset Circuit Breaker¶
Resets the failure counter and re-enables a subscription that was auto-disabled by the circuit breaker.
Response:
Event Types¶
All event types from the internal event bus are available for webhook subscriptions:
| Event Type | Description |
|---|---|
document.ingested |
New document indexed |
document.updated |
Existing document re-indexed |
document.deleted |
Document removed |
freshness.changed |
Document freshness status changed |
quality.scored |
Quality score computed or updated |
fragment.captured |
Knowledge fragment captured |
fragment.indexed |
Fragment auto-indexed into search |
fragment.promoted |
Fragment promoted from review queue |
gap.detected |
New gap cluster detected |
gap.assigned |
Gap assigned to a user |
gap.resolved |
Gap marked as resolved |
draft.generated |
AI draft generated for a gap |
draft.review_requested |
Draft sent for review |
draft.published |
Draft published to target system |
draft.rejected |
Draft rejected during review |
query.answered |
User query answered |
feedback.received |
User feedback submitted |
sla.breached |
Documentation SLA breached |
maintenance.fix_proposed |
Automated fix proposal generated |
Delivery Headers¶
Every webhook delivery includes these headers:
| Header | Description |
|---|---|
Content-Type |
application/json |
X-DocBrain-Signature |
HMAC-SHA256 signature: sha256=<hex-digest>. Compute HMAC-SHA256(secret, raw_body) and compare to verify authenticity. |
X-DocBrain-Event |
Event type (e.g. document.ingested) |
X-DocBrain-Delivery |
Unique delivery UUID for idempotency |
Retry Policy¶
Failed deliveries (non-2xx response or timeout) are retried with exponential backoff:
| Attempt | Delay |
|---|---|
| 1 | Immediate |
| 2 | 60 seconds |
| 3 | 300 seconds (5 minutes) |
| 4 | 3600 seconds (1 hour) |
After all retry attempts are exhausted, the failure counter is incremented. When the failure counter reaches WEBHOOK_CIRCUIT_BREAKER_THRESHOLD (default: 10) consecutive failures, the subscription is automatically disabled with disabled_reason: "circuit_breaker". Use POST /api/v1/webhooks/:id/reset to re-enable.
External Connectors¶
External connectors are stateless HTTP servers that implement a simple REST contract. DocBrain calls them on a cron schedule to ingest documents. All management endpoints require admin role. Status endpoint requires viewer+ role.
List Connectors¶
Response:
[
{
"id": "uuid",
"name": "internal-wiki",
"display_name": "Internal Wiki",
"base_url": "https://wiki-connector.example.com",
"source_type": "wiki",
"schedule_cron": "0 */6 * * *",
"space": "engineering",
"is_active": true,
"last_sync_at": "2026-03-24T06:00:00Z",
"last_sync_docs": 42,
"last_error": null,
"consecutive_failures": 0,
"created_at": "2026-03-20T08:00:00Z",
"updated_at": "2026-03-24T06:00:00Z"
}
]
Note: The
auth_headerfield is never returned in API responses.
Create Connector¶
Request Body:
{
"name": "internal-wiki",
"display_name": "Internal Wiki",
"base_url": "https://wiki-connector.example.com",
"auth_header": "Bearer your-token",
"source_type": "wiki",
"schedule_cron": "0 */6 * * *",
"space": "engineering"
}
| Field | Type | Required | Description |
|---|---|---|---|
name |
string | yes | Unique machine name (1-100 chars) |
display_name |
string | yes | Human-readable name (1-100 chars) |
base_url |
string | yes | Connector HTTP endpoint (1-2048 chars, must start with http:// or https://) |
auth_header |
string | no | Authorization header value (e.g. Bearer token) |
source_type |
string | yes | Document source type (1-20 chars, must be unique across connectors) |
schedule_cron |
string | no | Standard 5-field cron expression (default: 0 */6 * * * = every 6 hours) |
space |
string | no | Target space for ingested documents |
Response: 201 Created with the connector object.
Validation:
- base_url must not resolve to a private/internal IP address (unless CONNECTOR_ALLOW_INTERNAL=true)
- schedule_cron must be a valid cron expression
- source_type must be unique — returns 409 Conflict if already registered
Update Connector¶
Request Body: (all fields optional)
{
"display_name": "Updated Wiki",
"base_url": "https://new-url.example.com",
"auth_header": "Bearer new-token",
"source_type": "wiki",
"schedule_cron": "0 */12 * * *",
"space": "docs",
"is_active": true
}
Response: 200 OK with the updated connector object. Returns 404 if not found.
Delete Connector¶
Response: 200 OK with {"deleted": true}. Returns 404 if not found.
Trigger Manual Sync¶
Triggers an immediate sync for the connector, bypassing the cron schedule and circuit breaker. Returns an error if a sync is already in progress for this connector.
Response:
| Status | Meaning |
|---|---|
| 200 | Sync completed successfully |
| 404 | Connector not found |
| 409 | Sync already in progress |
Connector Status¶
Returns sync health information for a connector. Requires viewer+ role.
Response:
{
"id": "uuid",
"name": "internal-wiki",
"is_active": true,
"last_sync_at": "2026-03-24T06:00:00Z",
"last_sync_docs": 42,
"last_error": null,
"consecutive_failures": 0
}
Test Connector Health¶
Runs a health check against the connector's /health endpoint without triggering a sync.
Response:
If the health check fails:
Connector Protocol¶
External connectors must implement three endpoints:
| Endpoint | Method | Description |
|---|---|---|
/health |
GET | Returns {"status": "ok"} and optionally {"version": "..."} |
/documents/list |
POST | Lists available documents (paginated, supports incremental sync via since) |
/documents/fetch |
POST | Returns full document content for a list of source IDs |
List Request:
List Response:
{
"documents": [
{ "source_id": "doc-123", "title": "Getting Started", "updated_at": "2026-03-24T10:00:00Z" }
],
"has_more": false,
"total": 1
}
Fetch Request:
Fetch Response:
{
"documents": [
{
"source_id": "doc-123",
"title": "Getting Started",
"content": "# Getting Started\n\nWelcome to...",
"content_type": "markdown",
"url": "https://wiki.example.com/getting-started",
"author": "Jane Doe",
"updated_at": "2026-03-24T10:00:00Z",
"metadata": {},
"references": []
}
]
}
CI/CD Pipeline Capture¶
Automated knowledge extraction from merged PRs and deployments. Requires editor role.
POST /api/v1/ci/analyze¶
Analyze a merged PR and extract knowledge fragments.
Request body:
{
"pr_number": 1234,
"repo": "acme/platform",
"pr_title": "Switch event delivery to PG LISTEN/NOTIFY",
"pr_body": "Replaced Redis pub/sub with PostgreSQL...",
"diff_stat": "+120 -45",
"changed_files": "src/events/publisher.rs,src/events/subscriber.rs",
"labels": "architecture,breaking-change",
"author": "alice@acme.com"
}
| Field | Type | Required | Description |
|---|---|---|---|
pr_number |
integer | yes | Pull request number |
repo |
string | yes | Repository in owner/name format |
pr_title |
string | yes | PR title (max 500 chars) |
pr_body |
string | no | PR description (max 50,000 chars) |
diff_stat |
string | no | Git diff stats |
changed_files |
string | no | Comma-separated list of changed files |
labels |
string | no | Comma-separated PR labels |
author |
string | no | PR author email or username |
Response:
{
"fragments_created": 2,
"fragments": [
{
"id": "550e8400-e29b-41d4-a716-446655440000",
"fragment_type": "decision",
"summary": "Switched event delivery from Redis to PG LISTEN/NOTIFY",
"confidence": 0.85,
"routed_action": "auto_index"
}
],
"already_analyzed": false
}
| Status | Description |
|---|---|
| 200 | Analysis complete (check fragments_created and already_analyzed) |
| 400 | Validation error (missing required field, field too long) |
| 403 | Insufficient role (requires editor+) |
| 503 | CI analysis is disabled (CI_ANALYZE_ENABLED=false) |
The endpoint is idempotent: re-analyzing the same PR returns already_analyzed: true with no new fragments.
POST /api/v1/ci/deploy-capture¶
Capture deployment context as a knowledge fragment.
Request body:
{
"service": "payment-gateway",
"version": "2.4.1",
"environment": "production",
"changelog": "abc1234 Fixed retry logic\ndef5678 Updated timeout config",
"config_diff": "timeout: 30 -> 60"
}
| Field | Type | Required | Description |
|---|---|---|---|
service |
string | yes | Service name (max 256 chars) |
version |
string | yes | Version being deployed (max 128 chars) |
environment |
string | yes | Target environment (max 128 chars) |
changelog |
string | no | Git changelog (max 50,000 chars) |
config_diff |
string | no | Configuration changes (max 50,000 chars) |
Response:
{
"fragment_id": "550e8400-e29b-41d4-a716-446655440000",
"summary": "Deployed payment-gateway v2.4.1 to production with retry logic fix"
}
| Status | Description |
|---|---|
| 200 | Capture complete |
| 400 | Validation error |
| 403 | Insufficient role |
| 503 | CI analysis is disabled |
GitHub Action Setup¶
Add this workflow to your repository to automatically capture knowledge from merged PRs:
name: DocBrain Knowledge Capture
on:
pull_request:
types: [closed]
jobs:
capture:
if: github.event.pull_request.merged == true
runs-on: ubuntu-latest
steps:
- uses: actions/checkout@v4
with:
fetch-depth: 2
- name: Capture knowledge from PR
env:
DOCBRAIN_API_URL: ${{ secrets.DOCBRAIN_API_URL }}
DOCBRAIN_API_KEY: ${{ secrets.DOCBRAIN_API_KEY }}
run: |
curl -s -X POST "${DOCBRAIN_API_URL}/api/v1/ci/analyze" \
-H "Authorization: Bearer ${DOCBRAIN_API_KEY}" \
-H "Content-Type: application/json" \
-d "$(jq -n \
--argjson pr_number ${{ github.event.pull_request.number }} \
--arg repo '${{ github.repository }}' \
--arg pr_title '${{ github.event.pull_request.title }}' \
'{pr_number: $pr_number, repo: $repo, pr_title: $pr_title}')"
Required repository secrets:
- DOCBRAIN_API_URL — Your DocBrain server URL
- DOCBRAIN_API_KEY — API key with editor+ role