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:
| Field | Type | Required | Description |
|---|---|---|---|
fileName | string | Yes | Original file name (1–255 chars) |
fileSize | number | Yes | File size in bytes (positive integer) |
contentType | string | Yes | MIME type (e.g. image/jpeg) |
routeSlug | string | Yes | Your file route name (1–100 chars) |
metadata | object | No | Arbitrary 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 fileresult = 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 URLResponse:
{
"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:
| Parameter | Type | Default | Description |
|---|---|---|---|
limit | number | 50 | Results per page (1–100) |
cursor | string | — | Pagination 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 status | When |
|---|---|
| 400 | Invalid request body (missing fields, wrong types) |
| 401 | Missing or invalid API key |
| 403 | File type or size rejected by route configuration |
| 404 | File not found |
| 429 | Rate limit exceeded |
| 500 | Internal server error |