| Server IP : 35.80.110.71 / Your IP : 216.73.216.221 Web Server : Apache/2.4.58 (Ubuntu) System : Linux ip-172-31-21-44 6.17.0-1019-aws #19~24.04.1-Ubuntu SMP Tue Jun 23 18:53:06 UTC 2026 x86_64 User : ubuntu ( 1000) PHP Version : 8.3.31 Disable Function : NONE MySQL : OFF | cURL : ON | WGET : ON | Perl : ON | Python : OFF | Sudo : ON | Pkexec : OFF Directory : /var/www/almanac-web/public/ |
Upload File : |
openapi: 3.1.0
info:
title: Almanac API
version: 0.1.0
summary: Internal-docs RAG chatbot with citations, ACL-aware retrieval, prompt-injection defenses, and a no-answer gap report.
description: |
Almanac answers questions about a workspace's documents using only
the supplied source documents, returns answers with inline citations,
and respects source-side permissions.
Authentication is via `Authorization: Bearer alm_…` API keys. Keys
carry one of four scopes: `chat_only`, `chat_and_feedback`,
`admin_read`, `admin_write`. Higher scopes include lower ones.
All errors follow RFC 7807 problem-details. Responses are JSON by
default; the chat endpoint also supports SSE streaming when
`Accept: text/event-stream`.
contact:
name: Philip Rehberger
url: https://almanac.philiprehberger.com
license:
name: MIT
identifier: MIT
servers:
- url: https://api.almanac.philiprehberger.com
description: Production
- url: http://localhost:8000
description: Local development
security:
- bearerAuth: []
tags:
- name: Meta
description: Service liveness and metadata.
- name: Chat
description: Ask questions, submit feedback, and inspect conversation history and cited sources.
- name: Connectors
description: Manage source connectors and trigger ingest runs.
- name: GapReport
description: Clusters of unanswered questions surfaced from low-confidence responses.
- name: Audit
description: Audit log, prompt-injection signals, and per-workspace cost dashboards.
- name: Admin
description: API keys and right-to-be-forgotten deletion requests.
paths:
/v1/healthz:
get:
summary: Liveness probe
description: Returns service status and version. Unauthenticated.
operationId: getHealthz
security: []
tags: [Meta]
responses:
'200':
description: Healthy
content:
application/json:
schema:
type: object
properties:
status: { type: string }
version: { type: string }
/v1/chat:
post:
summary: Ask a question
operationId: postChat
description: |
Runs the full RAG pipeline: principal-set materialization →
ACL-aware retrieval → tagged-content prompt assembly → LLM call
(structured output) → output filter → response.
When `Accept: text/event-stream`, the response streams as SSE:
`event: token`, `event: citation`, `event: meta`, `event: done`
(or `event: error`). Citations arrive inline at the position the
model emitted them.
tags: [Chat]
requestBody:
required: true
content:
application/json:
schema:
$ref: '#/components/schemas/ChatRequest'
responses:
'200':
description: Answer
content:
application/json:
schema: { $ref: '#/components/schemas/ChatResponse' }
text/event-stream:
schema:
type: string
'402':
description: Workspace cost cap exceeded
content:
application/problem+json:
schema: { $ref: '#/components/schemas/Problem' }
/v1/feedback:
post:
summary: Submit thumbs-up / thumbs-down feedback for a query
description: Records a verdict and optional comment against a prior `query_id`. Feeds the gap report and quality dashboards.
operationId: postFeedback
tags: [Chat]
requestBody:
required: true
content:
application/json:
schema:
type: object
required: [query_id, verdict]
properties:
query_id: { type: string, minLength: 26, maxLength: 26 }
verdict: { type: string, enum: [up, down] }
comment: { type: string, maxLength: 2000 }
responses:
'201': { description: Created }
/v1/conversations/{id}:
get:
summary: Get a conversation's history
description: Returns the ordered turn history for a conversation, scoped to the caller's workspace.
operationId: getConversation
tags: [Chat]
parameters:
- { in: path, name: id, required: true, schema: { type: string } }
responses:
'200': { description: Conversation }
/v1/sources/{docId}:
get:
summary: Source metadata for a cited document — ACL-checked
description: Returns title, source URL, and connector for a document. Returns `404` if the caller's principal set does not grant access.
operationId: getSource
tags: [Chat]
parameters:
- { in: path, name: docId, required: true, schema: { type: string } }
responses:
'200': { description: Source }
/v1/connectors:
get:
summary: List connectors
description: Lists all source connectors configured for the workspace, including their kind, label, and last ingest summary.
operationId: listConnectors
tags: [Connectors]
responses:
'200': { description: List }
post:
summary: Create a connector
description: Registers a new source connector. Credentials supplied in `config` are encrypted at rest; the first ingest run is queued asynchronously.
operationId: createConnector
tags: [Connectors]
requestBody:
required: true
content:
application/json:
schema:
type: object
required: [kind]
properties:
kind: { type: string, enum: [drive, notion, slack] }
label: { type: string, maxLength: 120 }
config: { type: object, additionalProperties: true }
responses:
'201': { description: Created }
/v1/connectors/{id}/reindex:
post:
summary: Trigger a reindex
description: Queues an ingest run for the connector. `incremental` (default) only fetches changes since the last cursor; `full` re-fetches every document.
operationId: reindexConnector
tags: [Connectors]
parameters:
- { in: path, name: id, required: true, schema: { type: string } }
requestBody:
content:
application/json:
schema:
type: object
properties:
mode: { type: string, enum: [incremental, full], default: incremental }
responses:
'202': { description: Accepted }
/v1/ingest-runs:
get:
summary: Recent ingest runs across all connectors
description: Returns recent ingest-run records with status, document counts, and error summaries. Used by the admin dashboard's ingest health panel.
operationId: listIngestRuns
tags: [Connectors]
responses:
'200': { description: List }
/v1/gap-report:
get:
summary: Clusters of unanswered questions
description: Returns clusters of low-confidence or no-answer queries, ranked by frequency. The output is what an internal-docs team should write next.
operationId: getGapReport
tags: [GapReport]
responses:
'200': { description: Clusters }
/v1/prompt-injection-signals:
get:
summary: Output-filter trip events
description: Lists events where the output filter detected suspected prompt-injection content in retrieved sources or model output.
operationId: listPromptInjectionSignals
tags: [Audit]
responses:
'200': { description: List }
/v1/audit-log:
get:
summary: Workspace audit log
description: Returns a paginated audit-log feed for the workspace. Filterable by `subject_type` and cursor-paginated.
operationId: getAuditLog
tags: [Audit]
parameters:
- { in: query, name: cursor, schema: { type: string } }
- { in: query, name: subject_type, schema: { type: string } }
- { in: query, name: limit, schema: { type: integer, minimum: 1, maximum: 100 } }
responses:
'200': { description: Page }
/v1/cost:
get:
summary: Per-workspace cost dashboard
description: Returns per-workspace LLM cost and token usage rollups against the configured monthly cap.
operationId: getCost
tags: [Audit]
responses:
'200': { description: Cost }
/v1/api-keys:
get:
summary: List API keys
description: Lists API keys for the workspace. Secrets are never returned after the initial mint — only metadata (label, scope, prefix, last-used).
operationId: listApiKeys
tags: [Admin]
responses:
'200': { description: List }
post:
summary: Mint a new API key (secret returned once)
description: Creates a new API key with the given scope and optional IP allowlist. The full `alm_…` secret is returned exactly once in the response body.
operationId: createApiKey
tags: [Admin]
requestBody:
required: true
content:
application/json:
schema:
type: object
required: [scope]
properties:
name: { type: string }
scope: { type: string, enum: [chat_only, chat_and_feedback, admin_read, admin_write] }
ip_allowlist: { type: array, items: { type: string } }
expires_at: { type: string, format: date-time }
responses:
'201': { description: Created — secret returned once }
/v1/deletion-requests:
post:
summary: Right-to-be-forgotten — async sweep
description: Queues an async deletion sweep for a subject user across queries (or queries plus all associated artifacts). Returns `202` with a tracking id.
operationId: createDeletionRequest
tags: [Admin]
requestBody:
required: true
content:
application/json:
schema:
type: object
required: [subject_user_external_id]
properties:
subject_user_external_id: { type: string }
scope: { type: string, enum: [queries, all] }
responses:
'202': { description: Queued }
components:
securitySchemes:
bearerAuth:
type: http
scheme: bearer
bearerFormat: alm_…
schemas:
ChatRequest:
type: object
required: [query]
properties:
query: { type: string, minLength: 2, maxLength: 8000 }
conversation_id: { type: string, minLength: 26, maxLength: 26, nullable: true }
as_principal:
type: array
items:
type: object
required: [kind, id]
properties:
kind: { type: string, enum: [user, group, workspace, public] }
id: { type: string }
caller_label: { type: string, maxLength: 120, nullable: true }
caller_external_id: { type: string, maxLength: 200, nullable: true }
provider: { type: string, enum: [mock, openai, anthropic, ollama], nullable: true }
ChatResponse:
type: object
required: [query_id, conversation_id, answer, citations, confidence]
properties:
query_id: { type: string }
conversation_id: { type: string }
answer: { type: string }
citations:
type: array
items: { $ref: '#/components/schemas/Citation' }
confidence: { type: string, enum: [low, high] }
confidence_reason:
type: string
enum: [model_unsure, score_thin, acl_thin]
nullable: true
cost_usd: { type: number, format: float }
tokens_in: { type: integer }
tokens_out: { type: integer }
latency_ms: { type: integer }
Citation:
type: object
required: [marker, chunk_id]
properties:
marker: { type: string, example: "[1]" }
chunk_id: { type: string }
document_id: { type: string }
source_url: { type: string, format: uri }
title: { type: string }
snippet: { type: string }
Problem:
type: object
properties:
type: { type: string, format: uri }
title: { type: string }
status: { type: integer }
detail: { type: string }
instance: { type: string }