Arango logo

AutoGraph Corpus Build Reference

Create and monitor corpus builds for document analysis and clustering

Create Corpus Build

POST https://<EXTERNAL_ENDPOINT>:8529/autograph/v1/corpus/builds

Trigger a corpus build from File Manager categories or from files that you have imported directly.

Recommended path: Prefer categories, so that the service resolves the files of each category from the File Manager itself. Send only new categories on a first build and leave incremental at its default of false. To append documents to a category that has already been built, set incremental: true. Only one build can run at a time (409).

Selecting what to build

Provide at most one of the following selectors. A request that carries more than one is rejected with 400.

SelectorStatusWhat it does
categoriesPreferredCategory labels below the current project. The service lists the files of each category from the File Manager.
modulesDeprecated aliasIdentical to categories, and only used when categories is empty.
file_idsDeprecatedExplicit File Manager IDs of RAG inputs.
(none)DeprecatedUses the files staged by POST /v1/import-multiple at the project root.
Category labels are the bare labels, such as legal, exactly as GET /v1/projects/{project}/overview reports them in categories[].name. You never have to construct the internal {project}_legal form yourself. An already encoded value is still accepted as a legacy alias.

When you use the legacy path without a selector, every imported basename has to exist as a RAG input in the File Manager for the same database as well.

Request (preferred - by category)

{
  "embedding_strategy": "first_chunk",
  "categories": ["legal", "finance"],
  "strategy": {
    "top_k": 7,
    "cluster_threshold": 2
  },
  "incremental": false
}

Request (deprecated - by file IDs)

{
  "embedding_strategy": "first_chunk",
  "file_ids": ["file_id_1", "file_id_2", "file_id_3"],
  "strategy": {
    "top_k": 7,
    "cluster_threshold": 2
  }
}

Parameters

ParameterTypeRequiredDescriptionRecommended value
embedding_strategystringNoSelects how text is chosen for embedding.Omit to get "first_chunk", which is the only supported value.
categoriesstring[]NoPreferred. Bare category labels below the current project. The files are resolved server-side through the File Manager.For example ["legal", "finance"]. Mutually exclusive with modules and file_ids.
modulesstring[]NoDeprecated alias for categories, honored only when categories is empty.Use categories.
file_idsstring[]NoDeprecated. Explicit File Manager IDs of RAG inputs.Use categories.
strategyobjectNoTunables for similarity and clustering.Omit to use service defaults (see below).
strategy.top_kintegerNoHow many similar neighbors each document gets (edge count driver).7 (default): good general default. 5–10: typical range; higher = denser graph, more work and API cost.
strategy.cluster_thresholdintegerNoControls clustering depth. 1: flat grouping. 2: hierarchical (default, produces richer structure).2 for most corpora; 1 for simpler/faster clustering or very small document sets.
strategy.custom_paramsmapNoExtra string key/values interpreted by the service (e.g. graph naming).Omit unless your operator documents a key (e.g. graph_name).
incrementalbooleanNoBuild mode. false (default) cleans up and rebuilds the processed categories, and is only accepted if every processed category is new to the corpus. true appends to the processed categories, and on a File Manager category build it also removes corpus documents that are no longer in the current File Manager listing.false for a first build or when you add only new categories. true to append to, and sync deletions of, categories that already exist.

An already built category cannot be rebuilt from scratch. A request with incremental: false that lists a category which is already in the corpus is rejected with REBUILD_NOT_ALLOWED, before anything is deleted. This also applies to a list that mixes new and existing categories.

On the categories path the rejection is synchronous and comes back as 409. On the deprecated file_ids and import-multiple paths, the request can be accepted with 202 first and the build then fails with error_code: REBUILD_NOT_ALLOWED.

Send only new categories with incremental: false, use incremental: true to append, change individual documents with an incremental graph update, or remove the category with DELETE /v1/projects/{project}/categories/{category} and build it again.

Citation URLs

There is no citable_url field on this request. For File Manager builds, set custom_metadata.citable_url on the RAG input when you upload it. AutoGraph reads it while fetching the file and stores it on the source document.

