---
title: Create document
sidebar_label: Create document
sidebar_position: 1
description: "Create a document and get a signed form to upload its file straight to file storage."
---

# Create document

| Method | Path |
| --- | --- |
| `POST` | `/document` |

[Authentication](/authentication) · [Access policies](/access-policies) · [Files and documents](/files-and-documents)

Creates a document and returns a signed form for uploading its file. Use it for every new file. The file does not pass through Ovok: you send it to file storage with the form this route returns.

**Auth:** Bearer token. Any signed-in user can call it, if the access policy allows `Binary:create` and `DocumentReference:create`.
**Scope:** The document is created in the caller's project (from the token). The caller's profile is recorded as the author.

## Request

### Body

| Name | Type | Required | Description |
| --- | --- | --- | --- |
| `fileName` | `string` | Yes | Name of the file. Stored as the document title and used as the download name. At least 1 character. |
| `contentType` | `string` | Yes | MIME type of the file. One of `image/jpeg`, `image/jpg`, `image/png`, `image/gif`, `image/webp`, `application/pdf`, `application/json`, `application/xml`, `text/plain`, `text/csv`, `video/mp4`, `video/webm`, `audio/mpeg`, `audio/wav`. Matched exactly, so `text/plain; charset=utf-8` is rejected. |
| `isPublic` | `"true"` \| `"false"` | No | A string, not a JSON boolean. Default `"false"`. `"true"` also gives the document a public token. |

## Behaviour

- Creates an empty `Binary` for the file, then a `DocumentReference` with status `current`, `fileName` as the attachment title, `contentType` as the attachment type, and the caller's profile as `author`. The two are created one after the other, not in one transaction. If the second fails, an empty `Binary` can remain.
- With `isPublic` set to `"true"`, a public token is added and returned as `publicToken`. Anyone who has it can download the file with `GET /document/public/:token`.
- The answer holds `uploadOptions`: an upload `url` and the form `fields`. Send a `multipart/form-data` `POST` to `uploadOptions.url`. Add every entry of `uploadOptions.fields` as a form field, exactly as returned, then add the file as the last part, named `file`. The set of fields can change, so do not hard-code it.
- The form is valid for 10 minutes after it is issued and accepts files from 0 bytes up to 5 GB (5,368,709,120 bytes). File storage answers the upload, not Ovok. A `2xx` answer means the file is stored. If the form has expired, call `POST /document/:id` to get a new one for the same document.
- The form limits the size of the file only. It does not check that the file matches `contentType`.
- The document exists as soon as this call returns, even if you never upload the file. Delete it yourself if you abandon the upload.
- The route is rate limited per user, per client IP address, in a one-minute window. Admins can make 20 calls a minute. Practitioners, patients, related persons and client applications can make 10. Super admins are not limited on this route. Every call counts, including calls that fail validation.

## Example

```bash
curl -X POST 'https://api.sandbox.ovok.com/document' \
  -H "Authorization: Bearer ${OVOK_TOKEN}" \
  -H 'Content-Type: application/json' \
  -d '{
    "fileName": "cat.png",
    "contentType": "image/png",
    "isPublic": "false"
  }'
```

Then upload the file with the returned form. Send every field from `uploadOptions.fields`, and `file` last:

```bash
curl -X POST 'https://storage.example.com/ovok-files' \
  -F 'key=binary/5d2a9c64-7e1b-4830-b6f5-9a3c1e8d7b02/8c5e7a21-4b9d-4f36-a1c8-0d2e6b9f3a54' \
  -F '<next field name>=<its value>' \
  -F '<last field name>=<its value>' \
  -F 'file=@cat.png;type=image/png'
```

## Successful response

`201` — The new document with its upload URL and form fields.

```json
{
  "id": "3f1c2b7e-8d4a-4c1e-9b2f-6a7d5e4c3b21",
  "meta": { "lastUpdated": "2026-10-09T09:12:44.318Z" },
  "author": {
    "reference": "Practitioner/8c5e7a21-4b9d-4f36-a1c8-0d2e6b9f3a54",
    "display": "Alex Lee"
  },
  "fileName": "cat.png",
  "contentType": "image/png",
  "uploadOptions": {
    "url": "https://storage.example.com/ovok-files",
    "fields": {
      "key": "binary/5d2a9c64-7e1b-4830-b6f5-9a3c1e8d7b02/8c5e7a21-4b9d-4f36-a1c8-0d2e6b9f3a54",
      "<next field name>": "<its value>",
      "<last field name>": "<its value>"
    }
  }
}
```

| Field | Type | Description |
| --- | --- | --- |
| `id` | `string (uuid)` | Document id. Use it in the other routes. |
| `meta.lastUpdated` | `string` | When the document last changed. ISO 8601. |
| `author` | `object` | The caller's profile. Left out when the caller has none. |
| `author.reference` | `string` | Profile reference, for example `Practitioner/<id>`. |
| `author.display` | `string` | Display name of the caller. |
| `fileName` | `string` | The file name you sent. |
| `contentType` | `string` | The content type you sent. |
| `publicToken` | `string` | Public token. Present only when `isPublic` was `"true"`. |
| `uploadOptions.url` | `string` | URL to send the upload form to. |
| `uploadOptions.fields` | `object` | Form fields to send with the file. Each value is a string. Send every name and value unchanged. The names and the number of fields are not fixed, so the example shows placeholders for all but `key`. |

## Errors

| Status | Meaning |
| --- | --- |
| `400` | The body fails validation: `fileName` is missing or empty, `contentType` is missing or not an allowed type, or `isPublic` is not the string `"true"` or `"false"`. `message` lists each problem as `path: message`. |
| `401` | Bearer token is missing, invalid or revoked. |
| `403` | Your access policy does not allow creating the `Binary` or the `DocumentReference`. |
| `404` | The new file has no storage location. |
| `429` | You made more calls in the last minute than your user type allows. |
