---
title: "Replace Document Original"
method: POST
path: "/api/folders/documents/{document_id}/replace-original"
tags: ["domains"]
---

# Replace Document Original

`POST /api/folders/documents/{document_id}/replace-original`

## Request

- Base URL: `https://app.thinqos.com` (the document declares no server; this is the origin it was published from)
- URL: `https://app.thinqos.com/api/folders/documents/{document_id}/replace-original`
- Auth: none declared

Upload a NEW version of a document's retained original file (TOS-2452).

Unlike ``PUT .../content`` (which edits the TEXT and keeps a text
original in sync), this replaces the ORIGINAL FILE itself - the "upload a
new version" flow for a document that retains an original, including a
binary one (pdf/docx). The uploaded bytes become the new original; text is
re-extracted from them with the same extractor the bulk-ingest job uses
(``services.file_extractors.extract_text_for_filename``), and that
extracted text becomes the document's ``full_text`` via the normal save
fan-out. Media documents (``document_kind == 'media'``) are out of scope -
those are replaced by re-uploading via ``POST /api/folders/media`` instead.

Guard order: visibility (404) -> edit rung on shared docs (403) ->
``document_kind`` must be ``'text'`` (409 ``not_editable_kind``) -> an
original must already be retained (409 ``original_not_retained``) ->
base64/size (400/413) -> extraction must yield non-empty text (422
"could not extract text").

Ordering/transaction note: the object-store PUT and the ``doc.original_*``
metadata updates happen BEFORE ``save_document_content`` runs, in that
order, so the metadata update lands on the SAME ``doc`` object
``save_document_content`` commits - if its own transaction later fails and
rolls back, the metadata rolls back with it. The object-store PUT itself
is NOT part of that transaction (object stores aren't transactional), so
the OLD bytes are snapshotted first and restored best-effort when the
save core fails - the desync window shrinks to "the compensating restore
also failed", which is reported to Sentry (adversarial review on #3964,
P3; a binary original is higher-stakes than the text-resync path this was
modeled on). ``save_document_content`` is called with
``sync_original_bytes=False`` so it does not overwrite the just-written
binary with the extracted text.

## Path parameters

- `document_id` string, uuid, required

## Headers

- `x-impersonate-identity-id` string, nullable

## Request body

- ReplaceDocumentOriginalRequest
  - `filename` string, required
  - `mime_type` string, required
  - `content_base64` string, required

## Response `200`

Successful Response

- DocumentContentSaveResponse
  - `id` string, uuid, required
  - `source_id` string, required
  - `title` string
  - `status` string, required
  - `chunk_count` integer, required
  - `indexed_at` string, date-time, nullable, required
  - `error_message` string, nullable, required
  - `scope` string, required
  - `library_id` string, uuid, nullable
  - `tags` string[]
  - `created_at` string, date-time, nullable, required
  - `modified_at` string, date-time, nullable
  - `size_bytes` integer, nullable
  - `has_original` boolean
  - `document_kind` string
  - `slug` string, nullable
  - `original_content_type` string, nullable
  - `original_filename` string, nullable
  - `title_source` string, nullable
  - `has_fit` boolean
  - `deleted_at` string, date-time, nullable
  - `mind_extraction` MindExtractionScopeResponse: How much of a document actually reached Mind extraction (TOS-2063). Historical metadata from the retired inline document extraction worker. Existing document responses retain this read-only record; current teaching uses provenance imprints and resumable episode state instead.
    - `episodes_taught` integer, required
    - `episodes_total` integer, required
    - `truncated` boolean, required
  - `source_time` SourceTime: When source content happened, was observed, and entered thinqOS.
    - `occurred_at` string, date-time, nullable
    - `observed_at` string, date-time, nullable
    - `recorded_at` string, date-time, nullable
    - `precision` 'instant' | 'day' | 'month' | 'year', nullable
    - `provenance` 'stated' | 'parsed_from_title' | 'parsed_from_body' | 'inferred' | 'unknown'
    - `timezone` string, nullable
    - `evidence` string, nullable
  - `published` boolean
  - `sync_excluded` boolean
  - `mind_mode` 'immediate' | 'first_save' | 'never'
  - `mind_refresh_status` 'current' | 'queued' | 'failed'
  - `full_text` string, nullable, required
  - `content_hash` string, nullable
  - `draft_content` string, nullable
  - `draft_saved_at` string, date-time, nullable
  - `draft_based_on_hash` string, nullable
  - `synced_to` DocumentSyncedTo: TOS-7114: the out sync that sends this document's folder to GitHub. ``path`` is the file's place in the repository, or ``None`` when the sync has no file for it (kept out of syncs, removed, or not sent yet). ``library_id`` is the folder the sync belongs to, which may be a folder above the document's own.
    - `sync_id` string, uuid, required
    - `library_id` string, uuid, required
    - `repo` string, required
    - `path` string, nullable
  - `imprints_refreshed` integer
  - `mind_refresh_failed` boolean
  - `version_number` integer

## Other responses

- `422`: Validation Error

---

[API](https://skmtc.dev/thinqos/apis/thinqos-api.md) · [All operations](https://skmtc.dev/thinqos/apis/thinqos-api/llms.txt) · [OpenAPI document](https://skmtc.dev/thinqos/apis/thinqos-api/revisions/5387b4ded614?raw)