The URL has to be an http or https URL without whitespace or unbalanced parentheses. If the key is missing, empty, or invalid, the build still succeeds, citable_url is stored as an empty string, and the citation numbers stay unlinked. The API does not report the rejection, so check the build logs if links are missing.

Response

{
  "corpus_build_id": "cb_01ARZ3NDEKTSV4RRFFQ69G5FAV",
  "graph_name": "myproject_CorpusGraph"
}
FieldTypeDescription
corpus_build_idstringPoll GET /v1/corpus/builds/{corpus_build_id} with this ID.
graph_namestringThe named graph this build writes to. You do not have to poll for it.
Status CodeMeaning
202Build accepted and started in the background
400Invalid request: more than one selector, an empty category label, a category scope deeper than the File Manager limit, a selector the File Manager has nothing for, or a latched model configuration (see below)
401Authentication failed
409Another build is in progress, a document delete is running, a project deletion has started, or REBUILD_NOT_ALLOWED on a categories request with incremental: false
500Server error
This endpoint returns 202, not 200. Accept any 2xx as “accepted”. The response body is unchanged.
Model configuration gate. If the chat or embedding configuration that the service resolved at startup is invalid, or if a pod is still reloading the persisted settings, this endpoint returns 400 right away instead of queueing a build that would fail while embedding. The same gate applies to POST /v1/rag-strategizer/analyze and POST /v1/orchestrate. Clear it with PUT /v1/projects/{project}/model-config/credentials.

Selector validation

The service looks the selector up in the File Manager while it handles the request, and rejects the build with 400 if there is nothing to build:

  • A categories label that holds no files, on a full build with incremental: false.
  • file_ids that the File Manager cannot resolve, because they are unknown or malformed, or because they belong to another database.

The response names what was missing. No build is created in these cases, so there is no corpus_build_id to poll. Check the status code before you read the ID out of the response body.

Two cases are deliberately not rejected:

  • An incremental build (incremental: true) still accepts a category whose files are all gone, because reconciling such removals is part of what an incremental run is for. See Incremental Builds.
  • If the File Manager cannot be reached at all, the service reaches no verdict and accepts the build as before. An outage is reported on the build, never as a bad request.

A file that is deleted after the check still fails the build the way it always did. The check makes the common mistake visible right away, it does not remove the deferred failure path.

Document parsing

For File Manager builds (categories, modules, or file_ids), the service extracts the text through the File Parsing Service instead of parsing in-process: preview scope, the first 4,800 characters, images excluded. Files uploaded through the deprecated POST /v1/import-multiple are still parsed in-process.

Plain text files (.txt and .md) are parsed directly. Every other input is submitted to the File Parser service, which converts a sample to Markdown before AutoGraph embeds and clusters it. PDF files (including scanned documents, read using OCR) and the Office formats .doc, .docx, .ppt, and .pptx are officially supported. See Supported file formats for details.

Parsing is durable. If the pod restarts in the middle of a build, it picks up the batches it already submitted instead of submitting them again.

Three outcomes are worth planning for:

  • A file that yields no extractable text, such as a scanned image without OCR-readable content, is reported as a failed file. It is no longer embedded as an empty document.
  • If some files parse and others fail, the build completes instead of failing, and GET /v1/corpus/builds/{id} reports error_code: FILE_PARSER_PARTIAL_FAILURE.
  • If no file parses, the build fails with error_code: FILE_PARSER_NO_SUCCESS.

In every case the failing files are named individually in message, as filename (ID: file_id): error, so you can tell which document caused the failure rather than only which batch did:

Build partially completed: 2 file(s) failed in File Parser. Failed files:
report.pdf (ID: rag-input-abc): FILE_TOO_LARGE: source exceeds size limit;
scan.pdf (ID: rag-input-def): no extractable content

A completed build with partial failures lists the first five entries and then ; ... and N more. A build that failed because nothing parsed lists the first ten and then ... and N more file(s). On a large failure set, message alone therefore does not enumerate every affected file.

