# Read images and PDFs in your app

> Let your Cloud app read photos, scans and PDFs - receipts, invoices, forms - through Playcode AI Gateway, with the cost per document and where data goes.
> Source: https://playcode.io/docs/add-to-your-app/read-images-and-pdfs - last reviewed 2026-10-16.
> Ask Playcode's agent: "Let my app read the date, total, VAT and supplier from an invoice photo or PDF." It tells you what one document costs, the limits and where the data goes, builds the upload and a signed-in endpoint, and tries it once on a sample you give it, which uses your credits.

Your Cloud app can read photos and PDFs, such as receipts, invoices and scanned forms, and turn them into data: a date, a total, a supplier. The app sends each file to an AI model through Playcode AI Gateway, and each read uses your workspace's credits: in Playcode's tests, a one-page invoice or receipt cost from about 0.005 to about 0.2 credits, by model.

## Do it yourself

1. Check that the app has AI. In the project, open **Settings**, choose **Secrets** and look for `PLAYCODE_AI_URL`, marked **Managed**. If it is missing, see [Add AI to your app](/docs/add-to-your-app/ai-in-your-app).
2. Choose the model. Ask the agent which models read your file type: it reads each model's card from Playcode, and it names the models that Playcode's own tests found best at reading documents.
3. In the page, add a file picker for photos and PDFs. Before the upload, shrink each photo in the browser to about 2,000 px on its long edge and save it as JPEG. Then upload the file to your app's own endpoint with `apiUpload`:

   ```ts
   import { apiUpload } from '../apps/api'

   const form = new FormData()
   form.append('file', file) // the PDF, or the shrunk photo
   const { answer } = await apiUpload<{ answer: string }>('/invoices/read', form)
   ```

4. In the backend, put a sign-in check on that endpoint, take the file with `FileInterceptor`, and pass it to the AI service with `files` and a `schema` for the fields. The answer comes back as an object checked against the schema. Tell the model what it cannot know, like the date format:

   ```ts
   const { object } = await this.ai.generate({
     model: READER_MODEL, // the id of the model from step 2
     system: 'You read invoices. Dates on these invoices are day-month-year.',
     prompt: 'Read this invoice.',
     files: [{ data: file.buffer, mediaType: file.mimetype }],
     schema: z.object({ date: z.string(), total: z.number(), vatRate: z.number(), supplier: z.string() }),
   })
   ```

   Your project's `src/ai/examples/read-document/` has the whole endpoint, with its size limit and the file types it takes.

5. Check the answer in code: every field is there, the date is a real date, and the amounts add up. Show the fields to the user, and save them only after the user confirms each one.
6. Try it once with a sample document, never a customer's real one. The read uses credits. In **Settings**, **Usage**, the project's **AI Gateway** row shows the call.

The AI service in `backend-nest/src/ai/` is `playcode.ai` from the Playcode SDK, so the same call works with every model that reads your file type: change only `model`. An app made before it gets it when the agent updates the app's SDK.

> [!WARNING]
> Put a sign-in check on the endpoint. An endpoint that anyone can call spends your credits on their files.

### What one document costs

A read costs the tokens the model reads, at the model's input price, plus the tokens it writes, at its output price. Prices differ by model: the agent quotes the price of the model it picks, from Playcode's current list.

- A photo counts as tokens, not as bytes. The model scales a large photo down, so one image never counts more than the cap on the model's card.
- A PDF counts per page, up to the card's cap for one page.
- The answer counts too, so ask for short JSON, not prose.

An example, measured by Playcode on 2026-10-15 with its own sample invoices: Claude Sonnet 5.5 read a phone photo of a one-page invoice, shrunk to 2,000 px, as 4,098 input tokens, the photo and the prompt together. It answered in 125 output tokens, and the read cost 0.11 credits. The same invoice as a PDF was 2,166 input tokens and cost 0.06 credits.

While a call runs, Playcode sets aside credits for its longest possible answer, and at the end it charges what the model reports. So give each call an answer limit near what it needs (`max_tokens`; the AI service sets one). Without a limit, the call sets aside credits for the model's longest answer, and a workspace with few credits left can refuse a second call until the first one ends.

