> For the complete documentation index, see [llms.txt](https://docs.moderncollections.io/llms.txt). Markdown versions of documentation pages are available by appending `.md` to page URLs; this page is available as [Markdown](https://docs.moderncollections.io/rest-api/documents.md).

# Upload files

Upload contracts, invoices and supporting evidence, extract a new placement from an invoice file, or download the documents we issued.

Choose the route for what you want to do:

| Goal                                                           | Endpoint                               | Result                                                                                                                        |
| -------------------------------------------------------------- | -------------------------------------- | ----------------------------------------------------------------------------------------------------------------------------- |
| Attach a contract, invoice, delivery receipt or other evidence | `POST /v1/documents`                   | A stored document, optionally attached to an existing placement.                                                              |
| Create a placement from an invoice PDF or image                | `POST /v1/intake/file`                 | An extracted draft to review, then confirm. Upload alone creates no placement.                                                |
| Import a CSV of invoices                                       | `POST /v1/intake/csv/preview`          | A preview to map and commit. See [CSV intake](/guides/ingest-invoices.md#csv-bulk).                                           |
| Let a person upload from their browser                         | `POST /v1/placements/{id}/upload-link` | A short-lived link that files uploads onto the placement. See [Browser upload links](#browser-upload-links).                  |
| Download a notice, statement or receipt we issued              | `GET /v1/documents/generated`          | The documents we issued about a placement or a debtor, each with a download. See [Documents we issued](#documents-we-issued). |

The first three take the file bytes as the request body: set `Content-Type` and use `--data-binary`, no JSON or multipart wrapper. Terminal and agent examples: [CLI](/clients/cli.md#upload-files), [MCP](/clients/mcp.md#upload-an-invoice).

## Attach a contract or invoice

Use your API key and an existing placement ID.

```bash
export MC_API_BASE="https://api.moderncollections.io/v1"
export MC_API_KEY="ca_REPLACE_WITH_YOUR_KEY"
export PLACEMENT_ID="REPLACE_WITH_PLACEMENT_UUID"

curl --fail-with-body -X POST \
  "$MC_API_BASE/documents?placement_id=$PLACEMENT_ID&document_type=contract&source_channel=api_upload" \
  -H "Authorization: Bearer $MC_API_KEY" \
  -H "Content-Type: application/pdf" \
  -H "X-Filename: signed-contract.pdf" \
  --data-binary @signed-contract.pdf
```

One request per file. A partner key also sends `X-MC-Creditor`. The `201` response has `id`, `placement_id`, `document_type`, `status` (`queued` until processed; `uploaded` for a file we store but do not read) and `metadata`.

| Field            | Where  | Purpose                                                                                                                                |
| ---------------- | ------ | -------------------------------------------------------------------------------------------------------------------------------------- |
| `placement_id`   | query  | Attach to this placement. Omit to upload to the creditor's document library for matching later.                                        |
| `document_type`  | query  | Your document type, such as `contract` or `invoice`. Omit for `other` until classification. Processing records its own classification. |
| `source_channel` | query  | Set `api_upload` to identify your integration's uploads.                                                                               |
| `source_ref`     | query  | Optional reference from your system.                                                                                                   |
| `X-Filename`     | header | Original filename, including extension.                                                                                                |
| `Content-Type`   | header | The actual file MIME type from the table below.                                                                                        |

### Process and read back

```bash
export DOCUMENT_ID="REPLACE_WITH_DOCUMENT_UUID"

curl --fail-with-body -X POST "$MC_API_BASE/documents/$DOCUMENT_ID/process" \
  -H "Authorization: Bearer $MC_API_KEY"

curl --fail-with-body "$MC_API_BASE/documents/$DOCUMENT_ID" \
  -H "Authorization: Bearer $MC_API_KEY"

curl --fail-with-body "$MC_API_BASE/placements/$PLACEMENT_ID/documents" \
  -H "Authorization: Bearer $MC_API_KEY"
```

* `POST /v1/documents/{document_id}/process` runs classification, extraction and matching now. An already processed document answers `already_processed: true`, `facts_created: 0` and the earlier `match_id` and `match_status`; `?force=true` runs it again.
* The detail response has `document`, `facts` and `matches`. Both document lists return `{"items", "limit", "offset", "total"}` and page with `limit` (1–100, default 25) and `offset`.
* For an unbound upload, processing may propose a match. Confirm or reject it with `POST /v1/documents/{document_id}/matches/{match_id}/confirm` and `{"action": "confirm"}` or `{"action": "reject"}`.
* Identical bytes are deduplicated per creditor: still `201`, with `metadata.duplicate: true` and the existing document unchanged. Uploading the same file with another placement id does not reattach it.

## Browser upload links

When the person with the file is not running your integration (for example, someone working through a chat assistant), mint a link they open in a browser. It needs no login: they choose a file and it is filed on the placement through the same path as `POST /v1/documents`.

```bash
curl --fail-with-body -X POST "$MC_API_BASE/placements/$PLACEMENT_ID/upload-link" \
  -H "Authorization: Bearer $MC_API_KEY" \
  -H "Content-Type: application/json" \
  -d '{"document_type": "invoice", "expires_in_minutes": 30}'
```

| Field                | Default | Purpose                                                                     |
| -------------------- | ------- | --------------------------------------------------------------------------- |
| `document_type`      | none    | Type hint recorded on every file sent through the link (max 64 characters). |
| `expires_in_minutes` | `30`    | Lifetime, 1–60.                                                             |

The body is optional. The `201` response has `url`, `placement_id`, `document_type` and `expires_at`. The link takes several files until it expires or the placement can no longer take uploads (`410` after). Anyone holding it can upload, so give it only to the person uploading. Minting is audited as `document.upload_link_created`. Same formats and limits as `/documents`; `403` unless the creditor is `verified`, `404` for a placement that is not yours.

## Extract a new placement from an invoice

Upload a PDF or image, then review the returned `draft`, `confidence` and `needs_human_review` before submitting it.

```bash
curl --fail-with-body -X POST "$MC_API_BASE/intake/file" \
  -H "Authorization: Bearer $MC_API_KEY" \
  -H "Content-Type: application/pdf" \
  -H "X-Filename: invoice.pdf" \
  -H "X-File-Type: invoice" \
  --data-binary @invoice.pdf
```

Save the returned `draft_id`. After reviewing the extracted fields, confirm with an empty object to keep them, or send `{"edits": {...}}` with corrections:

```bash
export DRAFT_ID="REPLACE_WITH_DRAFT_UUID"
curl --fail-with-body -X POST "$MC_API_BASE/intake/file/$DRAFT_ID/confirm" \
  -H "Authorization: Bearer $MC_API_KEY" \
  -H "Content-Type: application/json" \
  -d '{}'
```

Read the result's `status`, `placement_id`, `missing_fields` and `reason`: confirmation can return a review or rejection instead of a placement. An accepted placement keeps the source document. Outreach still needs creditor verification and a fresh compliance decision.

## Formats and limits

| File format                                               | `Content-Type`                                                  | `/documents`                                                                  | `/intake/file`                |
| --------------------------------------------------------- | --------------------------------------------------------------- | ----------------------------------------------------------------------------- | ----------------------------- |
| PDF                                                       | `application/pdf`                                               | 10 MiB                                                                        | 10 MiB                        |
| PNG or JPEG                                               | `image/png`, `image/jpeg`, `image/jpg`                          | 5 MiB                                                                         | 5 MiB                         |
| Text, HTML, email or JSON                                 | `text/plain`, `text/html`, `message/rfc822`, `application/json` | 2 MiB                                                                         | Unsupported                   |
| Word, Excel, CSV, Outlook `.msg`, RTF, WebP, TIFF or HEIC | The file's own MIME type                                        | 10 MiB, read after conversion                                                 | 10 MiB, read after conversion |
| Any other type                                            | As declared                                                     | 10 MiB, stored as `application/octet-stream` with status `uploaded`, not read | Unsupported                   |

CSV preview accepts `text/csv`, `text/plain` or `application/octet-stream`, up to 20 MiB.

`document_type` on `/documents`: `bol`, `broker_carrier_agreement`, `change_order`, `contract`, `credit_application`, `credit_memo`, `email_thread`, `invoice`, `msa`, `other`, `payment_history`, `personal_guaranty`, `po`, `preliminary_notice`, `proof_of_service`, `purchase_order_ack`, `rate_confirmation`, `signed_delivery_receipt`, `sow`, `statement_of_account`.

`X-File-Type` on `/intake/file`: `invoice` for an invoice; also `contract`, `proof_of_service`, `msa`, `w9`, `audit_cert`; default `other`.

## Common errors

| Status | What to check                                                                                                                                            |
| ------ | -------------------------------------------------------------------------------------------------------------------------------------------------------- |
| `400`  | Empty or invalid file, invalid intake tag, missing partner creditor header, or a `/documents` file over its per-format cap.                              |
| `403`  | The creditor is not `verified`. Uploads open once business verification, the payout account and the agreement are all done.                              |
| `413`  | Request over the route cap, or an intake file over its per-format cap. Reduce the file size.                                                             |
| `415`  | On `/documents`: a declared MIME type that does not match the bytes, or an unknown `document_type`. On `/intake/file`: an unsupported MIME type as well. |

`POST /v1/documents/batch` registers a manifest of files already in storage; it does not upload bytes. The [OpenAPI spec](https://github.com/knollmeyersam-dot/MC-Docs/tree/main/api-reference/rest-api/openapi.yaml) has the full upload, list, process, match and debtor-sharing schemas; other responses are in [Errors and limits](/getting-started/errors.md).

## Documents we issued

Every PDF we issue gets a document number of the form `MC-{PREFIX}-{YEAR}-{NNNNNN}`, for example `MC-STM-2026-000123`. The prefix names the kind:

| Prefix | `kind`                     | `audience` |
| ------ | -------------------------- | ---------- |
| `LOR`  | `letter_of_representation` | `debtor`   |
| `NTC`  | `collection_notice`        | `debtor`   |
| `DSP`  | `dispute_acknowledgment`   | `debtor`   |
| `RCT`  | `payment_receipt`          | `debtor`   |
| `NOB`  | `outstanding_balance`      | `debtor`   |
| `STM`  | `monthly_statement`        | `creditor` |
| `ACC`  | `acceptance_certificate`   | `creditor` |
| `PAY`  | `payout_notice`            | `creditor` |

List the documents about one placement or one debtor:

```bash
curl --fail-with-body "$MC_API_BASE/documents/generated?placement_id=$PLACEMENT_ID" \
  -H "Authorization: Bearer $MC_API_KEY"
```

Pass exactly one of `placement_id` or `debtor_id` (`422` otherwise). A placement's list includes the documents about its debtor, and a debtor's list the documents about each of its placements. Newest first, `{"items", "limit", "offset", "total"}`, `limit` 1–100, default 25. Outside your account is `404`.

Each item:

```json
{
  "document_id": "6c1f4a0e-2b7d-4f3e-9a51-0d8c2e7b4f19",
  "document_number": "MC-NTC-2026-000042",
  "kind": "collection_notice",
  "title": "Collection notice",
  "audience": "debtor",
  "status": "issued",
  "placement_id": "3f9a2c1e-8d4b-4e7a-b6f0-1c2d3e4f5a6b",
  "debtor_id": "9b2e7c41-5f0a-4d3b-8e16-2a7c9d0f3b58",
  "issued_at": "2026-10-13T14:02:11.482913+00:00",
  "voided_at": null,
  "downloadable": true
}
```

`status` is `issued`, `superseded` (replaced by a newer document, still downloadable as history) or `voided` (withdrawn, `downloadable: false`).

Download one:

```bash
curl --fail-with-body "$MC_API_BASE/documents/generated/$DOCUMENT_ID/download" \
  -H "Authorization: Bearer $MC_API_KEY"
```

The response has `document_id`, `document_number`, `filename`, `download_url` and `download_url_ttl_seconds` (`300`). The URL serves the PDF for five minutes; request a new one each time. A voided document, or one outside your account, is `404`. Each download is recorded.

A monthly statement issued as a document lists on `GET /v1/statements` with `download_url` `null`; `GET /v1/statements/{id}/download` mints its five-minute URL. A statement from before documents keeps its one-hour URL in the list.

### Verify a document

Every document prints a verification address in its footer after `Verify:`. Anyone holding the paper can check it, with no key, at `GET /v1/public/documents/{document_number}/verify?t={code}`. The code is the ten characters after the document number in the printed address: `…/verify/MC-STM-2026-000123-7KQ2M9XDPA` is number `MC-STM-2026-000123`, code `7KQ2M9XDPA`. The response has `status` (`valid`, `superseded`, `voided`), `title`, `issued_on`, `issuer`, and for statements `period` and `ledger_range`; no amounts, no names. A wrong number or code is always `404 {"detail": "No document matches this number and code."}`. Rate-limited per IP.


---

# Agent Instructions
This documentation is published with GitBook. GitBook is the documentation platform designed so that both humans and AI agents can read, navigate, and reason over technical content effectively. Learn more at gitbook.com.

## Querying This Documentation
If you need additional information that is not directly available in this page, you can query the documentation by asking a question.

Perform an HTTP GET request on the following URL with the `ask` and `goal` query parameters:

```
GET https://docs.moderncollections.io/rest-api/documents.md?ask=<question>&goal=<user_goal>
```

`ask` is the immediate question: it should be specific, self-contained, and written in natural language.
`goal` is what the user is ultimately trying to achieve, the reason they need the answer. Sharing it helps GitBook give you a better, more relevant answer. A goal is most helpful when it describes the outcome the user wants rather than restating the question. For example, with `ask=how do I create an API token`, a goal like `automate deployments from our CI pipeline` lets GitBook tailor the answer to that use case.

The response will contain a direct answer to the question and relevant excerpts and sources from the documentation.

Use this mechanism when the answer is not explicitly present in the current page, you need clarification or additional context, or you want to retrieve related documentation sections.
