UploadKit
API Reference

Upload Endpoints

Request presigned URLs, confirm uploads, and manage multipart uploads via the REST API.

The upload flow is a two-step process: request a presigned URL, then confirm after the client uploads directly to storage. Files over 10 MB use the multipart endpoints automatically when using the SDK, but you can drive the multipart flow manually via these endpoints.

POST /api/v1/upload/request

Request a presigned URL for a single-file upload.

Request body:

FieldTypeRequiredDescription
fileNamestringYesOriginal file name (e.g., photo.jpg)
fileSizenumberYesFile size in bytes
contentTypestringYesMIME type (e.g., image/jpeg)
routeSlugstringYesFile route slug defined in your file router
metadataobjectNoArbitrary key-value pairs forwarded to onUploadComplete

Response:

{
  "fileId": "file_abc123",
  "uploadUrl": "https://your-bucket.r2.cloudflarestorage.com/uploads/abc123/photo.jpg?X-Amz-Signature=...",
  "key": "uploads/abc123/photo.jpg",
  "cdnUrl": "https://cdn.uploadkit.dev/uploads/abc123/photo.jpg"
}

curl example:

curl -X POST https://api.uploadkit.dev/api/v1/upload/request \
  -H "x-api-key: uk_live_xxxxxxxxxxxxxxxxxxxxx" \
  -H "Content-Type: application/json" \
  -d '{
    "fileName": "photo.jpg",
    "fileSize": 1048576,
    "contentType": "image/jpeg",
    "routeSlug": "imageUploader",
    "metadata": { "userId": "user_123" }
  }'

After receiving the uploadUrl, the client performs a PUT directly to that URL:

curl -X PUT "<uploadUrl>" \
  -H "Content-Type: image/jpeg" \
  --data-binary @photo.jpg

Error cases:

CodeCause
UNAUTHORIZEDInvalid or missing API key
FILE_TOO_LARGEfileSize exceeds route or tier limit
FILE_TYPE_NOT_ALLOWEDcontentType not in route's allowedTypes
TIER_LIMIT_EXCEEDEDMonthly upload quota reached
NOT_FOUNDrouteSlug does not exist in your project

POST /api/v1/upload/complete

Notify the API that the client-side PUT to the presigned URL has finished. This triggers verification against R2 and fires the onUploadComplete webhook.

Request body:

FieldTypeRequiredDescription
fileIdstringYesfileId returned from /upload/request
metadataobjectNoOptional metadata update (merges with existing)

Response:

{
  "id": "file_abc123",
  "key": "uploads/abc123/photo.jpg",
  "url": "https://cdn.uploadkit.dev/uploads/abc123/photo.jpg",
  "fileName": "photo.jpg",
  "fileSize": 1048576,
  "contentType": "image/jpeg",
  "status": "UPLOADED",
  "createdAt": "2026-04-08T12:00:00.000Z"
}

curl example:

curl -X POST https://api.uploadkit.dev/api/v1/upload/complete \
  -H "x-api-key: uk_live_xxxxxxxxxxxxxxxxxxxxx" \
  -H "Content-Type: application/json" \
  -d '{
    "fileId": "file_abc123"
  }'

Error cases:

CodeCause
UPLOAD_NOT_FOUNDfileId does not exist or belongs to another project
UPLOAD_NOT_VERIFIEDR2 object not found — the PUT may have failed

POST /api/v1/upload/multipart/init

Initialize a multipart upload for files over 10 MB. Returns presigned URLs for each chunk.

Request body:

FieldTypeRequiredDescription
fileNamestringYesOriginal file name
fileSizenumberYesTotal file size in bytes
contentTypestringYesMIME type
routeSlugstringYesFile route slug
metadataobjectNoForwarded to onUploadComplete

Response:

{
  "fileId": "file_abc123",
  "uploadId": "mpu_xyz789",
  "key": "uploads/abc123/large-video.mp4",
  "parts": [
    { "partNumber": 1, "uploadUrl": "https://...?partNumber=1&uploadId=mpu_xyz789&..." },
    { "partNumber": 2, "uploadUrl": "https://...?partNumber=2&uploadId=mpu_xyz789&..." },
    { "partNumber": 3, "uploadUrl": "https://...?partNumber=3&uploadId=mpu_xyz789&..." }
  ]
}

Each part URL is for a PUT request with a chunk of the file. Chunks must be at least 5 MB except the last part.

curl example:

curl -X POST https://api.uploadkit.dev/api/v1/upload/multipart/init \
  -H "x-api-key: uk_live_xxxxxxxxxxxxxxxxxxxxx" \
  -H "Content-Type: application/json" \
  -d '{
    "fileName": "large-video.mp4",
    "fileSize": 104857600,
    "contentType": "video/mp4",
    "routeSlug": "videoUploader"
  }'

POST /api/v1/upload/multipart/complete

Signal that all parts have been uploaded. R2 assembles the parts into the final object.

Request body:

FieldTypeRequiredDescription
fileIdstringYesfileId from /multipart/init
uploadIdstringYesuploadId from /multipart/init
partsarrayYesArray of { partNumber, etag } — ETags from each part PUT response
metadataobjectNoOptional metadata update

curl example:

curl -X POST https://api.uploadkit.dev/api/v1/upload/multipart/complete \
  -H "x-api-key: uk_live_xxxxxxxxxxxxxxxxxxxxx" \
  -H "Content-Type: application/json" \
  -d '{
    "fileId": "file_abc123",
    "uploadId": "mpu_xyz789",
    "parts": [
      { "partNumber": 1, "etag": "\"etag-value-1\"" },
      { "partNumber": 2, "etag": "\"etag-value-2\"" },
      { "partNumber": 3, "etag": "\"etag-value-3\"" }
    ]
  }'

The response is the same file object returned by /upload/complete.


POST /api/v1/upload/multipart/abort

Abort an in-progress multipart upload and clean up all uploaded parts.

Request body:

FieldTypeRequiredDescription
fileIdstringYesfileId from /multipart/init
uploadIdstringYesuploadId from /multipart/init

curl example:

curl -X POST https://api.uploadkit.dev/api/v1/upload/multipart/abort \
  -H "x-api-key: uk_live_xxxxxxxxxxxxxxxxxxxxx" \
  -H "Content-Type: application/json" \
  -d '{
    "fileId": "file_abc123",
    "uploadId": "mpu_xyz789"
  }'

Response:

{ "aborted": true }

When using the @uploadkitdev/core or @uploadkitdev/react SDK, multipart uploads are handled automatically for files over 10 MB. You do not need to call these endpoints manually.

On this page