### Where your documents are processed

A file your app reads leaves the app for two places:

- **Playcode AI Gateway**, on Playcode's servers in Germany. It passes the request to the model's maker and records each call's model, tokens and credits. **Settings**, **Usage** adds them up for the project.
- **The model's maker**, which reads the file and answers: Anthropic for Claude models, OpenAI for GPT models, Google for Gemini models, and so on.

Each model's card says where its maker processes the call. `EU` means in the European Union: the Gemini models your app can call run on Google Cloud's EU multi-region, in EU member states only. `global` means the maker may use any country where it runs, with no promise of one country or region: Claude and GPT models say `global`. `unknown` means the maker publishes no location. Ask the agent for the card of the model you pick.

To keep documents in the EU, use a model whose card says `EU`: ask the agent which ones read your files and what one document costs.

Documents often hold personal data, such as names, addresses and bank details. Tell your app's users which company reads their files.

### Get reliable reads

- **Shrink photos before the upload.** At about 2,000 px on the long edge, as JPEG, the upload is a fraction of the phone's original and far under every model's image limits. The browser's canvas export also turns an iPhone's HEIC photo into JPEG, where the browser can open HEIC.
- **Pick the model by measurement.** In Playcode's test of ten sample documents, Claude Sonnet 5.5 read all ten right, GPT-6 Luna nine, and Claude Haiku 4.5 five: it read day-month dates on photos month-first.
- **Keep the default thinking level.** In the same test, the highest level fixed no misread, cost up to about four times as much, and once used the whole answer limit without an answer.
- **Let the user confirm every field.** Checks in code catch an answer that does not add up, but not a misread: in the test, a model misread a receipt and still returned amounts that added up.
- **Add a second reader for more safety.** A second model, from another maker, reads the same document, and the app flags the document when the two disagree on a key field. In the test, whenever two models agreed, they were right. It was a small test on sample documents, so confirm it on your own.
- **Test with sample documents**, never with a customer's real ones.

## If it doesn't work

### The upload fails with 413 and `PAYLOAD_TOO_LARGE`

The file is over your endpoint's own size limit. Shrink the photo in the browser, or raise the endpoint's limit.

### The call fails with 413 and `request_too_large`

The request is over Playcode AI Gateway's limit of 32 MiB, and the error names the limit in bytes. Shrink the photos or split the PDF. Sending the same request again does not help.

### The upload fails with 415

The endpoint does not take that file type. The AI service sends JPEG, PNG, GIF, WebP and PDF. Convert other files first, for example a HEIC photo to JPEG.

### The call fails with 400 and names the file

The header `x-playcode-error-source` says who refused. With `upstream`, the model's maker refused the file for its type, its size or its pages: check the model's limits, then convert or shrink the file. With `gateway` and "does not read PDF", the model reads images but not PDFs. Playcode refused the call before it reached the model, and nothing was charged. Send the pages as images, or pick a model whose card lists PDF.

### The fields come back wrong

Use a model that Playcode's tests found good at documents, say the date format in the prompt, and check that the photo is sharp. No model reads what the photo does not show: in Playcode's test, the misses that remained were small print on a dark, blurred photo.

### The call fails with 402

`insufficient_balance` means the workspace is out of credits. `budget_exceeded` means this app's monthly AI budget is spent. [Add AI to your app](/docs/add-to-your-app/ai-in-your-app) explains both.

### The call fails with 429 and `x-playcode-error-source: upstream`

The model's maker is busy. Wait a moment and try once more, then use another model.

## Limits

- One request to Playcode AI Gateway carries at most 32 MiB. Files travel as base64 text, a third larger than the file, so the files in one request can add up to about 24 MiB.
- Each model also has its own file limits, on its card. Claude models take images up to 7.5 MB and 8,000 px on the long edge, and PDFs up to 24 MB with 100 or 600 pages, by model. They do not read a PDF with a password.
- Some models read images but not PDFs.
- Only a model whose card says `EU` is processed in the EU; the others may run in any country where their maker runs.
- AI needs a Cloud app.