Scanned and image-heavy documents are much slower to parse than digital text, and the quality of the extraction depends on how legible the scan is. The mechanics belong to the File Parsing Service, which ships on its own release cadence, so consult its documentation for the current per-format behavior.

Chunk limit: by default the first 1200 tokens (~4800 characters) per document drive the embedding. Your operator can tune the embedding budget with CHUNK_SIZE and CHARACTERS_PER_TOKENS, but the 4,800-character extraction cap of the File Parsing Service is fixed and does not move with it.

HTTP Example

curl -X POST \
  -H "Content-Type: application/json" \
  -H "Authorization: Bearer <token>" \
  -d '{
    "embedding_strategy": "first_chunk",
    "categories": ["legal", "finance"],
    "strategy": { "top_k": 10, "cluster_threshold": 2 }
  }' \
  https://<EXTERNAL_ENDPOINT>:8529/autograph/v1/corpus/builds

Monitoring Build Status

GET https://<EXTERNAL_ENDPOINT>:8529/autograph/v1/corpus/builds/:corpus_build_id

Check the progress of a corpus build.

Recommended path: Immediately after POST /v1/corpus/builds returns corpus_build_id, poll this endpoint until status is completed or failed. Typical interval: 5–30 seconds for short builds; 30–60 seconds for very large corpora to avoid load.

Parameters

ParameterLocationRequiredDescriptionRecommended value
corpus_build_idURL pathYesThe id returned by create build.Use the value verbatim (e.g. cb_…).

Response

{
  "corpus_build_id": "cb_01ARZ3NDEKTSV4RRFFQ69G5FAV",
  "status": "running",
  "message": "Creating similarity edges...",
  "progress": 55,
  "error": "",
  "error_code": null,
  "started_at": 1705344000.0,
  "completed_at": 0.0,
  "graph_name": "myproject_CorpusGraph",
  "document_count": 0,
  "cluster_count": 0,
  "documents_added": 0,
  "documents_removed": 0,
  "documents_unchanged": 0,
  "files_written": 0,
  "documents_created": 0,
  "documents_deduplicated": 0,
  "dedup_groups": []
}
FieldTypeDescription
corpus_build_idstringBuild identifier
statusstringpending → running → completed or failed. Only proceed to the Strategizer on completed.
messagestringHuman-readable stage (e.g. similarity or clustering).
progressinteger0–100; use together with message for UI.
errorstringNon-empty when failed. Use for support tickets.
error_codestring | nullMachine-readable failure code. Set when failed, and also on a completed build that had partial file failures. See below.
started_atdoubleUnix epoch seconds (float).
completed_atdoubleSet when finished; 0 while running.
graph_namestringThe named graph of this build, available from pending onward. Same value as the create response.
document_countintegerSize of the whole {project}_sources collection once the build is completed, otherwise 0. On a project with several categories this includes every other category’s sources, so it is a project total, not the contribution of this build.
cluster_countintegerNumber of domain clusters, set on completed, otherwise 0.
documents_addedintegerOn a completed incremental File Manager build: files that were in the File Manager but not yet in the corpus. 0 otherwise.
documents_removedintegerOn a completed incremental File Manager build: corpus documents removed because they were no longer in the File Manager listing. 0 otherwise.
documents_unchangedintegerOn a completed incremental File Manager build: corpus documents that are still in the File Manager. 0 otherwise.
files_writtenintegerOn a completed build: the input files that this build wrote successfully. It counts the File Manager entries the build was given, not your uploads, so a file that a re-upload under the same name superseded was never part of it. Set on every build, not only on incremental ones.
documents_createdintegerOn a completed build: the number of distinct documents that these files produced. It equals files_written minus documents_deduplicated.
documents_deduplicatedintegerOn a completed build: input files that were absorbed into a document another file already owned. A value above 0 means that the build produced fewer documents than it was given files, see Document identity and deduplication.
dedup_groupsobject[]One entry per collapsed document, naming every source file that mapped onto it. Empty when documents_deduplicated is 0.

Every dedup_groups entry has the following fields:

