UploadKit
Getting Started

API Only

Use the UploadKit REST API directly from any language or framework.

The UploadKit REST API lets you manage uploads from any backend language or framework — Python, Go, Ruby, PHP, or plain curl. No SDK required.

All file uploads follow the presigned URL flow: your server requests a short-lived upload URL, your client uploads directly to storage, then your server confirms completion.

Authentication

Pass your API key in the x-api-key header on every request:

curl https://api.uploadkit.dev/api/v1/upload/request \
  -H "x-api-key: uk_live_xxxxxxxxxxxxxxxxxxxxx"

Never expose your API key in client-side JavaScript. For browser uploads, use the React SDK which handles key security for you.

Request a presigned URL

Before uploading, request a presigned PUT URL from the UploadKit API. This validates your API key, checks file type and size against your route configuration, and generates a temporary upload URL.

POST /api/v1/upload/request

Request body:

FieldTypeRequiredDescription
fileNamestringYesOriginal file name (1–255 chars)
fileSizenumberYesFile size in bytes (positive integer)
contentTypestringYesMIME type (e.g. image/jpeg)
routeSlugstringYesYour file route name (1–100 chars)
metadataobjectNoArbitrary key/value pairs passed to your webhook
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": "profile.jpg",
    "fileSize": 204800,
    "contentType": "image/jpeg",
    "routeSlug": "imageUploader"
  }'
const response = await fetch('https://api.uploadkit.dev/api/v1/upload/request', {
  method: 'POST',
  headers: {
    'x-api-key': 'uk_live_xxxxxxxxxxxxxxxxxxxxx',
    'Content-Type': 'application/json',
  },
  body: JSON.stringify({
    fileName: 'profile.jpg',
    fileSize: 204800,
    contentType: 'image/jpeg',
    routeSlug: 'imageUploader',
  }),
});

const { fileId, uploadUrl } = await response.json();
import requests

response = requests.post(
    'https://api.uploadkit.dev/api/v1/upload/request',
    headers={
        'x-api-key': 'uk_live_xxxxxxxxxxxxxxxxxxxxx',
        'Content-Type': 'application/json',
    },
    json={
        'fileName': 'profile.jpg',
        'fileSize': 204800,
        'contentType': 'image/jpeg',
        'routeSlug': 'imageUploader',
    },
)

data = response.json()
file_id = data['fileId']
upload_url = data['uploadUrl']

Response:

{
  "fileId": "file_01J9KXYZ...",
  "uploadUrl": "https://pub-xxx.r2.dev/uploads/abc123?X-Amz-Signature=...",
  "cdnUrl": "https://cdn.uploadkit.dev/uploads/abc123.jpg",
  "key": "uploads/abc123.jpg"
}

Upload to storage

Use the presigned URL to PUT the file directly to R2/S3. You must include the exact Content-Type that was used when requesting the URL — it's locked into the signature.

curl -X PUT "https://pub-xxx.r2.dev/uploads/abc123?X-Amz-Signature=..." \
  -H "Content-Type: image/jpeg" \
  --data-binary @profile.jpg
// fileBuffer is an ArrayBuffer, Blob, or ReadableStream
await fetch(uploadUrl, {
  method: 'PUT',
  headers: { 'Content-Type': 'image/jpeg' },
  body: fileBuffer,
});
with open('profile.jpg', 'rb') as f:
    requests.put(
        upload_url,
        headers={'Content-Type': 'image/jpeg'},
        data=f,
    )

Confirm the upload

After the PUT succeeds, notify UploadKit that the upload is complete. This triggers your webhook (if configured) and marks the file as available.

POST /api/v1/upload/complete

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_01J9KXYZ..."}'
const result = await fetch('https://api.uploadkit.dev/api/v1/upload/complete', {
  method: 'POST',
  headers: {
    'x-api-key': 'uk_live_xxxxxxxxxxxxxxxxxxxxx',
    'Content-Type': 'application/json',
  },
  body: JSON.stringify({ fileId }),
});

const file = await result.json();
// file.url — permanent CDN URL for the uploaded file
result = requests.post(
    'https://api.uploadkit.dev/api/v1/upload/complete',
    headers={
        'x-api-key': 'uk_live_xxxxxxxxxxxxxxxxxxxxx',
        'Content-Type': 'application/json',
    },
    json={'fileId': file_id},
)

file = result.json()
print(file['url'])  # permanent CDN URL

Response:

{
  "id": "file_01J9KXYZ...",
  "key": "uploads/abc123/profile.jpg",
  "name": "profile.jpg",
  "size": 204800,
  "type": "image/jpeg",
  "url": "https://cdn.uploadkit.dev/uploads/abc123/profile.jpg",
  "status": "UPLOADED",
  "createdAt": "2026-04-07T11:59:30Z"
}

List files

GET /api/v1/files

Returns paginated files for the current project, newest first.

curl "https://api.uploadkit.dev/api/v1/files?limit=20" \
  -H "x-api-key: uk_live_xxxxxxxxxxxxxxxxxxxxx"
const response = await fetch('https://api.uploadkit.dev/api/v1/files?limit=20', {
  headers: { 'x-api-key': 'uk_live_xxxxxxxxxxxxxxxxxxxxx' },
});
const { files, nextCursor } = await response.json();
response = requests.get(
    'https://api.uploadkit.dev/api/v1/files',
    headers={'x-api-key': 'uk_live_xxxxxxxxxxxxxxxxxxxxx'},
    params={'limit': 20},
)
data = response.json()
files = data['files']

Query parameters:

ParameterTypeDefaultDescription
limitnumber50Results per page (1–100)
cursorstringPagination cursor from previous response

Delete a file

DELETE /api/v1/files/:key

Permanently removes the file from storage and marks it deleted in the database.

curl -X DELETE \
  "https://api.uploadkit.dev/api/v1/files/uploads%2Fabc123%2Fprofile.jpg" \
  -H "x-api-key: uk_live_xxxxxxxxxxxxxxxxxxxxx"
const key = 'uploads/abc123/profile.jpg';
await fetch(`https://api.uploadkit.dev/api/v1/files/${encodeURIComponent(key)}`, {
  method: 'DELETE',
  headers: { 'x-api-key': 'uk_live_xxxxxxxxxxxxxxxxxxxxx' },
});
from urllib.parse import quote

key = 'uploads/abc123/profile.jpg'
requests.delete(
    f'https://api.uploadkit.dev/api/v1/files/{quote(key, safe="")}',
    headers={'x-api-key': 'uk_live_xxxxxxxxxxxxxxxxxxxxx'},
)

Error responses

All errors follow a consistent shape:

{
  "error": "File size exceeds the 4 MB limit for route imageUploader",
  "code": "FILE_TOO_LARGE"
}
HTTP statusWhen
400Invalid request body (missing fields, wrong types)
401Missing or invalid API key
403File type or size rejected by route configuration
404File not found
429Rate limit exceeded
500Internal server error

On this page