Files
relata/docs/openapi.yaml
T
2026-08-11 10:40:10 +01:00

1350 lines
51 KiB
YAML

openapi: 3.0.3
info:
title: Relata API
version: "1.0.0"
description: |
Relata's REST interface.
**Authentication.** Every endpoint requires a session cookie obtained by posting the
browser login form at `POST /login`. There is no separate API key.
**CSRF.** State-changing requests (POST, PATCH, DELETE) must carry the session's CSRF
token in the `X-CSRF-Token` header. The token is rendered into every page as a hidden
`csrf_token` field.
**Three different scores.** Responses report `query_relevance`, `semantic_similarity` and
`relationship_strength` separately. None is a probability, and they must not be compared
with one another. See docs/SEARCH_AND_SCORING.md.
servers:
- url: http://127.0.0.1:8080
description: Default local instance
tags:
- name: Items
- name: Search
- name: Relationships
- name: Entities
- name: Jobs
- name: Health
security:
- sessionCookie: []
paths:
/api/v1/items:
post:
tags: [Items]
summary: Store pasted text or a JSON document
description: |
The content type is detected from the content itself; `mime_type` is a hint only.
Identical content returns 409 with the existing item's ID unless `allow_duplicate`
is set.
parameters: [{$ref: '#/components/parameters/CSRFToken'}]
requestBody:
required: true
content:
application/json:
schema: {$ref: '#/components/schemas/CreateItemRequest'}
responses:
'202':
description: Accepted and queued for processing
content:
application/json:
schema: {$ref: '#/components/schemas/ItemAccepted'}
'400': {$ref: '#/components/responses/BadRequest'}
'401': {$ref: '#/components/responses/Unauthorised'}
'403': {$ref: '#/components/responses/Forbidden'}
'409': {$ref: '#/components/responses/Duplicate'}
'413': {$ref: '#/components/responses/TooLarge'}
get:
tags: [Items]
summary: List items
parameters:
- {name: source_type, in: query, schema: {type: string}, description: 'Comma-separated. An unrecognised value is rejected.'}
- {name: mime_type, in: query, schema: {type: string}}
- {name: status, in: query, schema: {type: string}}
- {name: title, in: query, schema: {type: string}, description: 'Literal substring; metacharacters are escaped.'}
- {name: content_hash, in: query, schema: {type: string}}
- {name: created_from, in: query, schema: {type: string, format: date}}
- {name: created_to, in: query, schema: {type: string, format: date}}
- {$ref: '#/components/parameters/Limit'}
- {$ref: '#/components/parameters/Offset'}
responses:
'200':
description: A page of items
content:
application/json:
schema:
type: object
properties:
items: {type: array, items: {$ref: '#/components/schemas/Item'}}
pagination: {$ref: '#/components/schemas/Pagination'}
'400': {$ref: '#/components/responses/BadRequest'}
'401': {$ref: '#/components/responses/Unauthorised'}
/api/v1/items/upload:
post:
tags: [Items]
summary: Upload a document
description: |
Accepts plain text, Markdown, JSON, PDF and DOCX. The type is verified from the file's
content, not its name or declared type. Archives and executables are refused.
parameters: [{$ref: '#/components/parameters/CSRFToken'}]
requestBody:
required: true
content:
multipart/form-data:
schema:
type: object
required: [file]
properties:
file: {type: string, format: binary}
title: {type: string, maxLength: 300}
source_type: {type: string}
created_at: {type: string, format: date}
allow_duplicate: {type: string, enum: ['true', 'false']}
responses:
'202':
description: Accepted and queued for processing
content:
application/json:
schema: {$ref: '#/components/schemas/ItemAccepted'}
'400': {$ref: '#/components/responses/BadRequest'}
'401': {$ref: '#/components/responses/Unauthorised'}
'409': {$ref: '#/components/responses/Duplicate'}
'413': {$ref: '#/components/responses/TooLarge'}
'415': {$ref: '#/components/responses/UnsupportedMedia'}
/api/v1/items/{id}:
parameters: [{$ref: '#/components/parameters/ItemID'}]
get:
tags: [Items]
summary: Retrieve an item with everything derived from it
responses:
'200':
description: The item, its chunks, entities, claims, relationships and processing history
content:
application/json:
schema: {$ref: '#/components/schemas/ItemDetail'}
'400': {$ref: '#/components/responses/BadRequest'}
'401': {$ref: '#/components/responses/Unauthorised'}
'404': {$ref: '#/components/responses/NotFound'}
delete:
tags: [Items]
summary: Delete an item, or preview what deletion would remove
description: |
With `dry_run=true`, reports what would be removed and changes nothing.
Without it, the item is hidden immediately and its chunks, vectors, claims,
relationships and any entities that only it referenced are removed asynchronously.
Audit events and processing runs are retained.
parameters:
- {$ref: '#/components/parameters/CSRFToken'}
- {name: dry_run, in: query, schema: {type: boolean}}
responses:
'200':
description: The deletion plan (dry run only)
content:
application/json:
schema:
type: object
properties:
dry_run: {type: boolean}
plan: {$ref: '#/components/schemas/DeletionPlan'}
note: {type: string}
'202':
description: Deletion queued
content:
application/json:
schema:
type: object
properties:
deleted: {type: string, format: uuid}
job_id: {type: string, format: uuid}
status: {type: string}
'401': {$ref: '#/components/responses/Unauthorised'}
'403': {$ref: '#/components/responses/Forbidden'}
'404': {$ref: '#/components/responses/NotFound'}
/api/v1/items/{id}/reprocess:
parameters: [{$ref: '#/components/parameters/ItemID'}]
post:
tags: [Items]
summary: Process an item again
description: |
Opens a new processing run and restarts the pipeline. Derived records are replaced
rather than duplicated, and human review decisions on relationships are preserved.
parameters: [{$ref: '#/components/parameters/CSRFToken'}]
requestBody:
required: false
content:
application/json:
schema:
type: object
additionalProperties: false
properties:
reason: {type: string}
responses:
'202':
description: Queued for reprocessing
content:
application/json:
schema:
type: object
properties:
item_id: {type: string, format: uuid}
job_id: {type: string, format: uuid}
status: {type: string}
'401': {$ref: '#/components/responses/Unauthorised'}
'403': {$ref: '#/components/responses/Forbidden'}
'404': {$ref: '#/components/responses/NotFound'}
/api/v1/items/{id}/relationships:
parameters: [{$ref: '#/components/parameters/ItemID'}]
get:
tags: [Relationships]
summary: An item's incoming and outgoing relationships
parameters:
- {name: type, in: query, schema: {type: string}, description: Comma-separated relationship types
}
- {name: provenance, in: query, schema: {type: string}}
- {name: review_status, in: query, schema: {type: string}}
- {name: confirmed_only, in: query, schema: {type: boolean}}
- {name: min_score, in: query, schema: {type: number, minimum: 0, maximum: 1}}
- {name: direction, in: query, schema: {type: string, enum: [out, in, both]}}
responses:
'200':
description: Relationships with their evidence and explanations
content:
application/json:
schema:
type: object
properties:
item_id: {type: string, format: uuid}
outgoing: {type: array, items: {$ref: '#/components/schemas/RelationshipView'}}
incoming: {type: array, items: {$ref: '#/components/schemas/RelationshipView'}}
note: {type: string}
'400': {$ref: '#/components/responses/BadRequest'}
'401': {$ref: '#/components/responses/Unauthorised'}
/api/v1/search:
get:
tags: [Search]
summary: Hybrid search across stored items
description: |
Combines exact matching, MongoDB text search, vector similarity, entity matching and
relationship expansion. Every result explains why it matched.
parameters:
- {name: q, in: query, schema: {type: string, maxLength: 2000}}
- {name: source_type, in: query, schema: {type: string}}
- {name: mime_type, in: query, schema: {type: string}}
- {name: entity_id, in: query, schema: {type: string}, description: Comma-separated UUIDs}
- {name: relationship_type, in: query, schema: {type: string}}
- {name: created_from, in: query, schema: {type: string, format: date}}
- {name: created_to, in: query, schema: {type: string, format: date}}
- {name: confirmed_only, in: query, schema: {type: boolean}, description: Follow only relationships a person confirmed}
- {name: min_relationship_strength, in: query, schema: {type: number, minimum: 0, maximum: 1}}
- {name: expand, in: query, schema: {type: boolean, default: true}}
- {$ref: '#/components/parameters/Limit'}
- {$ref: '#/components/parameters/Offset'}
responses:
'200':
description: Ranked results with explanations
content:
application/json:
schema:
type: object
properties:
search: {$ref: '#/components/schemas/SearchResponse'}
score_guide:
type: object
description: What each score means, restated on every response
properties:
query_relevance: {type: string}
semantic_similarity: {type: string}
relationship_strength: {type: string}
'400': {$ref: '#/components/responses/BadRequest'}
'401': {$ref: '#/components/responses/Unauthorised'}
/api/v1/search/answer:
post:
tags: [Search]
summary: Answer a question using only stored material
description: |
Retrieves relevant items and generates an answer from them alone. Citations are
resolved against the passages actually supplied, so a fabricated reference cannot
point at a real item. Conflicts between sources are surfaced, never reconciled.
Requires a configured language model; returns 503 otherwise.
parameters: [{$ref: '#/components/parameters/CSRFToken'}]
requestBody:
required: true
content:
application/json:
schema:
type: object
required: [question]
additionalProperties: false
properties:
question: {type: string, maxLength: 2000}
source_types: {type: array, items: {type: string}}
entity_ids: {type: array, items: {type: string, format: uuid}}
confirmed_only: {type: boolean}
limit: {type: integer}
responses:
'200':
description: A cited answer
content:
application/json:
schema: {$ref: '#/components/schemas/Answer'}
'400': {$ref: '#/components/responses/BadRequest'}
'401': {$ref: '#/components/responses/Unauthorised'}
'502':
description: The model returned output that failed validation. Nothing was stored.
content:
application/json:
schema: {$ref: '#/components/schemas/Error'}
'503': {$ref: '#/components/responses/FeatureDisabled'}
/api/v1/relationships:
get:
tags: [Relationships]
summary: List relationships
parameters:
- {name: type, in: query, schema: {type: string}}
- {name: provenance, in: query, schema: {type: string}}
- {name: review_status, in: query, schema: {type: string}}
- {name: confirmed_only, in: query, schema: {type: boolean}}
- {name: min_score, in: query, schema: {type: number, minimum: 0, maximum: 1}}
- {$ref: '#/components/parameters/Limit'}
- {$ref: '#/components/parameters/Offset'}
responses:
'200':
description: A page of relationships
content:
application/json:
schema:
type: object
properties:
relationships: {type: array, items: {$ref: '#/components/schemas/RelationshipView'}}
pagination: {$ref: '#/components/schemas/Pagination'}
'400': {$ref: '#/components/responses/BadRequest'}
'401': {$ref: '#/components/responses/Unauthorised'}
post:
tags: [Relationships]
summary: Record a relationship asserted by a person
description: |
Stored with `explicit` provenance and `confirmed` review status: a human statement is
not a proposal awaiting review. Both items must be in the caller's workspace.
parameters: [{$ref: '#/components/parameters/CSRFToken'}]
requestBody:
required: true
content:
application/json:
schema:
type: object
required: [source_id, target_id, type]
additionalProperties: false
properties:
source_id: {type: string, format: uuid}
target_id: {type: string, format: uuid}
type: {$ref: '#/components/schemas/RelationshipType'}
note: {type: string, maxLength: 2000}
responses:
'201':
description: Created
content:
application/json:
schema:
type: object
properties:
relationship: {$ref: '#/components/schemas/RelationshipView'}
'400': {$ref: '#/components/responses/BadRequest'}
'401': {$ref: '#/components/responses/Unauthorised'}
'403': {$ref: '#/components/responses/Forbidden'}
'404': {$ref: '#/components/responses/NotFound'}
/api/v1/relationships/{id}:
parameters: [{$ref: '#/components/parameters/RelationshipID'}]
patch:
tags: [Relationships]
summary: Change a relationship's type
description: |
The original type and provenance are preserved on the record, and the change is
appended to the relationship's review history.
parameters: [{$ref: '#/components/parameters/CSRFToken'}]
requestBody:
required: true
content:
application/json:
schema:
type: object
required: [type]
additionalProperties: false
properties:
type: {$ref: '#/components/schemas/RelationshipType'}
note: {type: string, maxLength: 2000}
responses:
'200':
description: Updated
content:
application/json:
schema:
type: object
properties:
relationship: {$ref: '#/components/schemas/RelationshipView'}
'400': {$ref: '#/components/responses/BadRequest'}
'401': {$ref: '#/components/responses/Unauthorised'}
'403': {$ref: '#/components/responses/Forbidden'}
'404': {$ref: '#/components/responses/NotFound'}
/api/v1/relationships/{id}/confirm:
parameters: [{$ref: '#/components/parameters/RelationshipID'}]
post:
tags: [Relationships]
summary: Confirm a relationship
description: |
Adds a user-confirmation signal, raising the score to 1.00 through the same arithmetic
as every other signal. The original proposal and its evidence are retained.
parameters: [{$ref: '#/components/parameters/CSRFToken'}]
requestBody:
required: false
content:
application/json:
schema:
type: object
additionalProperties: false
properties:
note: {type: string, maxLength: 2000}
responses:
'200':
description: Confirmed
content:
application/json:
schema:
type: object
properties:
relationship: {$ref: '#/components/schemas/RelationshipView'}
'401': {$ref: '#/components/responses/Unauthorised'}
'403': {$ref: '#/components/responses/Forbidden'}
'404': {$ref: '#/components/responses/NotFound'}
/api/v1/relationships/{id}/reject:
parameters: [{$ref: '#/components/parameters/RelationshipID'}]
post:
tags: [Relationships]
summary: Reject a relationship
description: |
The relationship is retained with its evidence and history; its score falls to zero
and it no longer influences search. Nothing is deleted.
parameters: [{$ref: '#/components/parameters/CSRFToken'}]
requestBody:
required: false
content:
application/json:
schema:
type: object
additionalProperties: false
properties:
note: {type: string, maxLength: 2000}
responses:
'200':
description: Rejected
content:
application/json:
schema:
type: object
properties:
relationship: {$ref: '#/components/schemas/RelationshipView'}
'401': {$ref: '#/components/responses/Unauthorised'}
'403': {$ref: '#/components/responses/Forbidden'}
'404': {$ref: '#/components/responses/NotFound'}
/api/v1/entities:
get:
tags: [Entities]
summary: List extracted entities
parameters:
- {name: q, in: query, schema: {type: string}, description: Literal substring of the canonical name}
- {name: type, in: query, schema: {type: string}}
- {name: item_id, in: query, schema: {type: string, format: uuid}}
- {name: min_mentions, in: query, schema: {type: integer}}
- {$ref: '#/components/parameters/Limit'}
- {$ref: '#/components/parameters/Offset'}
responses:
'200':
description: A page of entities
content:
application/json:
schema:
type: object
properties:
entities: {type: array, items: {$ref: '#/components/schemas/Entity'}}
pagination: {$ref: '#/components/schemas/Pagination'}
'400': {$ref: '#/components/responses/BadRequest'}
'401': {$ref: '#/components/responses/Unauthorised'}
/api/v1/entities/{id}:
parameters: [{$ref: '#/components/parameters/EntityID'}]
get:
tags: [Entities]
summary: An entity with its aliases and citing items
responses:
'200':
description: The entity
content:
application/json:
schema:
type: object
properties:
entity: {$ref: '#/components/schemas/Entity'}
aliases: {type: array, items: {$ref: '#/components/schemas/EntityAlias'}}
items: {type: array, items: {$ref: '#/components/schemas/Item'}}
note: {type: string}
'401': {$ref: '#/components/responses/Unauthorised'}
'404': {$ref: '#/components/responses/NotFound'}
/api/v1/aliases/{id}/confirm:
parameters: [{$ref: '#/components/parameters/AliasID'}]
post:
tags: [Entities]
summary: Confirm that two similar names refer to the same thing
description: |
The two entities remain **separate records**. A confirmed alias becomes evidence when
relationships are discovered, weighted below an exact name match. Relata never merges
entities automatically, and confirming does not merge them either.
parameters: [{$ref: '#/components/parameters/CSRFToken'}]
responses:
'200':
description: Confirmed
content:
application/json:
schema: {$ref: '#/components/schemas/AliasDecision'}
'401': {$ref: '#/components/responses/Unauthorised'}
'403': {$ref: '#/components/responses/Forbidden'}
'404': {$ref: '#/components/responses/NotFound'}
/api/v1/aliases/{id}/reject:
parameters: [{$ref: '#/components/parameters/AliasID'}]
post:
tags: [Entities]
summary: Record that two similar names refer to different things
description: |
The proposal is retained so that it is not offered again as new, and the alias stops
contributing evidence.
parameters: [{$ref: '#/components/parameters/CSRFToken'}]
responses:
'200':
description: Rejected
content:
application/json:
schema: {$ref: '#/components/schemas/AliasDecision'}
'401': {$ref: '#/components/responses/Unauthorised'}
'403': {$ref: '#/components/responses/Forbidden'}
'404': {$ref: '#/components/responses/NotFound'}
/api/v1/jobs:
get:
tags: [Jobs]
summary: List background jobs
parameters:
- {name: status, in: query, schema: {type: string}}
- {name: type, in: query, schema: {type: string}}
- {$ref: '#/components/parameters/Limit'}
- {$ref: '#/components/parameters/Offset'}
responses:
'200':
description: A page of jobs
content:
application/json:
schema:
type: object
properties:
jobs: {type: array, items: {$ref: '#/components/schemas/Job'}}
pagination: {$ref: '#/components/schemas/Pagination'}
'401': {$ref: '#/components/responses/Unauthorised'}
/api/v1/jobs/{id}/retry:
parameters: [{$ref: '#/components/parameters/JobID'}]
post:
tags: [Jobs]
summary: Return a failed job to the queue
parameters: [{$ref: '#/components/parameters/CSRFToken'}]
responses:
'200':
description: Requeued
content:
application/json:
schema:
type: object
properties:
job: {$ref: '#/components/schemas/Job'}
'401': {$ref: '#/components/responses/Unauthorised'}
'403': {$ref: '#/components/responses/Forbidden'}
'409':
description: The job does not exist or is not in a retryable state
content:
application/json:
schema: {$ref: '#/components/schemas/Error'}
/api/v1/tokens:
get:
tags: [Tokens]
summary: List your API tokens
description: |
Returns the caller's own tokens, including revoked and expired ones so the
history stays visible. No secret is returned — only a hash was ever stored.
**Requires a signed-in session.** An API token cannot list, create or revoke
tokens: a leaked credential must not be able to mint its own replacements.
responses:
'200':
description: The caller's tokens
content:
application/json:
schema:
type: object
properties:
tokens:
type: array
items: {$ref: '#/components/schemas/APIToken'}
total: {type: integer}
'401': {$ref: '#/components/responses/Unauthorised'}
'403':
description: The request authenticated with an API token rather than a session
content:
application/json:
schema: {$ref: '#/components/schemas/Error'}
post:
tags: [Tokens]
summary: Issue an API token
description: |
The secret is returned **once** and cannot be retrieved again.
Granted scopes are the intersection of those requested and those the caller's
role permits, so a token never carries more authority than its creator.
parameters: [{$ref: '#/components/parameters/CSRFToken'}]
requestBody:
required: true
content:
application/json:
schema:
type: object
required: [name, scopes]
properties:
name: {type: string, maxLength: 80}
scopes:
type: array
minItems: 1
items: {type: string, enum: [ingest, query, review]}
ttl_hours:
type: integer
description: Optional. The configured default applies when absent, capped at the configured maximum.
responses:
'201':
description: Token issued
content:
application/json:
schema:
type: object
properties:
token: {$ref: '#/components/schemas/APIToken'}
secret:
type: string
description: The credential. Shown once; only its hash is stored.
warning: {type: string}
'400': {$ref: '#/components/responses/InvalidRequest'}
'401': {$ref: '#/components/responses/Unauthorised'}
'403':
description: The request authenticated with an API token rather than a session
content:
application/json:
schema: {$ref: '#/components/schemas/Error'}
/api/v1/tokens/{id}:
parameters: [{$ref: '#/components/parameters/TokenID'}]
delete:
tags: [Tokens]
summary: Revoke an API token
description: |
The record is kept rather than deleted, so the token's existence and its end stay
visible. A revoked token is refused on its next use; there is no cached grant.
parameters: [{$ref: '#/components/parameters/CSRFToken'}]
responses:
'200':
description: Revoked
content:
application/json:
schema:
type: object
properties:
revoked: {type: boolean}
id: {type: string, format: uuid}
'401': {$ref: '#/components/responses/Unauthorised'}
'403':
description: The request authenticated with an API token rather than a session
content:
application/json:
schema: {$ref: '#/components/schemas/Error'}
'404': {$ref: '#/components/responses/NotFound'}
/health/live:
get:
tags: [Health]
summary: Liveness
description: Checks only that the process is running. Unauthenticated.
security: []
responses:
'200':
description: Alive
content:
application/json:
schema:
type: object
properties:
status: {type: string}
version: {type: string}
uptime_sec: {type: integer}
/health/ready:
get:
tags: [Health]
summary: Readiness
description: |
MongoDB and Qdrant are required. AI providers are reported but not required:
ingestion, keyword search and browsing work without them. Unauthenticated,
and therefore reports *that* a dependency is unreachable but never *why* —
see the server log for the cause.
security: []
responses:
'200':
description: Ready
content:
application/json:
schema: {$ref: '#/components/schemas/Readiness'}
'503':
description: A required dependency is unavailable
content:
application/json:
schema: {$ref: '#/components/schemas/Readiness'}
components:
securitySchemes:
sessionCookie:
type: apiKey
in: cookie
name: relata_session
parameters:
CSRFToken:
name: X-CSRF-Token
in: header
required: true
schema: {type: string}
description: The session's CSRF token, rendered into every page as a hidden `csrf_token` field.
ItemID:
name: id
in: path
required: true
schema: {type: string, format: uuid}
RelationshipID:
name: id
in: path
required: true
schema: {type: string, format: uuid}
EntityID:
name: id
in: path
required: true
schema: {type: string, format: uuid}
TokenID:
name: id
in: path
required: true
schema: {type: string, format: uuid}
JobID:
name: id
in: path
required: true
schema: {type: string, format: uuid}
AliasID:
name: id
in: path
required: true
schema: {type: string, format: uuid}
Limit:
name: limit
in: query
schema: {type: integer, minimum: 1, maximum: 200, default: 20}
Offset:
name: offset
in: query
schema: {type: integer, minimum: 0, default: 0}
responses:
BadRequest:
description: The request was rejected by validation
content:
application/json:
schema: {$ref: '#/components/schemas/Error'}
Unauthorised:
description: Authentication is required
content:
application/json:
schema: {$ref: '#/components/schemas/Error'}
Forbidden:
description: The caller's role does not permit this, or the CSRF check failed
content:
application/json:
schema: {$ref: '#/components/schemas/Error'}
NotFound:
description: |
No such record in the caller's workspace. A record in another workspace is
reported identically, so probing cannot confirm that an ID exists elsewhere.
content:
application/json:
schema: {$ref: '#/components/schemas/Error'}
Duplicate:
description: Identical content already exists
content:
application/json:
schema: {$ref: '#/components/schemas/Error'}
TooLarge:
description: The body exceeds the configured limit
content:
application/json:
schema: {$ref: '#/components/schemas/Error'}
UnsupportedMedia:
description: The content type is not supported
content:
application/json:
schema: {$ref: '#/components/schemas/Error'}
FeatureDisabled:
description: The feature requires a language model, which is not configured
content:
application/json:
schema: {$ref: '#/components/schemas/Error'}
schemas:
Error:
type: object
properties:
error:
type: object
properties:
code:
type: string
description: A stable, machine-readable code. Clients may branch on it.
enum:
[invalid_request, validation_failed, unauthorised, forbidden, not_found,
duplicate_content, conflict, payload_too_large, unsupported_media_type,
rate_limited, csrf_check_failed, dependency_unavailable,
model_output_invalid, feature_disabled, internal_error]
message: {type: string, description: Safe to display. Never contains internal detail.}
fields:
type: array
items:
type: object
properties:
field: {type: string}
message: {type: string}
request_id: {type: string, description: Quote this when reporting a problem.}
detail: {type: object, additionalProperties: {type: string}}
Pagination:
type: object
properties:
total: {type: integer}
limit: {type: integer}
offset: {type: integer}
has_more: {type: boolean}
SourceType:
type: string
enum: [note, document, json, email, transcript, markdown]
ProcessingStatus:
type: string
enum: [pending, parsing, chunking, embedding, analysing, relating, ready, failed,
partial, deleting, tombstoned]
RelationshipType:
type: string
enum: [similar_to, mentions, involves, supports, contradicts, updates, supersedes,
provides_context_for, possibly_explains, occurred_before, occurred_after,
derived_from, same_entity_as]
Provenance:
type: string
description: How the relationship came to exist. `model_inferred` is a proposal, not a fact.
enum: [explicit, deterministic, model_inferred, user_confirmed, user_rejected]
ReviewStatus:
type: string
enum: [pending, confirmed, rejected, edited]
EntityType:
type: string
enum: [person, organisation, location, product, event, date, subject, concept]
CreateItemRequest:
type: object
required: [content]
additionalProperties: false
description: Unknown fields are rejected.
properties:
title: {type: string, maxLength: 300}
content: {type: string}
source_type: {$ref: '#/components/schemas/SourceType'}
mime_type: {type: string, description: A hint only; the type is detected from the content.}
filename: {type: string}
metadata: {type: object, additionalProperties: {type: string}}
created_at:
type: string
format: date-time
description: When the information itself dates from. Must not be in the future.
allow_duplicate:
type: boolean
description: Store even when identical content already exists.
ItemAccepted:
type: object
properties:
item: {$ref: '#/components/schemas/Item'}
job_id: {type: string, format: uuid}
status: {type: string}
Item:
type: object
properties:
id: {type: string, format: uuid}
workspace_id: {type: string, format: uuid}
title: {type: string}
source_type: {$ref: '#/components/schemas/SourceType'}
mime_type: {type: string}
original_filename: {type: string}
original_text: {type: string, description: The verbatim text supplied, never rewritten.}
extracted_text: {type: string}
size_bytes: {type: integer}
content_hash: {type: string, description: SHA-256 of the supplied bytes.}
metadata: {type: object, additionalProperties: {type: string}}
created_at: {type: string, format: date-time}
ingested_at: {type: string, format: date-time}
updated_at: {type: string, format: date-time}
status: {$ref: '#/components/schemas/ProcessingStatus'}
warnings:
type: array
items:
type: object
properties:
code: {type: string}
message: {type: string}
detail: {type: string}
error: {type: string}
parser_version: {type: string}
embedding_model: {type: string}
analysis_model: {type: string}
score_version: {type: string}
chunk_count: {type: integer}
entity_count: {type: integer}
claim_count: {type: integer}
relationship_count: {type: integer}
duplicate_of: {type: string, format: uuid}
deleted: {type: boolean}
Chunk:
type: object
properties:
id: {type: string, format: uuid, description: Also the Qdrant point ID.}
item_id: {type: string, format: uuid}
sequence: {type: integer}
text: {type: string}
content_hash: {type: string}
start_offset: {type: integer, description: Byte offset into the item's extracted text.}
end_offset: {type: integer}
page: {type: integer}
section: {type: string}
heading: {type: string}
embedding_model: {type: string}
embedded: {type: boolean}
Claim:
type: object
description: A record that a source *said* something. Not a fact.
properties:
id: {type: string, format: uuid}
item_id: {type: string, format: uuid}
chunk_id: {type: string, format: uuid}
statement: {type: string, description: A short normalised rendering.}
source_ref:
type: object
description: Always points at real text. A claim never loses this link.
properties:
item_id: {type: string, format: uuid}
chunk_id: {type: string, format: uuid}
start: {type: integer}
end: {type: integer}
quote: {type: string, description: The exact source wording.}
page: {type: integer}
attribution: {type: string}
nature: {type: string, enum: [quoted, paraphrased, inferred]}
subject: {type: string}
numbers: {type: array, items: {type: number}}
dates: {type: array, items: {type: string}}
polarity: {type: integer, enum: [-1, 1]}
method: {type: string, enum: [deterministic, model, user]}
model_version: {type: string}
confidence: {type: number}
Entity:
type: object
properties:
id: {type: string, format: uuid}
canonical_name: {type: string}
normalised_key: {type: string}
type: {$ref: '#/components/schemas/EntityType'}
aliases: {type: array, items: {type: string}}
item_ids: {type: array, items: {type: string, format: uuid}}
source_refs: {type: array, items: {type: object}}
method: {type: string, enum: [deterministic, model, user]}
evidence_score:
type: number
description: How well attested the entity is. Not a probability.
mentions: {type: integer}
model_version: {type: string}
EntityAlias:
type: object
description: A proposal that two names may refer to the same thing. Never applied automatically.
properties:
id: {type: string, format: uuid}
entity_id: {type: string, format: uuid}
alias: {type: string}
candidate_entity_id: {type: string, format: uuid}
status: {type: string, enum: [candidate, confirmed, rejected]}
similarity: {type: number}
reason: {type: string}
Signal:
type: object
description: One explicit contribution to a relationship's strength.
properties:
type:
type: string
enum: [semantic_similarity, shared_entity, alias_entity, shared_concept,
temporal_proximity, shared_metadata, claim_support, claim_contradiction,
model_classification, user_confirmation, user_rejection, content_hash_match]
value: {type: string, description: The human-readable evidence.}
strength: {type: number, minimum: 0, maximum: 1}
weight: {type: number}
detail: {type: string}
Relationship:
type: object
properties:
id: {type: string, format: uuid}
source_kind: {type: string, enum: [item, chunk, entity, claim]}
source_id: {type: string, format: uuid}
target_kind: {type: string, enum: [item, chunk, entity, claim]}
target_id: {type: string, format: uuid}
type: {$ref: '#/components/schemas/RelationshipType'}
provenance: {$ref: '#/components/schemas/Provenance'}
score:
type: number
minimum: 0
maximum: 1
description: |
A HEURISTIC relationship strength derived from the signals below, under the
named scoring version. NOT a calibrated probability.
score_version: {type: string}
evidence:
type: object
properties:
signals: {type: array, items: {$ref: '#/components/schemas/Signal'}}
source_chunk_ids: {type: array, items: {type: string}}
target_chunk_ids: {type: array, items: {type: string}}
shared_entities: {type: array, items: {type: string}}
source_excerpt: {type: string}
target_excerpt: {type: string}
model_rationale: {type: string, description: A model's words. Not a verified fact.}
model_version: {type: string}
original_type: {$ref: '#/components/schemas/RelationshipType'}
original_provenance: {$ref: '#/components/schemas/Provenance'}
review_status: {$ref: '#/components/schemas/ReviewStatus'}
review_history:
type: array
description: Append-only. A rejection never erases the original proposal.
items:
type: object
properties:
at: {type: string, format: date-time}
action: {type: string}
actor_id: {type: string}
actor_name: {type: string}
note: {type: string}
from_value: {type: string}
to_value: {type: string}
created_at: {type: string, format: date-time}
updated_at: {type: string, format: date-time}
RelationshipView:
allOf:
- {$ref: '#/components/schemas/Relationship'}
- type: object
properties:
explanation:
type: array
items: {type: string}
description: The score's arithmetic in plain English.
source_title: {type: string}
target_title: {type: string}
score_meaning: {type: string}
ItemDetail:
type: object
properties:
item: {$ref: '#/components/schemas/Item'}
chunks: {type: array, items: {$ref: '#/components/schemas/Chunk'}}
entities: {type: array, items: {$ref: '#/components/schemas/Entity'}}
claims: {type: array, items: {$ref: '#/components/schemas/Claim'}}
outgoing_relationships: {type: array, items: {$ref: '#/components/schemas/RelationshipView'}}
incoming_relationships: {type: array, items: {$ref: '#/components/schemas/RelationshipView'}}
processing_runs:
type: array
description: Retained across reprocessing, for audit.
items:
type: object
properties:
id: {type: string, format: uuid}
reason: {type: string}
started_at: {type: string, format: date-time}
ended_at: {type: string, format: date-time}
outcome: {type: string}
error: {type: string}
parser_version: {type: string}
embedding_model: {type: string}
analysis_model: {type: string}
score_version: {type: string}
stats: {type: object, additionalProperties: {type: integer}}
SearchResponse:
type: object
properties:
query: {type: string}
total: {type: integer}
limit: {type: integer}
offset: {type: integer}
took_ms: {type: integer}
notes:
type: array
items: {type: string}
description: Advisory messages, such as vector search being unavailable.
results: {type: array, items: {$ref: '#/components/schemas/SearchResult'}}
SearchResult:
type: object
properties:
item: {$ref: '#/components/schemas/Item'}
query_relevance:
type: number
description: How well this item answers THIS query, relative to the best result. Not a probability.
semantic_similarity:
type: number
description: |
Best cosine similarity of any matching section. Reported separately because
similarity is not relevance and not evidence of a factual relationship.
excerpt: {type: string}
why_matched: {type: array, items: {type: string}}
contributions:
type: array
items:
type: object
properties:
source: {type: string}
rank: {type: integer}
weight: {type: number}
score: {type: number}
detail: {type: string}
matched_chunks:
type: array
items:
type: object
properties:
chunk_id: {type: string, format: uuid}
sequence: {type: integer}
text: {type: string}
semantic_similarity: {type: number}
page: {type: integer}
section: {type: string}
matched_entities:
type: array
items:
type: object
properties:
id: {type: string, format: uuid}
name: {type: string}
type: {$ref: '#/components/schemas/EntityType'}
related_items: {type: array, items: {$ref: '#/components/schemas/RelatedItem'}}
via_relationship:
allOf: [{$ref: '#/components/schemas/RelatedItem'}]
description: Set when the item was reached by relationship expansion rather than a direct match.
rank_version: {type: string}
RelatedItem:
type: object
properties:
relationship_id: {type: string, format: uuid}
item_id: {type: string, format: uuid}
title: {type: string}
type: {$ref: '#/components/schemas/RelationshipType'}
provenance: {$ref: '#/components/schemas/Provenance'}
relationship_strength:
type: number
description: A heuristic strength, NOT query relevance and NOT a probability.
score_version: {type: string}
review_status: {$ref: '#/components/schemas/ReviewStatus'}
explanation: {type: array, items: {type: string}}
direction: {type: string, enum: [incoming, outgoing]}
Answer:
type: object
properties:
question: {type: string}
answer: {type: string, description: Generated from the sources below and nothing else.}
sufficient:
type: boolean
description: False when the stored material does not support an answer.
sources_used:
type: array
items:
type: object
properties:
ref: {type: string, description: The opaque handle shown to the model (S1, S2…).}
item_id: {type: string, format: uuid}
title: {type: string}
excerpt: {type: string}
date: {type: string}
cited: {type: boolean, description: Whether the answer actually referenced it.}
uncertainty_and_conflicts:
type: array
items: {type: string}
description: |
Disagreements between sources, insufficiency, reliance on inferred
relationships, and any prompt-injection attempt noticed. Never suppressed.
inferred_relationships_used:
type: array
items: {$ref: '#/components/schemas/RelatedItem'}
description: Links that influenced retrieval but are proposals, not facts.
model: {type: string}
retrieved_items: {type: integer}
took_ms: {type: integer}
prompt_injection_noticed: {type: boolean}
DeletionPlan:
type: object
properties:
item_id: {type: string, format: uuid}
title: {type: string}
chunks: {type: integer}
vectors: {type: integer}
claims: {type: integer}
relationships: {type: integer}
orphaned_entities:
type: array
items: {type: string}
description: Entities only this item references; they will be removed.
shared_entities_retained:
type: array
items: {type: string}
description: Entities referenced elsewhere; they will be kept.
original_file: {type: string}
queued_jobs: {type: integer}
AliasDecision:
type: object
properties:
alias_id: {type: string, format: uuid}
status: {type: string, enum: [candidate, confirmed, rejected]}
note:
type: string
description: States that the entities remain separate records.
Job:
type: object
properties:
id: {type: string, format: uuid}
type:
type: string
enum: [parse_item, chunk_item, generate_embeddings, extract_entities, extract_claims,
discover_relationships, reprocess_item, delete_item_data, reembed_workspace]
status: {type: string, enum: [pending, running, succeeded, failed, cancelled]}
item_id: {type: string, format: uuid}
priority: {type: integer}
run_after: {type: string, format: date-time}
attempts: {type: integer}
max_attempts: {type: integer}
lease_owner: {type: string}
lease_expires: {type: string, format: date-time}
last_error: {type: string}
history:
type: array
items:
type: object
properties:
number: {type: integer}
started_at: {type: string, format: date-time}
ended_at: {type: string, format: date-time}
worker: {type: string}
error: {type: string}
error_class: {type: string, enum: [transient, permanent, timeout, cancelled, invalid_model_output]}
duration_ms: {type: integer}
created_at: {type: string, format: date-time}
updated_at: {type: string, format: date-time}
completed_at: {type: string, format: date-time}
APIToken:
type: object
description: |
An API token as returned by the API. There is no field for the secret because
there is no secret to return: only its SHA-256 hash was stored.
properties:
id: {type: string, format: uuid}
name: {type: string}
prefix:
type: string
description: The leading, non-secret portion, kept so tokens can be told apart.
scopes:
type: array
items: {type: string, enum: [ingest, query, review]}
created_at: {type: string, format: date-time}
expires_at: {type: string, format: date-time}
last_used_at: {type: string, format: date-time}
revoked_at: {type: string, format: date-time}
status: {type: string, enum: [active, expired, revoked]}
Readiness:
type: object
properties:
status: {type: string, enum: [ready, 'not ready']}
version: {type: string}
note: {type: string}
checks:
type: object
additionalProperties:
type: object
properties:
status: {type: string, enum: [ok, unavailable]}
detail:
type: string
description: |
A fixed description of what an unavailable dependency means for the
service. Absent when the dependency is ok. It never carries the
underlying error: this endpoint is unauthenticated, and driver errors
embed hosts, ports and connection-string credentials. The cause is
logged server-side with the request ID.
required:
type: boolean
description: AI providers are false; MongoDB and Qdrant are true.