API reference
Every endpoint that ships with Pellucid today, with copy-paste cURL, Python, and JavaScript examples.
All endpoints sit under /api/v1. The default base URL in development is http://localhost:8000. Replace it with your deployment’s origin in production.
Conventions
- Identifiers. Documents, findings, and other rows use opaque string IDs (typically prefixed, e.g.
doc_…,find_…). - Timestamps. ISO-8601 UTC. Stored naive in SQLite, emitted with a trailing
Z. - Errors. Standard HTTP statuses. Error bodies are
{ "detail": "..." }. - Auth. Currently unauthenticated. The bearer keys minted via integrations are scaffolding for the auth middleware on the roadmap.
Health
/health#Liveness probe. Useful for Docker, k8s, and load balancers.
curl http://localhost:8000/health{
"status": "ok",
"service": "pellucid-api"
}Documents
Documents are the unit of analysis. Create one, then call analyze on it. A document keeps its content and any findings produced by the most recent analyze run; re-running analyze wipes the prior findings and debate.
/api/v1/documents#Create a document from inline content. Returns the persisted row.
contentstringrequiredThe document body. Plain text or markdown; min length 1.titlestring?Optional title. Falls back to the first non-empty line, trimmed to 80 chars.
curl -X POST http://localhost:8000/api/v1/documents \
-H "Content-Type: application/json" \
-d '{
"title": "Vehicle Telematics SRS",
"content": "The system shall be user-friendly and respond quickly."
}'{
"id": "doc_01HZ...",
"title": "Vehicle Telematics SRS",
"content": "The system shall be user-friendly and respond quickly.",
"created_at": "2025-04-08T17:21:09Z",
"updated_at": "2025-04-08T17:21:09Z"
}/api/v1/documents/upload#Multipart upload for .pdf, .docx, .txt, or .md. Server parses to clean text.
filemultipart/form-datarequiredOne of .pdf, .docx, .txt, .md. 415 if the type isn't supported.
curl -X POST http://localhost:8000/api/v1/documents/upload \
-F "file=@./srs.pdf"{
"id": "doc_01HZ...",
"title": "srs",
"content": "1. Introduction\nThe system shall...",
"created_at": "...",
"updated_at": "..."
}/api/v1/documents#List up to 50 documents, newest-updated first.
curl http://localhost:8000/api/v1/documents[
{
"id": "doc_01HZ...",
"title": "Vehicle Telematics SRS",
"content": "...",
"created_at": "...",
"updated_at": "..."
}
]/api/v1/documents/{id}#Fetch a single document with its current findings array embedded.
idstringrequiredDocument id.
curl http://localhost:8000/api/v1/documents/doc_01HZ...{
"id": "doc_01HZ...",
"title": "Vehicle Telematics SRS",
"content": "The system shall be user-friendly...",
"created_at": "...",
"updated_at": "...",
"findings": [
{
"id": "find_01J0...",
"document_id": "doc_01HZ...",
"start": 16,
"end": 29,
"text": "user-friendly",
"type": "vague_term",
"severity": "high",
"rationale": "Subjective adjective with no measurable acceptance criterion.",
"confidence": 0.92,
"source": "rules",
"suggestion": null,
"created_at": "..."
}
]
}/api/v1/documents/{id}#Replace a document's content. Used by the live-edit (?edit=1) surface.
Note. PUT does not invalidate findings — the live-edit client is responsible for re-running analyze, which wipes them itself.
idstringrequiredDocument id.contentstringrequiredNew body.titlestring?Optional rename.
curl -X PUT http://localhost:8000/api/v1/documents/doc_01HZ... \
-H "Content-Type: application/json" \
-d '{ "content": "The system shall present a labelled control..." }'{
"id": "doc_01HZ...",
"title": "...",
"content": "...",
"created_at": "...",
"updated_at": "..."
}/api/v1/documents/{id}#Hard-delete a document and its findings, debates, and shares.
idstringrequiredDocument id.
curl -X DELETE http://localhost:8000/api/v1/documents/doc_01HZ...Analyze
/api/v1/documents/{id}/analyze#Open a Server-Sent Events stream of findings. Wipes prior results.
The stream emits, in order: rule_findings, several agent_findings, one or more critique, final, then done. On error, an error event is emitted and the stream closes. See the streaming events table for full payload shapes.
idstringrequiredDocument id.
# -N disables curl's output buffering so events render live
curl -N -X POST http://localhost:8000/api/v1/documents/doc_01HZ.../analyzeFindings
/api/v1/findings/{id}/rewrite#Generate 2–3 candidate rewrites for a finding via the configured LLM.
idstringrequiredFinding id.
curl -X POST http://localhost:8000/api/v1/findings/find_01J0.../rewrite{
"finding_id": "find_01J0...",
"candidates": [
{ "text": "The system shall present a labelled control...", "rationale": "..." },
{ "text": "The system shall complete every action within 200 ms...", "rationale": "..." }
]
}/api/v1/findings/{id}/apply#Replace the finding's span in the document with the given text.
Returns 200 with the updated document. On success the server also clears every finding + the persisted debate transcript for this document — every offset past the edit point shifted by len(replacement) - len(original), so the previous analysis is stale. Re-trigger POST /analyze to repopulate findings against the cleaned-up text.
Refuses with 409 when document[finding.start:finding.end] != finding.text — the document was edited under us and applying would corrupt it. 422 on empty replacement; 404 on missing finding or document.
idstringrequiredFinding id.
curl -X POST http://localhost:8000/api/v1/findings/find_01J0.../apply \
-H "Content-Type: application/json" \
-d '{"replacement":"operable by a novice in under one minute"}'{
"id": "doc_01HZ...",
"title": "Vehicle Telematics System",
"content": "The system shall be operable by a novice in under one minute and provide optimal performance...",
"created_at": "2026-05-09T12:34:00Z",
"updated_at": "2026-05-10T09:15:42Z"
}Debate
/api/v1/debate/{document_id}#Fetch the full multi-agent transcript persisted by the most recent analyze.
document_idstringrequiredThe document the analysis ran against.
curl http://localhost:8000/api/v1/debate/doc_01HZ...{
"document_id": "doc_01HZ...",
"transcript": {
"reports": [
{
"agent": "lexical",
"findings": [ { "start": 16, "end": 29, "text": "user-friendly", "type": "vague_term", ... } ],
"reasoning": "Two adjectives flagged...",
"self_confidence": 0.85
}
],
"critiques": [
{ "voter": "domain", "target_agent": "lexical", "target_finding_index": 0, "agree": true, "reasoning": "Concur — no measurable criterion." }
],
"consensus_finding_ids": ["find_01J0..."]
},
"created_at": "..."
}Settings · Provider
Configure the active LLM provider. See BYOK for the long form.
/api/v1/settings/provider#Return the active provider, whether a key is present, and where it came from.
curl http://localhost:8000/api/v1/settings/provider{
"model": "anthropic/claude-sonnet-4-6",
"has_key": true,
"source": "byok"
}/api/v1/settings/provider#Save a model + API key. Plaintext is encrypted at rest with Fernet.
modelstringrequiredLiteLLM model identifier.api_keystringrequiredPlaintext key. Encrypted before persistence.
curl -X POST http://localhost:8000/api/v1/settings/provider \
-H "Content-Type: application/json" \
-d '{
"model": "anthropic/claude-sonnet-4-6",
"api_key": "sk-ant-..."
}'{ "model": "anthropic/...", "has_key": true, "source": "byok" }/api/v1/settings/provider#Drop the BYOK row. Pellucid falls back to the matching env var.
curl -X DELETE http://localhost:8000/api/v1/settings/provider{ "model": "xai/grok-4", "has_key": false, "source": "env" }Audit
/api/v1/audit/{document_id}.json#Download the full audit trail (document, findings, debate) as a JSON file.
document_idstringrequiredDocument id.
curl -O -J http://localhost:8000/api/v1/audit/doc_01HZ....json/api/v1/audit/{document_id}.md#Same as above, rendered as a human-readable Markdown report.
document_idstringrequiredDocument id.
curl -O -J http://localhost:8000/api/v1/audit/doc_01HZ....mdShare + badge
Public read-only document views and an embeddable clarity-score SVG.
/api/v1/badge/{document_id}.svg#Embed-friendly clarity score SVG. CORS-open, cached for 60s + SWR.
document_idstringrequiredDocument id.
# Markdown:
Glossary
Approved-term registry that the rule layer cross-references. Use it to enforce canonical terminology across a team.
/api/v1/glossary#List every glossary entry, ordered by term.
curl http://localhost:8000/api/v1/glossary[
{
"id": "gloss_01J...",
"term": "Telematics Unit",
"definition": "Embedded device that...",
"synonyms": ["TCU"],
"disallowed": ["unit", "device"]
}
]/api/v1/glossary#Create a glossary entry.
termstringrequiredCanonical term.definitionstringrequiredPlain-English meaning.synonymsstring[]Equivalent forms — accepted, no rewrite.disallowedstring[]Forms that should be rewritten to the canonical term.
curl -X POST http://localhost:8000/api/v1/glossary \
-H "Content-Type: application/json" \
-d '{
"term": "Telematics Unit",
"definition": "Embedded vehicle device.",
"synonyms": ["TCU"],
"disallowed": ["unit", "device"]
}'/api/v1/glossary/{id}#Fetch one glossary entry.
curl http://localhost:8000/api/v1/glossary/gloss_01J.../api/v1/glossary/{id}#Replace an entry. Same body as POST.
curl -X PUT http://localhost:8000/api/v1/glossary/gloss_01J... \
-H "Content-Type: application/json" \
-d '{ "term": "...", "definition": "...", "synonyms": [], "disallowed": [] }'/api/v1/glossary/{id}#Delete an entry. 404 if it doesn't exist.
curl -X DELETE http://localhost:8000/api/v1/glossary/gloss_01J...Custom rules
User-authored regex rules layered on top of the built-in detectors. The rule layer compiles them once per process and runs them on every analyze.
/api/v1/rules#List all custom rules, newest first.
curl http://localhost:8000/api/v1/rules/api/v1/rules#Create a custom rule. The pattern is compiled at write-time; bad regex returns 422.
patternstringrequiredRegex (Python re syntax). 1–4000 chars.flagsstringSubset of i, m, s, x — case-insensitive, multiline, dotall, verbose.typeFindingTyperequiredOne of vague_term, passive_voice, etc. See /api/v1/rules/_meta.severitySeverityrequiredlow | medium | high | critical.rationalestringrequiredWhy this match is a finding. Surfaces in the UI.enabledbooleanDefaults to true.
curl -X POST http://localhost:8000/api/v1/rules \
-H "Content-Type: application/json" \
-d '{
"pattern": "(?i)\\b(easy|simple|intuitive)\\b",
"flags": "",
"type": "vague_term",
"severity": "medium",
"rationale": "Subjective UX adjectives — replace with a measurable affordance."
}'/api/v1/rules/{id}#Replace a custom rule.
curl -X PUT http://localhost:8000/api/v1/rules/rule_01J... -H "Content-Type: application/json" -d '{...}'/api/v1/rules/{id}#Delete a custom rule. 404 if missing.
curl -X DELETE http://localhost:8000/api/v1/rules/rule_01J.../api/v1/rules/_meta#Enumerate the allowed finding types, severities, and regex flags.
curl http://localhost:8000/api/v1/rules/_meta{
"types": ["vague_term", "passive_voice", "pronoun_antecedent", ...],
"severities": ["low", "medium", "high", "critical"],
"flags": ["i", "m", "s", "x"]
}Templates
Read-only registry of bundled document templates. The web app uses these to seed the editor; you can use them to bootstrap synthetic test data.
/api/v1/templates#List every bundled template summary.
curl http://localhost:8000/api/v1/templates{
"templates": [
{ "slug": "saas-srs", "name": "SaaS SRS", "industry": "Software", "description": "..." }
]
}/api/v1/templates/{slug}#Fetch a template including its full markdown body.
curl http://localhost:8000/api/v1/templates/saas-srsCompare
/api/v1/compare#Diff two documents' findings. Returns clarity scores, deltas, and matched spans.
leftstringrequiredOlder document id.rightstringrequiredNewer document id.
curl "http://localhost:8000/api/v1/compare?left=doc_a&right=doc_b"Analytics
/api/v1/analytics#Dashboard payload — totals, severity histogram, agent agreement-rate proxy.
curl http://localhost:8000/api/v1/analyticsOntology
Approved-reference documents and the term graph extracted from them. Endpoints respond even when Neo4j is unconfigured — extraction runs in-memory so the UI can preview ingestion without a backing graph DB.
/api/v1/ontology/documents#Mark a document as an approved reference and ingest its terms.
document_idstring?Existing document id. Either this or content is required.titlestring?Optional override.contentstring?Inline content. Creates a Document row when document_id is omitted.
curl -X POST http://localhost:8000/api/v1/ontology/documents \
-H "Content-Type: application/json" \
-d '{ "document_id": "doc_01HZ..." }'{
"document_id": "doc_01HZ...",
"title": "ISO 26262 — vehicle safety",
"graph_enabled": true,
"term_count": 142,
"terms": [{ "lemma": "telematics", "frequency": 12, ... }]
}/api/v1/ontology/terms#List extracted terms with frequencies.
limitinteger1–1000. Defaults to 200.
curl "http://localhost:8000/api/v1/ontology/terms?limit=50"/api/v1/ontology/term/{term}#Look up ontology context for a single term — synonyms, parents, frequency.
curl http://localhost:8000/api/v1/ontology/term/telematicsIntegrations · API keys
API keys are minted server-side, returned in plaintext exactly once, and persisted only as a SHA-256 hash. Use them as the bearer credential when the auth middleware lands.
/api/v1/integrations/api-keys#Mint a new key. Returns the plaintext exactly once.
labelstringrequiredHuman-readable label, 1–128 chars.
curl -X POST http://localhost:8000/api/v1/integrations/api-keys \
-H "Content-Type: application/json" \
-d '{ "label": "CI bot" }'{
"id": "key_01J...",
"label": "CI bot",
"created_at": "...",
"last_used_at": null,
"revoked_at": null,
"plaintext": "pk_live_abcdEFGHijklMNO123..."
}/api/v1/integrations/api-keys#List minted keys. Plaintext is never included here.
curl http://localhost:8000/api/v1/integrations/api-keys/api/v1/integrations/api-keys/{id}#Hard-delete a key.
curl -X DELETE http://localhost:8000/api/v1/integrations/api-keys/key_01J...Integrations · Webhooks
/api/v1/integrations/webhooks#Register an outbound webhook target.
labelstringrequiredDisplay name.urlurlrequiredReceiver URL. Must be http(s)://.kind"generic" | "slack" | "discord"Selects the payload shape. Defaults to generic.secretstring?Optional shared secret. Encrypted at rest; used for HMAC signing.enabledbooleanDefaults to true.
curl -X POST http://localhost:8000/api/v1/integrations/webhooks \
-H "Content-Type: application/json" \
-d '{
"label": "Eng Slack",
"url": "https://hooks.slack.com/services/T0/B0/XXX",
"kind": "slack"
}'/api/v1/integrations/webhooks#List registered webhooks.
curl http://localhost:8000/api/v1/integrations/webhooks/api/v1/integrations/webhooks/{id}#Hard-delete a webhook.
curl -X DELETE http://localhost:8000/api/v1/integrations/webhooks/wh_01J.../api/v1/integrations/webhooks/{id}/test#Fire a synthetic ping at the receiver. Useful for verifying signing.
curl -X POST http://localhost:8000/api/v1/integrations/webhooks/wh_01J.../test{ "status": 200, "error": null }Collaboration
Real-time multi-user editing piggy-backs on Yjs over a per-document WebSocket. The route is purely a relay — no auth, no message validation beyond passthrough.
/api/v1/collab/{document_id}#WebSocket endpoint. Clients should be y-websocket compatible.
import * as Y from "yjs";
import { WebsocketProvider } from "y-websocket";
const ydoc = new Y.Doc();
const ws = new WebsocketProvider(
"ws://localhost:8000/api/v1/collab",
docId,
ydoc,
);
ws.on("status", (e) => console.log(e.status));Next steps
- Webhook spec — full payloads + HMAC verification snippets.
- Browser & VS Code extensions — see this API in action from outside the web app.
- Self-hosting — Postgres, Neo4j, and Docker Compose.