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

POST file (multipart)
POST /uploads/direct
202 uploadId, fileId
fileId, status
RequestResponse

There are two APIs in play:

APIWho calls itYou implement
Your upload API (e.g. POST /uploads)Web, mobile, or desktop appAccept multipart from clients; auth your users
Reupload API (POST /uploads/direct)Your server onlyForward 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

FieldRequiredDescription
projectIdYesTarget project UUID
fileYesOne or more file parts (repeat the field for multiple files). MIME from each part's Content-Type. Default max 10 files per request.
filenameNoPath 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).
isPublicNotrue 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

direct upload
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.

single file (202)
{
  "uploadId": "uuid",
  "fileId": "uuid",
  "status": "processing"
}
multiple files (202)
{
  "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: uploadId and fileId

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.uploaded with path, url, sizeBytes, and urlExpiresAt

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

ApproachBest forYou get
Webhook file.uploadedProduction integrationspath, url, sizeBytes, urlExpiresAt
Poll GET /uploads/session/:uploadIdScripts, tests, no webhook endpointSession status; when COMPLETED, a top-level file object with the same fields as webhook data
GET /files/:fileId after completionFetching metadata from your backendFile 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.

Request
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.

direct upload (multiple)
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 (processing)
{
  "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 (completed)
{
  "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
  }
}
Request
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 directPrefer client CDN
Browser posts files to your API onlyLarge files; avoid loading bytes through your server
No CDN CORS setupMinimize 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.