---
title: "Create a file"
description: "Hand the API a file for one purpose, then pass its id as file_id (or the field an operation names) to what uses it: a brand document, a toll-free number's opt-in evidence, a 10DLC campaign's screenshot, a library image, the logo. `POST /api/v1/files`"
url: https://www.unitpost.com/docs/platform/post-files
section: "Platform API reference"
updated: 2026-11-11
---
# Create a file

`POST /api/v1/files`

Hand the API a file for one `purpose`, then pass its id as `file_id` (or the field an operation names) to what uses it: a brand document, a toll-free number's opt-in evidence, a 10DLC campaign's screenshot, a library image, the logo. Three ways in, one per call: a public https `url` (fetched once, no redirects, no private addresses); base64 `data` with `content_type` (small files: a request over ~6 MB is refused); or an upload session, for a local file: send `content_type` and `size_bytes` without `url` or `data`, then PUT the bytes to `upload.url` with `upload.headers` within 15 minutes (`curl -T file -H "Content-Type: image/png" "<upload.url>"`). The bytes are checked by their contents against the purpose (`sms_opt_in_evidence`: PNG up to 400 KB; `sms_document`: PDF, PNG or JPEG up to 3 MB; `image`: PNG, JPEG, GIF or WebP up to 5 MB), never by the label. Files are private, never served back, and expire 24 hours after they're made: what uses one keeps its own copy. Send an `Idempotency-Key` so a retry makes one file. Requires the `sms:manage` or `templates:write` or `branding:write` capability.

## Parameters

| Name | In | Type | Required | Description |
| --- | --- | --- | --- | --- |
| `Idempotency-Key` | header | string | no | Any unique string (1–256 characters, e.g. a UUID). Retrying with the same key and the same body within 24 hours returns the first result instead of sending again; the same key with a different body is `409 invalid_idempotent_request`, and one still in flight is `409 concurrent_idempotent_requests`. |

## Request body

| Field | Type | Required | Description |
| --- | --- | --- | --- |
| `purpose` | enum | yes | What the file is for, which sets what it may be: `sms_opt_in_evidence` (PNG, up to 400 KB), `sms_document` (PDF, PNG or JPEG, up to 3 MB), `image` (PNG, JPEG, GIF or WebP, up to 5 MB). The bytes decide the type, never the name or the label. One of: `sms_opt_in_evidence`, `sms_document`, `image`. |
| `url` | string | no | A public https address to fetch the file from (no redirects, no private addresses, 12 seconds). One of `url`, `data`, or neither (an upload session). |
| `data` | string | no | The bytes, base64 (no `data:` prefix), with `content_type`. A request over ~6 MB is refused before it arrives, so this fits about 4 MB; agents should prefer `url` or an upload session for anything but small files. |
| `content_type` | enum | no | Required with `data` and for an upload session (it is signed into the PUT); refused with `url`. One of: `image/png`, `application/pdf`, `image/jpeg`, `image/gif`, `image/webp`. |
| `size_bytes` | integer | no | An upload session only: the exact size of the file you'll PUT, in bytes (signed into the PUT). Within the purpose's maximum. |
| `filename` | string | no | A name for the file (shown on what uses it). Defaults to the URL's last path part, or `file.<ext>`. |

## Response fields (201)

| Field | Type | Required | Description |
| --- | --- | --- | --- |
| `object` | string | yes | — |
| `id` | string | yes | — |
| `purpose` | enum | yes | What the file is for: `sms_opt_in_evidence` (PNG, up to 400 KB), `sms_document` (PDF, PNG or JPEG, up to 3 MB), `image` (PNG, JPEG, GIF or WebP, up to 5 MB). One of: `sms_opt_in_evidence`, `sms_document`, `image`. |
| `status` | enum | yes | `ready`: checked and usable by `file_id`. `awaiting_upload`: an upload session whose bytes haven't been PUT yet (reading the file checks again). `failed`: the uploaded bytes were refused (`failure_reason` says why). One of: `awaiting_upload`, `ready`, `failed`. |
| `filename` | string | yes | — |
| `content_type` | string | yes | The type the file's bytes carry (checked by their contents), or, before an upload session's bytes arrive, the type declared. |
| `size_bytes` | integer | yes | — |
| `sha256` | string \| null | yes | The hex SHA-256 of the bytes once `ready`. |
| `failure_reason` | string \| null | yes | — |
| `upload` | object \| null | yes | An upload session's PUT, in the create response only (null otherwise). |
| `created_at` | string | yes | — |
| `expires_at` | string | yes | The file is deleted at this time, 24 hours after it was created, whether or not it was used. An operation that used it keeps its own copy. |

## Responses

| Status | Description |
| --- | --- |
| 201 | The file: `ready`, or `awaiting_upload` with its `upload` session. |
| 400 | The body isn't JSON, or the `Idempotency-Key` is malformed. |
| 401 | Missing or invalid API key. |
| 403 | Insufficient scope for the purpose, or 500 unexpired files already (`limit_reached`). |
| 404 | An SMS purpose while SMS is not enabled. |
| 409 | The `Idempotency-Key` is in use by a request still running, or with another body. |
| 413 | The file is larger than the purpose takes. |
| 415 | The file's contents aren't a type the purpose takes. |
| 422 | A bad source (both or neither of `url`/`data` when they're needed, a URL that can't be fetched), a missing `content_type` or `size_bytes`, or an unknown key. |
| 429 | Too many files this hour; retry after the wait. |
| 503 | `engine_unavailable`: file storage isn't configured on this deployment. |

## Example request

```bash
curl -X POST 'https://www.unitpost.com/api/v1/files' \
  -H 'Authorization: Bearer YOUR_API_KEY' \
  -H 'User-Agent: my-app/1.0' \
  -H 'Content-Type: application/json' \
  -d '{ }'
```

## Example response (201)

```json
{
  "object": "file",
  "id": "id_123",
  "purpose": "sms_opt_in_evidence",
  "status": "awaiting_upload",
  "filename": "string",
  "content_type": "string",
  "size_bytes": 1,
  "sha256": "string",
  "failure_reason": "string",
  "upload": {
    "url": "https://example.com",
    "method": "PUT",
    "headers": {},
    "expires_at": "2026-01-01T00:00:00.000Z"
  },
  "created_at": "2026-01-01T00:00:00.000Z",
  "expires_at": "2026-01-01T00:00:00.000Z"
}
```

## Links

- HTML reference (with Node.js, Python, Ruby, PHP, Laravel, Go, Java, Rust, .NET and CLI examples): https://www.unitpost.com/docs/platform/post-files
- OpenAPI 3.1 (full schemas): https://www.unitpost.com/api/v1/openapi.json
