API · Uploads
Server-side uploads (direct)
Send file bytes to the Reupload API in one multipart/form-data request. Reupload creates the session, writes to storage, and enqueues processing — no separate complete step and no CDN PUT.
Use when users upload to your backend first and your server forwards the file. The browser never needs uploadUrl or project upload CORS settings.
Uploads overview · Client-side (CDN) · @reupload/sdk · reupload-sdk · Server-side upload guide (integration walkthrough)
Architecture
Direct flow: the client sends file bytes to your server; your server forwards them to Reupload with POST /uploads/direct. No signed CDN URL, no client PUT to storage, and no separate complete step.
Browser
React / Next
Your API
Node.js
Reupload
API
There are two APIs in play:
| API | Who calls it | You implement |
|---|---|---|
Your upload API (e.g. POST /uploads) | Web, mobile, or desktop app | Accept multipart from clients; auth your users |
Reupload API (POST /uploads/direct) | Your server only | Forward file bytes with your ru_ API key — never expose the key to clients |
Full integration walkthrough: Server-side upload guide.
Direct upload
POST /uploads/direct — requires files.write. Returns 202 Accepted.
Form fields
| Field | Required | Description |
|---|---|---|
projectId | Yes | Target project UUID |
file | Yes | One or more file parts (repeat the field for multiple files). MIME from each part's Content-Type. Default max 10 files per request. |
filename | No | Path such as docs/report.pdf (folders created under the project); defaults to the uploaded file name. Single-file only — omit when sending multiple fileparts (use each part's filename instead). |
isPublic | No | true or false (case-insensitive). When true, the file is publicly accessible via CDN after processing. Applies to all files in a multi-file request. Default false. |
Example
curl -s -X POST 'https://api.reupload.dev/api/v1/uploads/direct' \
-H 'Authorization: Bearer ru_...' \
-F 'projectId=your-project-uuid' \
-F 'filename=docs/report.pdf' \
-F 'file=@./report.pdf;type=application/pdf'Response
One file — same shape as before. Two or more files — an array under files.
{
"uploadId": "uuid",
"fileId": "uuid",
"status": "processing"
}{
"files": [
{ "uploadId": "uuid", "fileId": "uuid", "status": "processing" },
{ "uploadId": "uuid", "fileId": "uuid", "status": "processing" }
]
}Why the response is 202 + processing
It is normal to expect a direct upload to return the full file record immediately — your server already sent the bytes in the same request. Reupload intentionally separates stored in object storage from ready to use.
What already happened when you get 202
- File bytes were written to storage
- Declared size and content type were verified against the stored object
- You received stable identifiers:
uploadIdandfileId
You are not being asked to upload again or call a separate /complete step (that step is internal for direct upload).
What still runs in the background
The same processing pipeline as the CDN upload flow (create session → PUT → complete):
- Malware scanning
- Finalizing the file record (
status = COMPLETED) - Usage and quota accounting
- Webhooks —
file.uploadedwithpath,url,sizeBytes, andurlExpiresAt
Until that finishes, treat the file as pending. GET /files/:fileId returns the file only after processing completes.
Why not return url / path in the upload response?
- Safety — processing can fail (for example malware rejection). A URL in the upload response would imply the file is safe to serve before that check finishes.
- One integration model — CDN and direct uploads share one pipeline, so polling and webhooks work the same way regardless of how bytes were sent.
- Fast API responses — your server is not blocked on scanning or webhook delivery; you avoid HTTP timeouts on large files.
How to get the file when it is ready
| Approach | Best for | You get |
|---|---|---|
Webhook file.uploaded | Production integrations | path, url, sizeBytes, urlExpiresAt |
Poll GET /uploads/session/:uploadId | Scripts, tests, no webhook endpoint | Session status; when COMPLETED, a top-level file object with the same fields as webhook data |
GET /files/:fileId after completion | Fetching metadata from your backend | File record; use GET /files/:fileId/access for a fresh download URL |
Save fileId from the 202 response — it is stable and appears in webhooks and file APIs. Typical processing completes in seconds.
curl -s -X POST 'https://api.reupload.dev/api/v1/uploads/direct' \
-H 'Authorization: Bearer ru_your_api_key_here' \
-F 'projectId=00000000-0000-4000-8000-000000000001' \
-F 'filename=your-file.pdf' \
-F 'file=@./your-file.pdf;type=application/pdf'Select a file in the Try it panel — session and upload will use the same file metadata.
Multiple files
Repeat the file field. Do not send a global filename field — each part uses its own name.
curl -s -X POST 'https://api.reupload.dev/api/v1/uploads/direct' \
-H 'Authorization: Bearer ru_...' \
-F 'projectId=your-project-uuid' \
-F 'file=@./report.pdf;type=application/pdf' \
-F 'file=@./notes.txt;type=text/plain'Files are processed sequentially. If a later file fails validation or storage, earlier files may already be stored and processing — there is no automatic rollback.
When the file is ready
After 202, wait for processing to finish before serving the file or showing a download link. Recommended: handle a webhook file.uploaded (includes path, url, and sizeBytes). Or poll GET /uploads/session/:uploadId until status is COMPLETED, then use GET /files/:fileId or /access.
{
"session": {
"id": "uuid",
"fileId": "uuid",
"status": "PROCESSING",
"expectedSize": 502400,
"expectedMime": "application/pdf"
}
}When session.status is COMPLETED, the response also includes file — the same fields as a file.uploaded webhook data object (path, url, sizeBytes, urlExpiresAt, and so on):
{
"session": {
"id": "uuid",
"fileId": "uuid",
"status": "COMPLETED",
"expectedSize": 502400,
"expectedMime": "application/pdf",
"completedAt": "2026-05-30T14:30:00.000Z"
},
"file": {
"fileId": "uuid",
"projectId": "uuid",
"path": "projects/uuid/files/uuid/docs/report.pdf",
"name": "report.pdf",
"mimeType": "application/pdf",
"sizeBytes": 502400,
"url": "https://cdn.example.com/v1/download/...",
"urlExpiresAt": "2026-05-30T14:35:00.000Z",
"isPublic": false
}
}curl -s 'https://api.reupload.dev/api/v1/uploads/session/00000000-0000-4000-8000-000000000002' \
-H 'Authorization: Bearer ru_your_api_key_here'When to use server-side vs CDN
| Prefer server direct | Prefer client CDN |
|---|---|
| Browser posts files to your API only | Large files; avoid loading bytes through your server |
| No CDN CORS setup | Minimize your server bandwidth |
| Simpler client (one call to your backend) | Mobile/desktop PUT to CDN without your proxy |
Errors
Same validation, quotas, and rate limits as session create. See Errors.