# Push documents from your own systems
Source: https://nordvec.com/docs/guides/how-to/push-documents#replace-a-whole-datasource

## Replace a whole datasource [#replace-a-whole-datasource]

When your system can list everything a datasource should hold, send the full
listing as one **upload session**, and the documents it no longer contains are
moved to the trash when the session closes. Sessions need an indexing API key
holding `index:delete` as well as `index:write`, because the close removes
documents; the key that opens one is the only key that can continue it.

1. Send the first page with `"isFirstPage": true`. It is page `0`.
2. Send every further page with its `pageIndex` (`1`, `2`, ...), in any order.
   A page sent twice counts once, so a retry is always safe.
3. Send the last page with `"isLastPage": true` and its `pageIndex`. A listing
   that fits in one page sends `isFirstPage` and `isLastPage` together. The
   last page may carry no documents.

Every page uses the same `uploadId`, and each answer carries the session's
progress under `upload`. The session closes only when every page from `0` to
the last has arrived. Closing it moves to the trash each document in the
datasource that no page of the session named and that existed before the
session opened. Any other push to the datasource while the session runs keeps
the document it names: a single push, a batch without session fields, a
permissions update, and a re-push of unchanged content alike. The trash keeps
what the close moved there for 30 days; pushing a document again brings it
back, and so does restoring the whole session (see below).

A session that receives no page for 24 hours expires and closes without
removing anything. A refused page is answered with `409 Conflict`, writes
nothing, and its `data.reason` says why:

| `reason`                                               | What to do                                                                                                                                                                                                                                                                                                  |
| ------------------------------------------------------ | ----------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------- |
| `upload_incomplete`                                    | Send the pages listed in `missingPageIndexes`, then the last page again                                                                                                                                                                                                                                     |
| `deletion_confirmation_required`                       | The close would trash more than 20% of the datasource. If that is right, send the last page again with `"confirmDeletions"` set to `wouldTombstone`                                                                                                                                                         |
| `deletion_confirmation_too_large`                      | `confirmDeletions` is larger than the number of documents the datasource held when the session opened. Send the count you expect to remove                                                                                                                                                                  |
| `upload_in_progress`                                   | A session is open on this datasource. If it is your key's, finish it, wait for it to expire, or start over with `"forceRestartUpload": true` on your first page. If another key opened it, `forceRestartUpload` replaces it only once it has received no page for an hour, from the time in `restartableAt` |
| `upload_expired`, `upload_missing`, `upload_restarted` | The session is gone; start a new one with a new `uploadId`                                                                                                                                                                                                                                                  |
| `upload_closed`, `upload_id_reused`                    | The `uploadId` is spent; use a new one                                                                                                                                                                                                                                                                      |
| `page_index_required`                                  | Your key has a session open on this datasource; send `pageIndex` with the page                                                                                                                                                                                                                              |

To resume after a crash, read the session with
`GET /documents/push/upload?tenantId=...&datasource=...&uploadId=...` (scope
`index:status`). Its `missingPageIndexes` lists the pages still to send.

### Undo a session's close [#undo-a-sessions-close]

If a session removed documents it should not have, for example because the
listing it sent was cut short, restore them in one call:

```bash
curl https://nordvec.com/api/v1/documents/push/upload/restore \
  -H "Authorization: Bearer $NORDVEC_API_KEY" \
  -H "Content-Type: application/json" \
  -d '{ "tenantId": "YOUR_WORKSPACE_ID", "datasource": "confluence-export", "uploadId": "nightly-2026-09-28" }'
```

The key that opened the session can restore it, and so can a workspace admin
signed in to Nordvec, for a session opened by any key. Every document the close
moved to the trash comes back with the content it had, and the answer counts
them: `restored` are live again, `purged` had already been deleted for good by
the trash, and `skipped` had changed since the close (pushed again, or removed
again) and were left as they are. Restoring a session twice answers with the
first restore's counts and `"replayed": true`, and queues any restored document
still waiting to be indexed, so repeating a restore that failed to answer is
safe. A session can be restored for 35 days after it closed, and for as long
as the trash still holds any document it removed. A refused restore is answered
with `409 Conflict` and its `data.reason`:

| `reason`                 | What it means                                                                                                                                                                                      |
| ------------------------ | -------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------- |
| `upload_not_closed`      | The session never closed, so it removed nothing                                                                                                                                                    |
| `upload_in_progress`     | A session is open on the datasource. Restore once it has closed or expired                                                                                                                         |
| `restore_purged`         | More than 30 days have passed, and the trash has deleted every one of the documents. Push them again                                                                                               |
| `workspace_not_entitled` | The workspace's plan does not currently allow restoring from the trash                                                                                                                             |
| `corpus_cap_exceeded`    | Bringing the documents back would pass the workspace's document limit, so none came back. `data.wouldRestore` is how many it needs and `data.headroom` how many fit. Free room, then restore again |