API Reference
Base URL: https://your-instance (all REST endpoints live under /v1).
Unless noted, endpoints require authentication via Authorization: Bearer <api-key>. See
Authentication for roles. A machine-readable OpenAPI spec (all public endpoints except the OAuth, MCP and internal admin ones) is served at
GET /v1/openapi.json.
All request and response bodies are JSON unless stated otherwise. Errors use the format described in the Error Reference.
Roles. Each endpoint lists the minimum role: read < write < admin. Endpoints marked “any key”
accept any valid API key.
Health
No authentication.
| Endpoint | Description |
|---|---|
GET /health/live | 200 if the process is alive |
GET /health/ready | 200 if the database is reachable, otherwise 503 |
GET /health/index | 200 if the vector indexes exist, otherwise 503. Body: {"idx_chunks_embedding": "ok|missing", "idx_platform_chunks_embedding": "ok|missing"} |
Knowledge Bases
Create Knowledge Base
POST /v1/knowledge-bases
Role: write.
{
"name": "guidelines",
"dimensions": 1024
}
| Field | Type | Notes |
|---|---|---|
name | string | Required, 1–200 characters |
dimensions | integer | Optional, 1–4096, default 1024. Immutable after creation |
Response 201: {"id": "<kb-uuid>"}
Errors: 403 knowledge_base_limit_reached (plan limit), 422 (validation).
List Knowledge Bases
GET /v1/knowledge-bases
Any key. Returns an array of knowledge bases. Sub-tenant keys only see their own.
Update Knowledge Base
PATCH /v1/knowledge-bases/{kb_id}
Role: write. Body may contain name and/or translation_config (see
Translation Settings). Including dimensions is rejected with 422.
Response: 204.
List Documents in a Knowledge Base
GET /v1/knowledge-bases/{kb_id}/documents
Any key. Returns an array of documents. 404 if the knowledge base is not accessible to your key.
There is no endpoint to delete a knowledge base through the API; delete its documents individually or use the web portal / account deletion.
Documents
Ingest Document (JSON)
POST /v1/documents
Role: write. Rate limited per tenant.
{
"knowledge_base_id": "uuid",
"title": "Document Title",
"content": "Full text content...",
"anonymize": true,
"tags": {"specialty": "cardiology"}
}
| Field | Type | Notes |
|---|---|---|
knowledge_base_id | string (uuid) | Required |
title | string | Required, 1–500 characters |
content | string | Required, max 10 MB and the plan’s max document size |
anonymize | boolean | Optional. Defaults to the tenant’s anonymization setting |
tags | object | Optional metadata, filterable at query time via filters |
Response 202: {"job_id": "<uuid>"}. Ingestion is asynchronous; poll Get Job.
The size of content counts against your storage quota (and the sub-tenant’s, if the knowledge base belongs to one); deleting the document frees it.
Errors: 400 invalid knowledge_base_id, 404 not_found, 413 (document_too_large, storage quota exceeded, or sub_tenant_storage_quota_exceeded), 402 document_limit_reached,
402 chunk_quota_exceeded, 422. Enterprise sub-tenants may also see 403 sub_tenant_document_limit_reached,
429 sub_tenant_chunk_quota_exceeded and 429 tenant_pool_quota_exceeded.
Upload Document (File)
POST /v1/documents/upload
Content-Type: multipart/form-data
Role: write. Rate limited per tenant. Maximum request size 55 MB.
| Part | Notes |
|---|---|
file | Required. Content type text/plain or application/pdf |
metadata | Required JSON string. Must include knowledge_base_id |
curl -X POST https://your-instance/v1/documents/upload \
-H "Authorization: Bearer $MEDRAG_API_KEY" \
-F 'file=@guideline.pdf;type=application/pdf' \
-F 'metadata={"knowledge_base_id": "<kb-id>"}'
Response 202: {"job_id": "<uuid>"}.
Errors: 400 (missing file, invalid metadata), 404 not_found, 413 (document_too_large, storage quota exceeded, or sub_tenant_storage_quota_exceeded with current and limit when the knowledge base’s sub-tenant is over its storage quota), 402 (document/chunk limits), 422 (unsupported file type, pdf_extraction_failed,
parse_timeout, content_too_large).
Get Document
GET /v1/documents/{id}
Any key. 404 not_found if it does not exist.
Download Original File
GET /v1/documents/{id}/download
Any key. Returns the stored original file (application/pdf or application/octet-stream). 404 if the
document has no stored file.
Delete Document
DELETE /v1/documents/{id}
Role: write. Response: 204, or 404 not_found.
Get Job
GET /v1/jobs/{id}
Any key. Returns the status of an asynchronous job. job_type is ingestion, transcription or report_generation;
status progresses from queued through processing to completed or failed (with a sanitized error):
{
"id": "uuid",
"tenant_id": "uuid",
"document_id": "uuid",
"knowledge_base_id": "uuid",
"job_type": "ingestion",
"status": "completed",
"error": null,
"total_chunks": 42,
"processed_chunks": 42,
"created_at": "2026-10-04T10:00:00Z",
"completed_at": "2026-10-04T10:00:03Z"
}
Query
Semantic Search
POST /v1/query
Role: read. Rate limited per tenant (see Rate Limits). Optionally send an
X-User-Id header (max 256 characters) to attribute the query to an end user for per-user limits and
preferences.
{
"query": "blood pressure targets",
"top_k": 5,
"knowledge_base_ids": ["uuid"],
"filters": {"tags.specialty": "cardiology", "created_after": "2025-01-01"},
"metadata_filter": {},
"source_language": "auto",
"translate_response": false
}
| Field | Type | Notes |
|---|---|---|
query | string | Required, 1–4096 characters |
top_k | integer | Optional, 1–100, default 10 |
knowledge_base_ids | string[] | Optional. Restrict to these knowledge bases |
filters | object | Optional. Keys: tags.<key>, created_after, created_before, document_title (string values) |
metadata_filter | object | Optional. Metadata filter for platform corpora results |
source_language | string | Optional. auto (default) or one of en, nl, fr, de, es, it, bg, ro |
translate_response | boolean | Optional. Translate result content back to the query language. Not available on the Free plan |
Response 200:
{
"results": [
{
"content": "Blood pressure targets for adults...",
"score": 0.87,
"source": "tenant",
"document_id": "uuid",
"document_title": "Hypertension Guidelines",
"chunk_id": "uuid",
"knowledge_base_id": "uuid",
"chunk_index": 3,
"document_tags": {"specialty": "cardiology"}
}
]
}
Results from your own documents have "source": "tenant" and the document fields above. Results from
platform corpora carry source_record_id and metadata instead. When
translation is in effect, a metadata object is added at the top level with translation_config_source
and translation_latency_ms, and translated results include the original text in content_original.
Response headers: X-RateLimit-Limit, X-RateLimit-Remaining, X-RateLimit-Reset, Retry-After.
Errors: 400 unsupported_language, 402 quota_exhausted (Free plan), 402 payment_required (suspended
account), 403 (response translation on Free plan), 422, 429 rate_limit_exceeded /
per_user_rate_limit_exceeded, 503 translation_unavailable.
Platform Corpora
List Corpora
GET /v1/corpora
Any key.
{
"corpora": [
{
"slug": "pubmed",
"name": "PubMed Abstracts",
"license_info": "…",
"requires_dua": true,
"accepted": false,
"accessible": true
}
]
}
accessible reflects your plan; accepted whether your tenant has accepted the corpus’s Data Use Agreement.
Accept Data Use Agreement
POST /v1/corpora/{slug}/accept-dua
Role: admin. Response 200: {"status": "ok"}. 404 if the corpus does not exist, 403 if it is not
available on your plan.
Translation
Role: read for translate endpoints. Supported languages: en, nl, fr, de, es, it, bg, ro.
Translate
POST /v1/translate
{"text": "Hypertension", "source": "en", "target": "nl"}
Response: {"translated_text": "…", "source": "en", "target": "nl", "latency_ms": 12.3}
Translate Batch
POST /v1/translate/batch
{"texts": ["Hypertension", "Diabetes"], "source": "en", "target": "nl"}
1–100 texts per request. Response: {"translations": ["…", "…"], "source": "en", "target": "nl", "latency_ms": 20.1}
Detect Language
POST /v1/detect
Any key. Body: {"text": "…"}. Response: {"language": "nl", "confidence": 0.973}
Translation Health and Languages
| Endpoint | Description |
|---|---|
GET /v1/translation/health | {"status": "healthy|degraded|disabled", "models_loaded": n, "models_total": n, "models": [{"source", "target", "status"}]} |
GET /v1/translation/languages | Array of supported {"source", "target"} pairs |
Translation errors: 400 (text_too_long, invalid_batch_size, unsupported_language_pair),
503 translation_unavailable, 500 translation_failed.
Translation Settings
Translation configuration object (all fields optional):
{
"query_translation": "auto",
"response_translation": "on_request",
"preferred_language": "nl",
"fallback_behaviour": "passthrough"
}
| Field | Values |
|---|---|
query_translation | auto, never |
response_translation | always, on_request, never |
fallback_behaviour | passthrough, error |
| Endpoint | Role | Description |
|---|---|---|
GET /v1/tenant/settings | admin | Returns {"translation_config": {…}} |
PATCH /v1/tenant/settings | admin | Body {"translation_config": {…}}. The Free plan cannot set response_translation to always (403 free_tier_restriction) |
PATCH /v1/sub-tenants/{id}/users/{user_id}/preferences | admin | Per-user preferences, same body. Enterprise only (403 enterprise_only) |
API Keys
Create API Key
POST /v1/auth/keys
Role: admin.
{"name": "ci-key", "role": "read"}
role must be read, write or admin. Response 201: {"key": "medrag_sk_…", "id": "<uuid>"}. The
key is shown only once.
Revoke API Key
DELETE /v1/auth/keys/{id}
Role: admin. Response: 204.
Usage and Billing
Get Current Usage
GET /v1/usage
Any key.
{
"chunks": {"used": 120, "limit": 5000, "percent": 2.4},
"documents": {"used": 3, "limit": 50, "percent": 6.0},
"storage_bytes": {"used": 10240, "limit": 52428800, "percent": 0.0},
"knowledge_bases": {"used": 1, "limit": 1},
"queries_this_period": {"used": 10, "limit": 1000, "percent": 1.0},
"query_rate_limit_per_min": 10
}
Billing Usage
GET /v1/billing/usage
Role: admin. Usage over the last 30 days plus current-period counters:
{
"plan_type": "standard",
"included_queries": 50000,
"total_requests": 1234,
"total_input_tokens": 0,
"total_output_tokens": 0,
"total_cost_microcents": 0,
"query_count": 1234,
"ingestion_chunks": 560,
"overage_queries": 0,
"overage_cost_microcents": 0
}
Billing Events
GET /v1/billing/events
Role: admin. Currently always returns an empty array.
Plan Changes
Role: admin for all. Plans: free, payg, standard, enterprise. These return 503 billing_not_available
when billing is not enabled on the instance.
| Endpoint | Body | Response |
|---|---|---|
POST /v1/billing/upgrade | {"plan": "standard"} | {"status": "upgraded", "plan": "standard", "prorated_queries": n} |
POST /v1/billing/downgrade | {"plan": "free"} | {"status": "scheduled", "plan": "free"} — applied at the end of the period |
POST /v1/billing/cancel | — | {"status": "cancellation_scheduled"} |
DELETE /v1/billing/cancel | — | {"status": "cancellation_retracted"}, or 404 no_pending_cancellation |
POST /v1/billing/interval | {"billing_interval": "monthly|annual"} | {"status": …, "billing_interval": …} |
POST /v1/billing/renew | — | Renews an annual plan; 409 not_annual_plan otherwise |
Errors: 422 invalid_plan, 409 (already_on_plan, use_downgrade_endpoint, billing_details_required,
cancellation_pending, already_on_free), 422 exceeds_target_plan_limits (downgrade blocked because current
usage exceeds the target plan).
Data Processing Agreement
| Endpoint | Role | Description |
|---|---|---|
GET /v1/dpa/status | any key | {"compliant": bool, "current_version": n, "accepted_version": n|null} |
POST /v1/dpa/accept | admin | Body {"version": n}. Optional X-Accepted-By header records who accepted. Response {"status": "accepted", "dpa_version": n} |
When your tenant has not accepted the current DPA version, API responses carry the header X-DPA-Action-Required: DPA re-acceptance required.
Account
Role: admin.
| Endpoint | Description |
|---|---|
POST /v1/account/export | Exports all account data. Returns download_url (valid for expires_in_seconds: 86400) plus account, knowledge_bases, documents, usage_history |
DELETE /v1/account | Deletes the account and all data. Requires header X-Confirm-Delete: DELETE_MY_ACCOUNT. Returns 202 |
Enterprise: Sub-Tenants
Requires the Enterprise plan and an admin key that is not itself scoped to a sub-tenant
(403 enterprise_plan_required, 403 sub_tenant_keys_cannot_manage_sub_tenants).
Sub-Tenants
| Endpoint | Description |
|---|---|
POST /v1/sub-tenants | Create. Returns 201 (or 200 with the existing record if external_id already exists) |
GET /v1/sub-tenants | List |
GET /v1/sub-tenants/{id} | Get one |
PATCH /v1/sub-tenants/{id} | Update name and/or limits |
DELETE /v1/sub-tenants/{id} | Delete. Returns 204 |
Create body:
{
"name": "Clinic A",
"external_id": "clinic-a",
"max_total_chunks": 100000,
"max_knowledge_bases": 5,
"max_documents": 1000,
"max_document_size_bytes": 26214400,
"query_rate_limit_per_min": 100,
"included_queries_per_period": 10000,
"storage_quota_bytes": 1073741824
}
Only name is required (1–200 characters); every other field is optional. Sub-tenant limits are enforced inside the parent tenant’s plan limits.
Sub-Tenant API Keys
| Endpoint | Description |
|---|---|
POST /v1/sub-tenants/{id}/api-keys | Body {"role": "read|write"}. Returns 201 {"key", "role", "sub_tenant_id"} |
GET /v1/sub-tenants/{id}/api-keys | List keys |
DELETE /v1/sub-tenants/{id}/api-keys/{key_id} | Revoke. Returns 204 |
Keys created here only see knowledge bases and documents of that sub-tenant.
Sub-Tenant Usage
| Endpoint | Description |
|---|---|
GET /v1/sub-tenants/{id}/usage | chunks, documents, queries_this_period, storage_bytes (each {used, limit}; storage counts uploaded files and is freed when a document is deleted) and active_users |
GET /v1/usage/breakdown | {"sub_tenants": [{sub_tenant_id, name, chunks, queries_this_period, active_users}], "total_chunks": n} |
GET /v1/usage/users | Array of {sub_tenant_id, name, active_users} for the current month |
Add-on: Transcription and Reports
Requires the corresponding add-on on your plan (otherwise 403 {"error": "addon_not_enabled", "addon": "transcription|report_generation"})
and the feature to be enabled on the instance (503 scribe_api_not_available). Role: write. Both endpoints are asynchronous: they return a job_id and
deliver the result to your callback_url.
Transcribe Audio
POST /v1/transcribe
Content-Type: multipart/form-data
Maximum request size 25 MB. Parts: audio (required), language, callback_url, callback_secret.
Response 202: {"job_id": "<uuid>"}.
Errors: 400 (unsupported_language, audio_too_long with max_seconds, invalid_callback_url),
503 transcription_unavailable.
Generate Report
POST /v1/reports/generate
{
"transcript": "…",
"callback_url": "https://example.com/hook",
"callback_secret": "…"
}
Response 202: {"job_id": "<uuid>"}.
Errors: 400 (empty transcript, transcript_too_long with max_chars, invalid_callback_url),
503 report_generation_unavailable.
OAuth and MCP
OAuth 2.1 endpoints (/oauth/token, /oauth/revoke, /oauth/register, /.well-known/oauth-*) and the MCP
transport (/mcp/sse, /mcp/message) are documented in MCP Integration and
Authentication.