FieldTypeDescription
document_keystringThe _key of the surviving document in {project}_sources.
document_idstringThe _id of the surviving document.
modulestringThe category the document belongs to.
filenamestringThe filename the document is keyed by.
source_filenamesstring[]Every input file that mapped onto this document, in input order. The content of the last one is what the document holds.
source_file_idsstring[]The File Manager IDs of these files, in the same order. Use them to tell files with the same name apart.
collapsed_onto_existingbooleantrue if the collapse also overwrote a document from an earlier build, instead of only colliding within this build.
A documents_deduplicated of 0 does not prove that nothing was lost. A file that a re-upload under the same name superseded within one category is replaced by the File Manager before the build even runs, so it cannot show up in these counters.
A completed build with a non-empty error_code is a partial success. Read error_code even when status is completed.

Build error codes

CodeStatusMeaningWhat to do
UNKNOWN_ERRORfailedThe service could not classify the failure. This is the fallback for any unrecognized error.Read error and message and quote them in a support ticket.
FILE_PARSER_PARTIAL_FAILUREcompletedSome files could not be parsed, or yielded no extractable text, but others did.message names each failing file as filename (ID: file_id): error, the first five only, followed by ; ... and N more. Re-upload or fix those files, then build that category again.
FILE_PARSER_NO_SUCCESSfailedNo file produced any usable text, so there was nothing to embed.message names the first ten failing files in the same form. Check that the documents contain extractable text and are not corrupt or password-protected, see Document parsing.
FILE_PARSER_TIMEOUTfailedThe File Parsing Service did not finish the batch within the deadline. The batch was not cancelled and may still complete on the parser side.message names the batch and up to five of the submitted files. Retry the build; if it recurs, the corpus is probably too slow to parse, for example because it is mostly scanned material.
STORAGE_FILE_TOO_LARGEcompletedA file was larger than the entire local staging space and could therefore never be downloaded, so it was skipped. The remaining files were still processed. Ordinary exhaustion of the staging space is expected on a large build and drops nothing: files are downloaded in waves, and each wave is processed and deleted before the next one starts, so a build of several gigabytes fills and frees the space many times over.message names the skipped File Manager IDs, the first five, then , ... (N more). Split or shrink those files, or ask your operator to raise LOCAL_STORAGE_MAX_BYTES past the size of the largest single file.
REBUILD_NOT_ALLOWEDfailedincremental: false over a category that is already built, on a path where the categories are only resolved after the request was accepted.Use incremental: true, send only new categories, or delete the category and build it again. On the categories path this is a 409 on the create request instead.
LLM_RATE_LIMITEDfailedThe embedding or chat provider throttled the service.Retry later, or lower the embedding concurrency.
LLM_QUOTA_EXCEEDEDfailedThe provider quota for the key is used up.Top up or rotate the key on the secret profile.
LLM_AUTHENTICATION_FAILEDfailedThe provider rejected the API key.Fix it with PUT …/model-config/credentials.
LLM_API_KEY_MISSINGfailedNo chat or embedding key is configured on the service.Fix it with PUT …/model-config/credentials.
LLM_PERMISSION_DENIEDfailedThe API key is valid but has no access to the model.Fix it with PUT …/model-config/credentials.

Two different situations report STORAGE_FILE_TOO_LARGE:

  • A file larger than the whole staging space is skipped on its own, and the wave carries on with the files behind it.

  • Separately, if a wave can download nothing at all and no staging space can be reclaimed, every file still queued is abandoned and reported as Local storage budget exhausted; could not download remaining files. Those files are not too large and succeed on a retry once the staging space is free.

Check the per-file text before you split or shrink anything.

This list is not closed. Any failure the service cannot classify is reported as UNKNOWN_ERROR, so if you switch on error_code, you need a fall-through branch that surfaces message verbatim.

The two partial-success codes are not alternatives. A build can hit parse failures and the staging budget in the same run. error_code is then FILE_PARSER_PARTIAL_FAILURE, which takes precedence, and message carries both texts. If you branch on STORAGE_FILE_TOO_LARGE alone, you never learn that files were skipped, so read message, not only error_code.

