Skip to main content

Documentation Index

Fetch the complete documentation index at: https://mintlify.com/No-Country-simulation/G9-LATAM-Team-58/llms.txt

Use this file to discover all available pages before exploring further.

When you have a backlog of technical content to add to Mindloom — scraped articles, exported notes, or a curated reading list — the batch upload endpoint lets you ingest them all in a single HTTP request. You provide a CSV file; the API parses each row, runs the full ingestion pipeline (inference, INSERT, embedding UPDATE, related lookup) for every valid entry, and returns a structured summary showing which rows succeeded, which failed, and how the successful items were distributed across categories. Errors on individual rows never abort the whole batch.

File format

The endpoint expects a multipart/form-data request with a single field named file:
POST /contents/batch HTTP/1.1
Content-Type: multipart/form-data; boundary=----X
The CSV file must have exactly two columns: title and body.
title,body
"Índices vectoriales","Un índice HNSW aproxima el vecino más cercano"
"React y Suspense","Suspense permite declarar estados de carga"

Format rules

Header row

The first line is always discarded, regardless of its content. If your CSV has no header row, the first data row is silently lost.

Two columns only

Only title and body can be provided. category is predicted by the inference service; source is fixed to "user" and language to "es".

Double-quote escaping

Fields containing commas must be wrapped in double quotes. Multi-line fields are not supported — a newline inside a quoted field splits the row and produces invalid entries.

Encoding

UTF-8 is expected. Non-UTF-8 files may parse incorrectly without an explicit error.

Pre-processing validation

The controller performs these checks before calling any service:
ConditionHTTP StatusError code
File field is empty400VALIDATION_ERROR — “El archivo no puede estar vacío”
Filename does not end in .csv400VALIDATION_ERROR — “El archivo debe ser un CSV”
File exceeds 5 MB400VALIDATION_ERROR — “El archivo excede el tamaño máximo permitido”
Database not configured503INTERNAL_ERROR
The .csv check is extension-only, not content-based. A file named data.csv that actually contains Excel XML will pass validation and then produce row-level parse errors inside the batch.

Per-row processing

Each valid row calls IContentIngestionService.ingest() — the exact same method used by POST /content. For every row this means:
  1. POST /predict to the inference service (body only)
  2. INSERT INTO contents (JPA, without embedding)
  3. UPDATE contents SET embedding = TO_VECTOR(?, 384, FLOAT32) (JDBC, embedding string via VectorUtils.toVectorString())
  4. SELECT … ORDER BY VECTOR_DISTANCE(COSINE) FETCH FIRST 5 ROWS ONLY (neighbors — computed and discarded; not included in the batch response)
A row is skipped and added to errors if it has fewer than two columns or contains blank values for title or body. All other rows continue processing regardless.

Response format

The endpoint always returns HTTP 200. The response body contains the full per-batch accounting:
{
  "processed": 2,
  "failed": 1,
  "ids": [
    "usr-9f3c1e0a-42b8-4d17-9a55-7c0e1b2d3f44",
    "usr-1a2b3c4d-5e6f-4071-8293-a4b5c6d7e8f9"
  ],
  "errors": [
    { "row": 3, "reason": "Título y cuerpo no pueden estar vacíos" }
  ],
  "byCategory": {
    "Bases de datos": 1,
    "Frontend": 1
  }
}

Response fields

FieldTypeDescription
processedintegerNumber of rows successfully ingested (ids.size()).
failedintegerNumber of rows that produced an error (errors.size()).
idsstring[]Assigned IDs for every successfully ingested row, in order.
errorsobject[]Per-row error details. Each entry has row (1-based, after the header) and reason (in Spanish).
byCategoryobjectCount of successfully ingested items per predicted category.
processed + failed equals the total number of non-header rows in the file. Row numbers in errors are 1-based starting after the header — the second line of the file is row: 1.

Critical warnings

The batch endpoint always returns HTTP 200, even when every single row fails and processed is 0. Your code must inspect the failed field and the errors array — not the HTTP status code — to determine whether the upload succeeded.
Each row is committed in its own transaction. ingest() is annotated @Transactional, but the outer batch loop is not. If row 300 fails after rows 1–299 have succeeded, those 299 rows are already committed to the database and cannot be rolled back automatically. To undo a partial upload, issue DELETE requests using the ids returned in the response.

Performance considerations

A 500-row CSV triggers approximately 500 sequential calls to the inference service, plus 1 500 database operations (INSERT + UPDATE + SELECT per row). With realistic inference latency, this can take several minutes. The HTTP connection stays open for the entire duration — there is no streaming or progress callback.Recommendation: keep batches to roughly 50 rows. This keeps response times under a few seconds and makes partial failures easier to diagnose and retry.

Worked example

Given this four-row CSV (one header + three data rows):
title,body
"Índices vectoriales","Un índice HNSW aproxima el vecino más cercano"
"React y Suspense","Suspense permite declarar estados de carga"
,"Cuerpo sin título"
The response would be:
{
  "processed": 2,
  "failed": 1,
  "ids": [
    "usr-9f3c1e0a-42b8-4d17-9a55-7c0e1b2d3f44",
    "usr-1a2b3c4d-5e6f-4071-8293-a4b5c6d7e8f9"
  ],
  "errors": [
    { "row": 3, "reason": "Título y cuerpo no pueden estar vacíos" }
  ],
  "byCategory": {
    "Bases de datos": 1,
    "Frontend": 1
  }
}
Row 3 ("Cuerpo sin título") failed because title was blank. Rows 1 and 2 were committed successfully and their IDs are in the ids array. For the full endpoint reference including multipart field names and error envelope shapes, see the POST /contents/batch API reference.

Build docs developers (and LLMs) love