# File inputs (https://upapi.io/docs/file-inputs)



Some operations take a file: an image to run OCR on, a PDF to extract text from,
an audio file to transcribe. There are two ways an operation can ask for one, and
which one it uses is declared in its input schema. You can tell them apart at a
glance, and the difference decides how large a file you can send.

## 1. Inline base64 [#1-inline-base64]

The operation declares a string parameter carrying `contentEncoding: "base64"`:

```json
{
  "type": "object",
  "properties": {
    "image": {
      "type": "string",
      "contentEncoding": "base64",
      "contentMediaType": "image/png",
      "description": "The image to read text from"
    }
  },
  "required": ["image"]
}
```

Send the file's bytes base64-encoded, with **no `data:` URL prefix**:

```bash
curl -X POST https://api.upapi.io/image-ocr \
  -H 'x-api-key: upapi_YOUR_KEY' \
  -H 'content-type: application/json' \
  -d "{\"image\": \"$(base64 -w0 receipt.png)\"}"
```

```ts
const bytes = await readFile('receipt.png');
await fetch('https://api.upapi.io/image-ocr', {
  method: 'POST',
  headers: { 'x-api-key': key, 'content-type': 'application/json' },
  body: JSON.stringify({ image: bytes.toString('base64') }),
});
```

`contentMediaType`, when present, tells you which formats the operation accepts.

### Size limit — do the math before you send [#size-limit--do-the-math-before-you-send]

The request body is capped at **5 MB**, and base64 inflates a file by **4 bytes
for every 3** (about 33%). A little headroom is reserved for the rest of the JSON
body — the other parameters, the key names, the quoting — which leaves roughly:

|                       |              |
| --------------------- | ------------ |
| Request body cap      | 5 MB         |
| JSON envelope reserve | 64 KB        |
| **Largest real file** | **≈ 3.7 MB** |

Sending more returns `413 PAYLOAD_TOO_LARGE` at the edge, before the operation
runs. If your file is bigger, use an operation that takes a URL instead — or
host the file yourself and pass its URL.

<Callout type="info" title="The Try it panel does this for you">
  The "Try it" panel on an operation's marketplace page renders a file picker for these parameters,
  checks the size before it encodes anything, and does the base64 encoding for you.
</Callout>

## 2. A URL the operation fetches [#2-a-url-the-operation-fetches]

Larger inputs — audio, video, long PDFs — are passed **by reference**. The
operation declares a `uri` parameter and fetches the file itself, so nothing
large crosses the API gateway and the 5 MB cap does not apply:

```json
{
  "type": "object",
  "properties": {
    "url": {
      "type": "string",
      "format": "uri",
      "x-upapi-upload": true,
      "description": "Publicly reachable URL of the audio to transcribe (e.g. https://example.com/talk.mp3)"
    }
  },
  "required": ["url"]
}
```

```bash
curl -X POST https://api.upapi.io/audio-transcribe.post \
  -H 'x-api-key: upapi_YOUR_KEY' \
  -H 'content-type: application/json' \
  -d '{"url": "https://example.com/talk.mp3"}'
```

The URL must be reachable from the public internet — a `localhost` address, a
private-network address or a link behind a login will fail.

### `x-upapi-upload` [#x-upapi-upload]

`"x-upapi-upload": true` marks a URL parameter as one whose value **may be a file
of yours**, as opposed to a URL that is inherently someone else's (the page a
scraper reads, for example). It changes nothing about how the API validates your
request — `x-` keywords are ignored by JSON Schema validators — and you can
always just pass a URL you already have.

What it does change is the "Try it" panel: for a marked parameter, when hosted
uploads are enabled on the deployment you are using, the panel offers an **Upload
a file** button that stores your file and fills the field with its public URL.
When they are not enabled, the field is an ordinary URL box and nothing else
differs.

## Reading a file back out [#reading-a-file-back-out]

An operation that *returns* a file uses the same base64 convention on its output
schema. The response is bounded the same way, so operations that produce large
artifacts return a URL instead.

## Summary [#summary]

|               | Inline base64               | URL reference                             |
| ------------- | --------------------------- | ----------------------------------------- |
| Schema marker | `contentEncoding: "base64"` | `format: "uri"` (+ `x-upapi-upload`)      |
| Max size      | ≈ 3.7 MB                    | Bounded by the operation, not the gateway |
| You send      | The encoded bytes           | A public URL                              |
| Good for      | Images, small documents     | Audio, video, large documents             |
