Documents — MDDB API

Document management operations

Add or update document #

POST /v1/add

Add a new document or update an existing one. If a document with the same collection, key, and language exists, it will be updated and a new revision will be created.

Authorization: BearerAuth or APIKeyAuth or none

Request body #

Required.

Content type application/json: AddRequest

{
  "collection": "blog",
  "contentMd": "# Hello World\n\nWelcome to MDDB!",
  "expectedRevision": "string",
  "key": "hello-world",
  "lang": "en_US",
  "meta": {
    "author": [
      "John Doe"
    ],
    "category": [
      "blog"
    ]
  }
}

Responses #

Status Description Body
200 Document added/updated successfully. Since v2.18.8 the document carries revision (INT-021) and, when the write queued an embedding, indexing (GO-042): searchable by text now, by meaning once the job finishes. AddResponse (application/json)
409 expectedRevision did not match; nothing was written RevisionConflict (application/json)
507 (v2.18.9+) Refused by a collection quota or the disk reserve / vector RAM limit; nothing was written (GO-043) QuotaExceeded (application/json)
400 Invalid request ErrorResponse (application/json)
403 Read-only mode ErrorResponse (application/json)

Batch add documents #

POST /v1/add-batch

Add multiple documents to a collection in a single request. Uses the optimized batch processor for high throughput. Fires all post-commit hooks (embedding, FTS indexing, webhooks, TTL, automation triggers).

Authorization: BearerAuth or APIKeyAuth or none

Request body #

Required.

Content type application/json: AddBatchRequest

{
  "collection": "blog",
  "documents": [
    {
      "contentMd": "# Hello World",
      "key": "post1",
      "lang": "en",
      "meta": {
        "category": [
          "blog"
        ]
      },
      "saveRevision": false
    }
  ]
}

Responses #

Status Description Body
200 Batch result AddBatchResponse (application/json)
507 (v2.18.9+) Refused by a collection quota or the disk reserve / vector RAM limit; nothing was written (GO-043) QuotaExceeded (application/json)
400 Invalid request ErrorResponse (application/json)
403 Read-only mode ErrorResponse (application/json)

Submit async bulk ingest job #

POST /v1/bulk-ingest-job

Queue a long-running bulk ingest job and return immediately with a job identifier. Clients should poll /v1/bulk-ingest-job/{id} for progress or supply callbackUrl to receive a POST with the final job record on completion.

Jobs are processed by a single background worker in FIFO order. Payloads are kept in memory — if the server restarts mid-job, the status record is flipped to failed on the next start.

Authorization: BearerAuth or APIKeyAuth or none

Request body #

Required.

Content type application/json: BulkIngestSubmitRequest

{
  "callbackUrl": "https://example.com/webhook",
  "collection": "blog",
  "documents": [
    {
      "contentMd": "# Hello World",
      "key": "post1",
      "lang": "en",
      "meta": {
        "category": [
          "blog"
        ]
      },
      "saveRevision": false
    }
  ]
}

Responses #

Status Description Body
202 Job accepted BulkIngestJob (application/json)
400 Invalid request ErrorResponse (application/json)

Get bulk ingest job status #

GET /v1/bulk-ingest-job/{id}

Returns the current status record — including counters, timestamps, and up to 50 error messages.

Authorization: BearerAuth or APIKeyAuth or none

Parameters #

Name In Type Description
id (required) path string Job identifier returned from submit

Responses #

Status Description Body
200 Job status BulkIngestJob (application/json)
404 Job not found

Cancel pending bulk ingest job #

DELETE /v1/bulk-ingest-job/{id}

Only jobs in pending status can be cancelled. In-flight jobs must run to completion.

Authorization: BearerAuth or APIKeyAuth or none

Parameters #

Name In Type Description
id (required) path string

Responses #

Status Description Body
200 Job cancelled
400 Job already started or completed

List bulk ingest jobs #

GET /v1/bulk-ingest-jobs

Returns all jobs newest-first, optionally filtered by collection.

Authorization: BearerAuth or APIKeyAuth or none

Parameters #

Name In Type Description
collection query string Filter by collection name

Responses #

