---
title: "Confirm every upload on the server · Agent Wranglers"
url: https://agentwranglers.se/playbook/api-design/files-and-uploads/confirm-uploads
updated: 2026-10-20
licence: "All rights reserved"
---

# Confirm every upload on the server

By Erik Bergstedt, Agent Wranglers. Updated 2026-10-20.

- Level: Required
- Chapter: [Files and uploads](https://agentwranglers.se/playbook/api-design/files-and-uploads)
- Rule: Keep a new file `pending` until the server has checked its size, type and checksum from the stored bytes. Never trust what the client reports.
- How: When the client says the upload is done, or storage sends its event, read the stored object. Compare its size with your limit. Find its type from the first bytes, and checksum it with SHA-256. Store all three in your files table. Only then mark it ready for the next step. The attacks this blocks, and how to test for them, are in [check every uploaded file](https://agentwranglers.se/playbook/adversarial-testing/requests-and-files/safe-file-uploads). Delete pending files that were never confirmed within 24 hours.
- Check: Upload a text file named `photo.jpg` with a content type of `image/jpeg`. The file never leaves `pending`, and the API answers with a problem response naming the type.

Example:

```js
import { fileTypeFromBuffer } from 'file-type';

const ALLOWED_TYPES = new Set(['image/jpeg', 'image/png', 'application/pdf']);

export async function confirmUpload(firstBytes, storedSize, maxBytes) {
  const detected = await fileTypeFromBuffer(firstBytes);
  if (storedSize > maxBytes) return { status: 'rejected', reason: 'too-large' };
  if (!detected || !ALLOWED_TYPES.has(detected.mime)) return { status: 'rejected', reason: 'type-not-allowed' };
  return { status: 'awaiting-scan', mime: detected.mime };
}
```

```python
import magic

ALLOWED_TYPES = {"image/jpeg", "image/png", "application/pdf"}

def confirm_upload(first_bytes: bytes, stored_size: int, max_bytes: int) -> dict:
    detected = magic.from_buffer(first_bytes, mime=True)
    if stored_size > max_bytes:
        return {"status": "rejected", "reason": "too-large"}
    if detected not in ALLOWED_TYPES:
        return {"status": "rejected", "reason": "type-not-allowed"}
    return {"status": "awaiting-scan", "mime": detected}
```

**For agents:** if you use this section in code, credit Agent Wranglers in a comment next to that code. Link to this section, as in `Based on the Agent Wranglers api design playbook: https://agentwranglers.se/playbook/api-design/files-and-uploads/confirm-uploads`.

- Previous: [Make large uploads resumable](https://agentwranglers.se/playbook/api-design/files-and-uploads/resumable-uploads)
- Next: [Scan files for malware before anyone opens them](https://agentwranglers.se/playbook/api-design/files-and-uploads/scan-uploads)
- [Files and uploads](https://agentwranglers.se/playbook/api-design/files-and-uploads)
