# Upload a filing document

> The filing_periods.documents.upload operation: POST /api/v1/filing_periods/{id}/documents in the REST API and the filing_periods_documents_upload tool in the MCP server.

Uploads a document to a filing period, typically the tax portal's confirmation of the filed return. Send the
file base64-encoded in `content`: a PDF, PNG, JPEG or WebP file of at most 3 MB, with its `filename`. The
type is read from the file itself. The document is stored privately (spec §7.10) and returned with its
`file_…` ID: pass it as a receipt to `filing_periods.submit`. Uploading the same file again returns the same
document.

Write tier: tax managers, finance, owners and admins. Dry-run it to check a file without storing it.

## At a glance

- **REST:** `POST /api/v1/filing_periods/{id}/documents`
- **MCP tool:** `filing_periods_documents_upload`
- **Operation ID:** `filing_periods.documents.upload`
- **Scope:** Legal entity
- **Permission:** `filings:write`
- **Risk tier:** `write`
- **Dry run:** Supported (send `Ownrecord-Dry-Run: true`)
- **Idempotency:** `Idempotency-Key` required
- **Modes:** Test and live
- **Authentication:** API key with the `filings:write` scope
- **Since:** M8

## Request

### Path parameters

| Field | Type      | Description                 |
| ----- | --------- | --------------------------- |
| `id`  | `fp_…` ID | The filing period (`fp_…`). |

### Body

A JSON object (`Content-Type: application/json`):

| Field         | Type                                                              | Required | Description                                                                      |
| ------------- | ----------------------------------------------------------------- | -------- | -------------------------------------------------------------------------------- |
| `content`     | string                                                            | Yes      | The file's bytes, base64-encoded: a PDF, PNG, JPEG or WebP file of at most 3 MB. |
| `filename`    | string                                                            | No       | The file's name.                                                                 |
| `contentType` | one of `application/pdf`, `image/png`, `image/jpeg`, `image/webp` | No       | The file's type. Optional: it is read from the file, and must agree when given.  |

## Response

`201 Created` with a JSON object:

| Field            | Type                | Description |
| ---------------- | ------------------- | ----------- |
| `id`             | `file_…` ID         |             |
| `object`         | `"filing_document"` |             |
| `filingPeriodId` | `fp_…` ID           |             |
| `filename`       | string or null      |             |
| `contentType`    | string              |             |
| `size`           | integer             |             |
| `sha256`         | string              |             |
| `createdAt`      | date-time           |             |

## Examples

### Upload the OSS portal's confirmation

```http
POST /api/v1/filing_periods/fp_01m4tx5nj8emwbyf7y9vcf47y0/documents
Authorization: Bearer or_test_…
Idempotency-Key: 3f1c9a52-7d0e-4b8f-9a61-0c2d5e8b7f14
Content-Type: application/json

{
  "content": "JVBERi0xLjQKJcOkw7zDtsOfCjEgMCBvYmoKPDwvVHlwZS9DYXRhbG9nPj4KZW5kb2JqCnRyYWlsZXIKPDwvUm9vdCAxIDAgUj4+CiUlRU9GCg==",
  "filename": "oss-q3-confirmation.pdf"
}
```

Response (`201`):

```json
{
  "id": "file_01m4tx5nj8emwbyf7y9vcf47y4",
  "object": "filing_document",
  "filingPeriodId": "fp_01m4tx5nj8emwbyf7y9vcf47y0",
  "filename": "oss-q3-confirmation.pdf",
  "contentType": "application/pdf",
  "size": 48213,
  "sha256": "3e7a1c9f5b2d8e4a6c0f3b7d1e9a5c2f8b4d6e0a3c7f1b9d5e2a8c4f6b0d3e7a",
  "createdAt": "2026-10-15T09:00:00.000Z"
}
```

## Errors

| Code                                                                                     | HTTP status | When                                                                                    | What to do                                                                                                                                         |
| ---------------------------------------------------------------------------------------- | ----------- | --------------------------------------------------------------------------------------- | -------------------------------------------------------------------------------------------------------------------------------------------------- |
| [`validation_failed`](https://ownrecord.io/docs/reference/errors.md#validation_failed)   | 400         | A field is malformed (`param` names it).                                                | Fix the fields listed in `details.issues` and send the request again.                                                                              |
| [`resource_not_found`](https://ownrecord.io/docs/reference/errors.md#resource_not_found) | 404         | No filing period with this ID exists for this legal entity and mode.                    | Check the ID and that your credentials are scoped to the right organization, entity, brand and mode.                                               |
| [`scope_required`](https://ownrecord.io/docs/reference/errors.md#scope_required)         | 400         | The request names no legal entity (an org key without `Ownrecord-Entity`).              | Call the operation with credentials or a target at its level: an organization, a legal entity, a brand or the platform (`details.required_scope`). |
| [`upstream_timeout`](https://ownrecord.io/docs/reference/errors.md#upstream_timeout)     | 502         | The file store didn't answer in time; nothing was recorded. Retry.                      | Retry the request with the same Idempotency-Key after a short wait.                                                                                |
| [`version_conflict`](https://ownrecord.io/docs/reference/errors.md#version_conflict)     | 409         | The period changed at the same moment. Reload it and try again.                         | Fetch the object again and retry the change.                                                                                                       |
| [`permission_denied`](https://ownrecord.io/docs/reference/errors.md#permission_denied)   | 403         | You can't work on this entity's filings (tax managers, finance, owners and admins can). | Ask an organization owner or admin for a role or scope that allows this action.                                                                    |

It can also return these [common errors](https://ownrecord.io/docs/reference.md#common-errors): [`api_version_unsupported`](https://ownrecord.io/docs/reference/errors.md#api_version_unsupported), [`invalid_json`](https://ownrecord.io/docs/reference/errors.md#invalid_json), [`unsupported_media_type`](https://ownrecord.io/docs/reference/errors.md#unsupported_media_type), [`authentication_required`](https://ownrecord.io/docs/reference/errors.md#authentication_required), [`api_key_invalid`](https://ownrecord.io/docs/reference/errors.md#api_key_invalid), [`idempotency_key_required`](https://ownrecord.io/docs/reference/errors.md#idempotency_key_required), [`idempotency_key_reused`](https://ownrecord.io/docs/reference/errors.md#idempotency_key_reused), [`idempotency_in_progress`](https://ownrecord.io/docs/reference/errors.md#idempotency_in_progress), [`approval_required`](https://ownrecord.io/docs/reference/errors.md#approval_required), [`approval_rejected`](https://ownrecord.io/docs/reference/errors.md#approval_rejected), [`approval_expired`](https://ownrecord.io/docs/reference/errors.md#approval_expired), [`stale_approval`](https://ownrecord.io/docs/reference/errors.md#stale_approval), [`approval_policy_denied`](https://ownrecord.io/docs/reference/errors.md#approval_policy_denied), [`rate_limited`](https://ownrecord.io/docs/reference/errors.md#rate_limited), [`internal_error`](https://ownrecord.io/docs/reference/errors.md#internal_error).