Status Description Body
200 Job list object (application/json)
400 Bulk ingest is not initialized ErrorResponse (application/json)
401 Auth is on (MDDB_AUTH_ENABLED=true) and the request carries no credentials, an invalid or expired JWT, or an unknown API key. ErrorResponse (application/json)
403 The caller lacks the permission the route needs (SEC-019 access gate, auth on: read, write or admin on the collections the request names), or a write meets read-only mode (MDDB_MODE=read or the protocol's own mode). ErrorResponse (application/json)
429 The client is over the API rate limit (MDDB_RATE_LIMIT_ENABLED=true, MDDB_RATE_LIMIT_REQUESTS per MDDB_RATE_LIMIT_WINDOW seconds). A call that embeds can also be refused by the collection's daily embedding budget, with a QuotaExceeded body. RateLimitError (application/json)

401 example (application/json)

{
  "error": "missing authentication"
}

403 example (application/json)

{
  "error": "forbidden"
}

Bulk ingest documents #

POST /v1/ingest

Advanced bulk ingest endpoint for scraping pipelines and data import workflows. Supports URL key derivation, YAML frontmatter extraction, content deduplication, auto-metadata injection, and collection auto-configuration.

Authorization: BearerAuth or APIKeyAuth or none

Request body #

Required.

Content type application/json: IngestRequest

{
  "collection": "imported",
  "documents": [
    {
      "contentBase64": "string",
      "contentMd": "# Page 1\n\nContent here.",
      "extractFrontmatter": false,
      "format": "pdf",
      "images": [
        {}
      ],
      "key": "page1",
      "lang": "en",
      "scrapedAt": 1709500000,
      "scraper": "my-crawler",
      "ttl": 86400,
      "url": "https://example.com/page1"
    }
  ],
  "options": {
    "autoConfigureCollection": false,
    "extractImages": false,
    "fetchRemoteImages": false,
    "profile": "default",
    "saveRevision": false,
    "skipDuplicates": false,
    "skipEmbeddings": false,
    "skipFts": false,
    "skipWebhooks": false,
    "textOnly": false
  }
}

Responses #

Status Description Body
200 Ingest result IngestResponse (application/json)
507 (v2.18.9+) Refused by a collection quota or the disk reserve / vector RAM limit; nothing was written (GO-043) QuotaExceeded (application/json)
400 Invalid request ErrorResponse (application/json)
403 Read-only mode ErrorResponse (application/json)

Upload files with auto-conversion to Markdown #

POST /v1/upload

Upload files via multipart/form-data. Files are auto-converted to Markdown and stored as documents. Supports single and batch upload.

Supported formats: .md, .txt, .html, .htm, .pdf, .docx, .odt, .rtf, .xlsx, .ods, .csv, .tsv, .pptx, .odp, .epub, .eml, .mbox, .yaml, .yml, .log, .lex, .tex, .latex. Default (md/txt) is stored as-is with frontmatter extraction; others are converted. ODT and DOCX are ZIP-based with XML extraction. RTF is parsed to plain text. LaTeX/TeX is converted (sections, formatting, environments). YAML/LOG/LEX are wrapped in code blocks.

(v2.18.9+, GO-046) XLSX and ODS become one section per sheet with a table, dates as ISO 8601; CSV/TSV a table (separator detected). PPTX and ODP become one section per slide, speaker notes included. EPUB is read in spine order. EML and MBOX put from, to, date, subject and messageId into meta, and each convertible attachment (and each message of a mailbox) becomes a document of its own with meta.parent, listed under parts. The format is taken from the content where it has a signature (%PDF, a zip's main part, {\rtf), so a PDF named .txt is converted as a PDF; binary content no converter recognises is refused. Every converted document carries meta.conversionConfidence (0-1) and meta.conversionWarnings.

Authorization: BearerAuth or APIKeyAuth or none

Request body #

Required.

Content type multipart/form-data: object

Property Type Description
file string (binary) Single file to upload
files[] array of string (binary) Multiple files to upload
collection (required) string Target collection name
lang (required) string Document language code (e.g. en_US)
key string Document key (derived from filename if empty)
meta string JSON-encoded metadata map
ttl integer Time-to-live in seconds
maxSize integer Per-file size limit in bytes (default 10MB, max 100MB)
profile string Ingest profile, as for /v1/ingest (default, fast)
textOnly string Extract plain text from html, docx, odt and epub instead of rebuilding Markdown structure One of "true", "false".
extractImages string (v2.18.9, RAG-010) Images in the files become image documents; image files sent with a Markdown file are the files its links name, not documents of their own. On by default in a collection of type images, where an image file uploaded on its own is an image document. One of "true", "false".
fetchRemoteImages string (v2.18.9, RAG-010) Fetch Markdown image links to http(s) URLs, SSRF-guarded. One of "true", "false".
{
  "collection": "string",
  "extractImages": "true",
  "fetchRemoteImages": "true",
  "file": "string",
  "files[]": [
    "string"
  ],
  "key": "string",
  "lang": "string",
  "maxSize": 0,
  "meta": "string",
  "profile": "string",
  "textOnly": "true",
  "ttl": 0
}

Responses #

Status Description Body
200 File(s) uploaded and stored oneOf UploadResponse | UploadBatchResponse (application/json)
400 Bad request ErrorResponse (application/json)

Get document #

POST /v1/get

Retrieve a document by collection, key, and language. Supports template variable substitution using the env parameter.

Authorization: BearerAuth or APIKeyAuth or none

Request body #

Required.

Content type application/json: GetRequest

{
  "collection": "blog",
  "env": {
    "siteName": "My Blog",
    "year": "2025"
  },
  "key": "hello-world",
  "lang": "en_US"
}

Responses #

Status Description Body
200 Document retrieved successfully Document (application/json)
400 Invalid request ErrorResponse (application/json)
404 Document not found ErrorResponse (application/json)

Delete document #

POST /v1/delete

Delete a specific document by collection, key, and language

Authorization: BearerAuth or APIKeyAuth or none

Request body #

Required.

Content type application/json: DeleteRequest

{
  "collection": "blog",
  "key": "hello-world",
  "lang": "en_US"
}

Responses #

Status Description Body
200 Document deleted successfully object (application/json)
400 Invalid request ErrorResponse (application/json)
404 Document not found (v2.18.8; was 400 before, DOC-018) ErrorResponse (application/json)
403 Read-only mode ErrorResponse (application/json)

Batch delete documents #

POST /v1/delete-batch

Delete multiple documents in a single request

Authorization: BearerAuth or APIKeyAuth or none

Request body #

Required.

Content type application/json: object

Property Type Description
collection (required) string Target collection
documents (required) array of object
documents[].key string
documents[].lang string
{
  "collection": "blog",
  "documents": [
    {
      "key": "post-1",
      "lang": "en"
    },
    {
      "key": "post-2",
      "lang": "en"
    }
  ]
}

Responses #

Status Description Body
200 Batch delete result object (application/json)
400 Invalid request ErrorResponse (application/json)
403 Forbidden ErrorResponse (application/json)

Delete collection #

POST /v1/delete-collection

Delete all documents in a collection

Authorization: BearerAuth or APIKeyAuth or none

Request body #

Required.

Content type application/json: DeleteCollectionRequest

{
  "collection": "blog"
}

Responses #

Status Description Body
200 Collection deleted successfully object (application/json)
400 Invalid request ErrorResponse (application/json)
403 Read-only mode ErrorResponse (application/json)

Partial document update #

PATCH /v1/update

Update document metadata and/or content independently. Only provided fields are updated; omitted fields remain unchanged. Use meta: {} to clear all metadata.

Authorization: BearerAuth or APIKeyAuth or none

Request body #

Required.

Content type application/json: object

Property Type Description
collection (required) string
key (required) string
lang (required) string
meta object If provided, replaces all metadata. Use {} to clear.
contentMd string If provided, replaces document content.
ttl integer If provided, updates TTL in seconds. 0 = no expiry.
expectedRevision string (v2.18.8, INT-021) Update only if the document is at this revision (from a read or write); otherwise 409.
{
  "collection": "blog",
  "key": "p1",
  "lang": "en",
  "meta": {
    "tag": [
      "go",
      "updated"
    ]
  }
}

Responses #

Status Description Body
200 Updated document, with its new revision AddResponse (application/json)
507 (v2.18.9+) Refused by a collection quota or the disk reserve / vector RAM limit; nothing was written (GO-043) QuotaExceeded (application/json)
400 Missing required fields or no fields to update
404 Document not found
409 expectedRevision did not match; nothing was written RevisionConflict (application/json)
405 Method not allowed

Get document metadata only #

GET /v1/doc-meta

Returns metadata for a document without content. Lightweight read useful for checking tags without transferring document body.

Authorization: BearerAuth or APIKeyAuth or none

Parameters #

Name In Type Description
collection (required) query string Collection name
key (required) query string Document key
lang query string Language code (defaults to "en") Default "en".

Responses #

Status Description Body
200 Document metadata object (application/json)
400 Missing required parameters
404 Document not found

An image document's bytes #

GET /v1/image

(v2.18.9, RAG-010) The bytes of an image document, served with the type the bytes have (never a stored claim) and nosniff. The url of every ImageRef a search returns points here.

Authorization: BearerAuth or APIKeyAuth or none

Parameters #

Name In Type Description
collection (required) query string
key (required) query string
lang (required) query string

Responses #

Status Description Body
200 The image string (binary) (image/jpeg)
400 Missing collection, key or lang
403 Forbidden
404 No image under that key
405 Method not allowed

Delete every document a metadata filter matches #

POST /v1/documents:deleteWhere

GO-045, v2.18.8. Each document goes through the single-document delete. Use dryRun first. At most limit per call; continue with cursor.

Authorization: BearerAuth or APIKeyAuth or none

Request body #

Required.

Content type application/json: WhereRequest

{
  "collection": "string",
  "cursor": "string",
  "dryRun": false,
  "filter": {
    "kind": {
      "not": [
        "test"
      ]
    },
    "modified": {
      "gte": "2026-09-01"
    },
    "path": {
      "prefix": "internal/"
    },
    "size": {
      "lt": 10000
    },
    "summary": {
      "exists": true
    },
    "title": {
      "contains": "hnsw"
    }
  },
  "limit": 1000,
  "ttl": 0
}

Responses #

Status Description Body
200 What matched and what was affected WhereResult (application/json)
400 No filter, bad limit, or invalid request ErrorResponse (application/json)
403 Forbidden, or read-only mode (a dry run is allowed)

Merge metadata into every document a filter matches #

POST /v1/documents:updateWhere

Authorization: BearerAuth or APIKeyAuth or none

Request body #

Required.

Content type application/json: WhereRequest

{
  "collection": "string",
  "cursor": "string",
  "dryRun": false,
  "filter": {
    "kind": {
      "not": [
        "test"
      ]
    },
    "modified": {
      "gte": "2026-09-01"
    },
    "path": {
      "prefix": "internal/"
    },
    "size": {
      "lt": 10000
    },
    "summary": {
      "exists": true
    },
    "title": {
      "contains": "hnsw"
    }
  },
  "limit": 1000,
  "ttl": 0
}

Responses #

Status Description Body
200 What matched and what was affected WhereResult (application/json)
400 No filter, no meta, or invalid request
403 Forbidden, or read-only mode (a dry run is allowed)

Set the time to live of every document a filter matches #

POST /v1/documents:setTTLWhere

Authorization: BearerAuth or APIKeyAuth or none

Request body #

Required.

Content type application/json: WhereRequest

{
  "collection": "string",
  "cursor": "string",
  "dryRun": false,
  "filter": {
    "kind": {
      "not": [
        "test"
      ]
    },
    "modified": {
      "gte": "2026-09-01"
    },
    "path": {
      "prefix": "internal/"
    },
    "size": {
      "lt": 10000
    },
    "summary": {
      "exists": true
    },
    "title": {
      "contains": "hnsw"
    }
  },
  "limit": 1000,
  "ttl": 0
}

Responses #

Status Description Body
200 What matched and what was affected WhereResult (application/json)
400 No filter, no ttl, or invalid request
403 Forbidden, or read-only mode (a dry run is allowed)