Status CodeMeaning
200Status returned
401Authentication failed
404Unknown or expired corpus_build_id. The status is held in memory, evicted after 24 hours, and lost on a pod restart.
500Server error

HTTP Example

curl -H "Authorization: Bearer <token>" \
  https://<EXTERNAL_ENDPOINT>:8529/autograph/v1/corpus/builds/cb_01ARZ3NDEKTSV4RRFFQ69G5FAV

Document identity and deduplication

A corpus document is identified by its category and its filename, and by nothing else. The document key is a hash of exactly these two values, and the writes use the update overwrite mode of ArangoDB. That has the following consequences:

  • Two input files with the same filename in the same category produce a single document. The one written last provides the content, the other one is absorbed. This is what makes a re-import idempotent instead of duplicating rows, and it is reported in documents_deduplicated and dedup_groups.
  • The deduplication is by name, never by content. No content is hashed, no embedding similarity is compared, and no similarity threshold is involved. Two documents whose extracted text is identical, a shared boilerplate cover page for example, stay two documents as long as their filenames differ.
  • The category scopes the name. A cover.pdf in legal and a cover.pdf in finance are two separate documents.
  • Filenames are disambiguated on the way in. If a build resolves several File Manager files that share a basename, they are renamed to report.pdf, report_1.pdf, and so on, before any of the above applies. The counter is shared by the entire build, so the renaming also occurs between files in different categories.
  • A re-upload under an existing name never reaches this rule. The File Manager keys a RAG input by database, project, category, and name, so uploading the same name into the same category supersedes the earlier file with a new version instead of adding a second entry. Only the latest version of a file is built, and this loss occurs before AutoGraph sees anything.

When a collapse actually happens

Because names are unique within a category and are disambiguated across categories, few sequences reach a collapse. The known one is a filename that collides with the output of the disambiguation:

UploadCategoryStored asResult
a.mdarchivea.md—
a.mdlegala_1.mdRenamed, because a.md already occurred in archive
a_1.mdlegala_1.mdCollides with the row above: the same category and the same name

Three File Manager entries become two documents, with documents_deduplicated: 1. The renaming does not reserve the name it generates, so a file that is genuinely called a_1.md is not disambiguated against it. Watch out for this if you use systematic _1 and _2 suffixes in your filenames and reuse those basenames across categories.

Which files a document came from

Every document records its own mapping, so you do not have to reconstruct the key hash. The documents in the {project}_sources collection have the following fields:

FieldDescription
source_filenamesEvery input file that mapped onto this document, in input order.
source_file_idsThe File Manager IDs of the same files, in the same order.

Both are on every document, with a single entry in the ordinary case where nothing collapsed, so you never have to handle their absence. Documents that were written before these fields existed have neither. Import them again to populate them.

If documents_deduplicated is not what you expected:

  1. Read dedup_groups. It names the exact files and their File Manager IDs.
  2. If the collapse was unintended, upload the absorbed file under a filename that is distinct and not of the form name_1 or name_2 for a basename that is used elsewhere in the build, then add it with POST /v1/graph/insert. Uploading it under the same name in the same category does not help, because the File Manager supersedes the earlier version instead of creating a second entry.
  3. To replace the content of a document deliberately, use POST /v1/graph/update instead of relying on a re-import under the same name.

If 5% or more of a batch collapses, the completion message of the build says so as well, so a high deduplication ratio is visible without reading the counts.


Incremental Builds

An incremental build appends documents to categories that already exist, without rebuilding the rest of the corpus. For guidance on structuring categories, see the Design Guide.

When to use an incremental build:

  • Adding documents to a category that has already been built.
  • Syncing deletions: on a File Manager category build, incremental: true also removes corpus documents that are no longer in the File Manager listing.
  • Reducing the build time for large corpora.

When not to use one:

  • For a first build, or when you add a category that is new to the corpus, leave incremental at its default of false.
  • To change individual documents in a graph that is already built, use an incremental graph update instead.
A corpus build never removes Layer 3 records, whether incremental is true or false. Removing knowledge graph data is exclusive to DELETE /v1/projects/{project}/categories/{category}.

Next Steps