Base URL: https://tempfile.org/api
Our REST API enables developers to integrate temporary file hosting into their applications. Upload files programmatically, get instant download URLs, and leverage automatic deletion for secure temporary storage.
Test endpoints in real-time with our interactive documentation, or download the complete OpenAPI 3.2 schema for integration
File Size: 100MB maximum per file
Files per Request: 20 maximum - send multiple files in one request
Rate Limit: 200 requests per hour per IP (up to 20 files per request = up to 4,000 files per hour)
Retention: 1-48 hours automatic deletion
Authentication: None required
CDN Caching: Intelligent cache invalidation on delete
Supported Formats: All safe file types (malware scanning included)
Endpoint: POST /api/upload/local
Content-Type: multipart/form-data
files field once per file. Array-style names (files[], files[0], files[1], ...) are also accepted, which is how PHP and some HTTP libraries name themcurl -X POST https://tempfile.org/api/upload/local \
-F "[email protected]" \
-F "expiryHours=24"
Uploading more than one file? Send them together in a single request (up to 20) instead of one request per file - see Upload Multiple Files in One Request.
{
"success": true,
"files": [
{
"id": "kN8mP2xQvR7",
"name": "document.pdf",
"size": 2048576,
"url": "https://tempfile.org/kN8mP2xQvR7/",
"expiryTime": 1725273634567
}
],
"message": "1 file(s) uploaded successfully"
}
Endpoint: POST /api/upload/url
Content-Type: application/json
curl -X POST https://tempfile.org/api/upload/url \
-H "Content-Type: application/json" \
-d '{
"url": "https://example.com/image.jpg",
"customName": "my-image.jpg",
"expiryHours": 6
}'
{
"success": true,
"file": {
"id": "Tz9mKpWx3Nq",
"name": "my-image.jpg",
"size": 1024000,
"url": "https://tempfile.org/Tz9mKpWx3Nq/",
"expiryTime": 1725208834567
}
}
Endpoint: GET /api/file/{fileId}
curl https://tempfile.org/api/file/kN8mP2xQvR7
{
"success": true,
"file": {
"id": "kN8mP2xQvR7",
"name": "document.pdf",
"size": 2048576,
"mimeType": "application/pdf",
"uploadTime": 1725187234567,
"expiryTime": 1725273634567,
"exists": true,
"downloadUrl": "/kN8mP2xQvR7/download",
"security": {
"hasWarning": false,
"warningLevel": "none",
"warningMessage": null,
"suspiciousPatterns": []
}
}
}
Endpoint: GET /api/file/{fileId}/security
Returns detailed security analysis including risk assessment, detected patterns, and safety recommendations.
curl https://tempfile.org/api/file/kN8mP2xQvR7/security
{
"success": true,
"security": {
"fileId": "kN8mP2xQvR7",
"fileName": "project-build.zip",
"mimeType": "application/x-zip-compressed",
"hasWarning": true,
"suspiciousPatterns": [".exe", ".bat"],
"hash": "a1b2c3d4e5f6789...",
"size": 127133,
"uploadType": "local",
"uploadTime": 1725187234567,
"clientIP": "172.71.130.xxx",
"riskLevel": "medium",
"recommendations": [
"Scan file with antivirus before opening",
"Verify file source is trustworthy",
"Open in isolated/sandboxed environment if possible",
"Extract archive in secure location and inspect contents before execution"
]
}
}
hasWarning (boolean): Whether file triggered security alertssuspiciousPatterns (array): Detected patterns like [".exe", "eval(", "shell_exec"]riskLevel (string): Overall risk assessment - "safe", "low", "medium", "high", or "unknown"recommendations (array): Safety recommendations based on detected patternshash (string): SHA-256 hash for file integrity verification| Level | Description | Example Patterns |
|---|---|---|
| safe | No security warnings detected | Clean files like images, PDFs, documents |
| low | Common development patterns | eval(), exec() in scripts |
| medium | Contains executable files | .exe, .bat, .cmd in archives |
| high | Potentially dangerous content | vbscript:, shell_exec, root exploits |
| unknown | Warning exists but pattern unclear | Unclassified suspicious patterns |
hasWarning and riskLevelrecommendations array to end usersriskLevel of "medium" or "high"hash after download to detect tampering// JavaScript example
async function downloadFile(fileId) {
// Get security info first
const securityResponse = await fetch(`/api/file/${fileId}/security`);
const securityData = await securityResponse.json();
if (!securityData.success) {
console.error('Failed to get security info');
return;
}
const { riskLevel, recommendations, hasWarning } = securityData.security;
// Warn user if file has security concerns
if (hasWarning) {
const proceed = confirm(
`Security Warning (${riskLevel.toUpperCase()} risk):\n\n` +
recommendations.join('\n') +
'\n\nDo you want to proceed with download?'
);
if (!proceed) return;
}
// Proceed with download
window.location.href = `/${fileId}/download`;
}
Endpoint: DELETE /api/file/{fileId}
curl -X DELETE https://tempfile.org/api/file/kN8mP2xQvR7
{
"success": true,
"message": "File deleted successfully"
}
Endpoint: GET /{fileId}/download
Returns the actual file content with appropriate headers for download.
curl -L https://tempfile.org/kN8mP2xQvR7/download \
-o downloaded_file.pdf
Uploaded files are accessible via multiple URL formats:
/{fileId}/ - Shows file info with download link (e.g., /kN8mP2xQvR7/)/{fileId}/download - Immediate file download (e.g., /kN8mP2xQvR7/download)/{fileId}/preview - For image files only (e.g., /kN8mP2xQvR7/preview)Expiry: when a file expires it is deleted. From that moment the file info, security, download and preview endpoints (and the file page) return 404, and image previews show the expired-image placeholder.
All API responses use JSON format with consistent structure:
{
"success": true,
"files": [...], // For upload endpoints
"file": {...}, // For single file operations
"message": "..." // Human-readable message
}
{
"success": false,
"error": "Descriptive error message"
}
All endpoints return errors in the format shown under Response Formats above (success: false plus a descriptive error message).
files)Retry-After header, then retry (see Rate Limits & Fair Usage)Our API implements fair usage policies to ensure service quality for all users:
The upload limit counts requests, not files. Uploading one file per request uses up the 200 requests after only 200 files and then returns 429. Sending 20 files per request lets the same 200 requests carry up to 4,000 files per hour. Batching is the intended way to use the API and is not a way around the limit. See Upload Multiple Files in One Request.
Upload responses include these headers, so your code can see how much quota is left:
| Header | Description |
|---|---|
X-RateLimit-Limit |
Maximum requests allowed in the current window (e.g. 200) |
X-RateLimit-Remaining |
Requests left in the current window |
X-RateLimit-Reset |
Unix timestamp (in seconds) when the window resets |
Retry-After |
Only sent with a 429 response: seconds to wait before trying again |
X-RateLimit-Limit: 200
X-RateLimit-Remaining: 187
X-RateLimit-Reset: 1735467634
A rate-limited request returns 429 Too Many Requests with the headers below and the usual error body (success: false and an error message).
HTTP/1.1 429 Too Many Requests
Retry-After: 1423
X-RateLimit-Limit: 200
X-RateLimit-Remaining: 0
X-RateLimit-Reset: 1735467634
X-RateLimit-Remaining: when it reaches 0, wait until X-RateLimit-Reset before the next request.429, wait Retry-After seconds and retry the same request.400 or 413.The multi-file examples below implement all of this in cURL/Bash, JavaScript, Python and PHP.
Examples in cURL/Bash, JavaScript, Python and PHP. Start with a single file, then see how to upload many files efficiently and handle rate limits.
Good for one-off uploads. If you have more than one file, use the multi-file approach below instead of calling this once per file.
// Upload file
const formData = new FormData();
formData.append('files', fileInput.files[0]);
formData.append('expiryHours', '24');
try {
const response = await fetch('https://tempfile.org/api/upload/local', {
method: 'POST',
body: formData
});
if (!response.ok) {
throw new Error(`HTTP ${response.status}: ${response.statusText}`);
}
const result = await response.json();
if (result.success) {
console.log('Upload URL:', result.files[0].url);
console.log('File ID:', result.files[0].id);
console.log('Expires at:', new Date(result.files[0].expiryTime));
} else {
throw new Error(result.error);
}
} catch (error) {
console.error('Upload failed:', error.message);
}
import requests
# Upload file
with open('document.pdf', 'rb') as f:
files = {'files': f}
data = {'expiryHours': '24'}
response = requests.post(
'https://tempfile.org/api/upload/local',
files=files,
data=data
)
result = response.json()
print(f"Upload URL: {result['files'][0]['url']}")
<?php
$curl = curl_init();
curl_setopt_array($curl, [
CURLOPT_URL => 'https://tempfile.org/api/upload/local',
CURLOPT_POST => true,
CURLOPT_POSTFIELDS => [
'files' => new CURLFile('document.pdf'),
'expiryHours' => '24'
],
CURLOPT_RETURNTRANSFER => true,
CURLOPT_TIMEOUT => 60,
CURLOPT_FOLLOWLOCATION => true
]);
$response = curl_exec($curl);
$httpCode = curl_getinfo($curl, CURLINFO_HTTP_CODE);
if (curl_error($curl)) {
echo "Error: " . curl_error($curl);
} else {
$result = json_decode($response, true);
if ($result && $result['success']) {
echo "Upload URL: " . $result['files'][0]['url'];
} else {
echo "Upload failed: " . ($result['error'] ?? 'Unknown error');
}
}
curl_close($curl);
Add one files field per file (up to 20). The response contains one entry per file in the files array.
curl -X POST https://tempfile.org/api/upload/local \
-F "[email protected]" \
-F "[email protected]" \
-F "[email protected]" \
-F "expiryHours=24"
{
"success": true,
"files": [
{ "id": "kN8mP2xQvR7", "name": "photo1.jpg", "size": 482113, "url": "https://tempfile.org/kN8mP2xQvR7/", "expiryTime": 1725273634567 },
{ "id": "Tz9mKpWx3Nq", "name": "photo2.jpg", "size": 391204, "url": "https://tempfile.org/Tz9mKpWx3Nq/", "expiryTime": 1725273634567 },
{ "id": "Wb4hRs7LcY2", "name": "report.pdf", "size": 2048576, "url": "https://tempfile.org/Wb4hRs7LcY2/", "expiryTime": 1725273634567 }
],
"message": "3 file(s) uploaded successfully"
}
The examples below take any number of files and:
X-RateLimit-Remaining and pause until X-RateLimit-Reset when the quota is used up, so they never hit a 429 in the first place.429 still happens, wait for Retry-After (or X-RateLimit-Reset), then retry. With no headers they use exponential backoff with jitter.429, 502, 503, 504 and network errors (up to 5 times). Other errors such as 400 or 413 fail immediately.#!/usr/bin/env bash
# Upload many files, 20 per request, backing off when rate limited
API="https://tempfile.org/api/upload/local"
MAX_FILES_PER_REQUEST=20
MAX_RETRIES=5
upload_batch() {
local args=() f
for f in "$@"; do args+=(-F "files=@$f"); done
for ((attempt = 0; attempt <= MAX_RETRIES; attempt++)); do
# -D saves response headers, -w prints the HTTP status
status=$(curl -s -o response.json -D headers.txt -w '%{http_code}' \
-X POST "$API" "${args[@]}" -F "expiryHours=24")
if [[ $status == 2* ]]; then
cat response.json; echo
# Quota used up? Wait for the window to reset instead of earning a 429
remaining=$(grep -i '^x-ratelimit-remaining:' headers.txt | tr -d '\r' | awk '{print $2}')
reset=$(grep -i '^x-ratelimit-reset:' headers.txt | tr -d '\r' | awk '{print $2}')
if [[ $remaining == 0 && -n $reset ]]; then
wait=$(( reset - $(date +%s) ))
(( wait > 0 )) && { echo "Rate limit reached - waiting ${wait}s" >&2; sleep "$wait"; }
fi
return 0
fi
# Only retry 429 and temporary gateway errors
if [[ $status != 429 && $status != 502 && $status != 503 && $status != 504 ]]; then
echo "HTTP $status: $(cat response.json)" >&2
return 1
fi
# Prefer the server's Retry-After, otherwise exponential backoff
delay=$(grep -i '^retry-after:' headers.txt | tr -d '\r' | awk '{print $2}')
[[ $delay =~ ^[0-9]+$ ]] || delay=$(( 2 ** attempt ))
echo "HTTP $status - retrying in ${delay}s" >&2
sleep "$delay"
done
echo "Gave up after $MAX_RETRIES retries" >&2
return 1
}
# Usage: ./upload.sh *.jpg
files=("$@")
for ((i = 0; i < ${#files[@]}; i += MAX_FILES_PER_REQUEST)); do
upload_batch "${files[@]:i:MAX_FILES_PER_REQUEST}" || exit 1
done
// Usage: node upload.mjs file1.pdf file2.png file3.zip ...
// Node.js 18+ (ES module) - uses the built-in fetch, FormData and Blob
import { readFile } from 'node:fs/promises';
import path from 'node:path';
const API = 'https://tempfile.org/api/upload/local';
const MAX_FILES_PER_REQUEST = 20; // the API accepts up to 20 files per request
const MAX_RETRIES = 5;
const sleep = (ms) => new Promise((resolve) => setTimeout(resolve, ms));
// How long to wait before retrying? Prefer what the server tells us.
function retryDelayMs(res, attempt) {
const retryAfter = Number(res?.headers.get('retry-after')); // seconds
if (retryAfter > 0) return retryAfter * 1000;
const reset = Number(res?.headers.get('x-ratelimit-reset')); // unix timestamp (seconds)
if (reset > 0) return Math.max(reset * 1000 - Date.now(), 1000);
// No headers (network error, or a proxy removed them): exponential backoff + jitter
return Math.min(2 ** attempt * 1000, 60000) + Math.random() * 1000;
}
// If this response used up our last request, how long until the window resets?
function quotaWaitMs(res) {
if (res.headers.get('x-ratelimit-remaining') !== '0') return 0;
const reset = Number(res.headers.get('x-ratelimit-reset'));
return reset > 0 ? Math.max(reset * 1000 - Date.now(), 0) : 0;
}
// Upload up to 20 files in ONE request, retrying when rate limited
async function uploadBatch(filePaths, expiryHours = 24) {
for (let attempt = 0; attempt <= MAX_RETRIES; attempt++) {
// Rebuild the form on every attempt - a request body can only be sent once
const form = new FormData();
for (const filePath of filePaths) {
form.append('files', new Blob([await readFile(filePath)]), path.basename(filePath));
}
form.append('expiryHours', String(expiryHours));
let res;
try {
res = await fetch(API, { method: 'POST', body: form });
} catch (err) {
// Network error - back off and try again
if (attempt === MAX_RETRIES) throw err;
await sleep(retryDelayMs(null, attempt));
continue;
}
if (res.ok) {
const result = await res.json();
if (!result.success) throw new Error(result.error);
return { files: result.files, waitMs: quotaWaitMs(res) };
}
// Retry on 429 (rate limited) and temporary gateway errors, fail on anything else
const retryable = res.status === 429 || [502, 503, 504].includes(res.status);
if (!retryable || attempt === MAX_RETRIES) {
const body = await res.json().catch(() => ({}));
throw new Error(`HTTP ${res.status}: ${body.error || res.statusText}`);
}
const delay = retryDelayMs(res, attempt);
console.log(`HTTP ${res.status} - retrying in ${Math.ceil(delay / 1000)}s`);
await sleep(delay);
}
}
// Upload any number of files, 20 per request
async function uploadMany(filePaths, expiryHours = 24) {
const uploaded = [];
for (let i = 0; i < filePaths.length; i += MAX_FILES_PER_REQUEST) {
const batch = filePaths.slice(i, i + MAX_FILES_PER_REQUEST);
const { files, waitMs } = await uploadBatch(batch, expiryHours);
uploaded.push(...files);
console.log(`Uploaded ${uploaded.length}/${filePaths.length} files`);
// Quota used up? Wait for the window to reset instead of earning a 429
if (waitMs > 0 && i + MAX_FILES_PER_REQUEST < filePaths.length) {
console.log(`Rate limit reached - waiting ${Math.ceil(waitMs / 1000)}s`);
await sleep(waitMs);
}
}
return uploaded;
}
const files = await uploadMany(process.argv.slice(2));
for (const f of files) console.log(f.name, '->', f.url);
# Usage: python upload.py file1.pdf file2.png file3.zip ...
import os
import random
import sys
import time
import requests
API = "https://tempfile.org/api/upload/local"
MAX_FILES_PER_REQUEST = 20 # the API accepts up to 20 files per request
MAX_RETRIES = 5
def retry_delay(response, attempt):
"""Seconds to wait before retrying. Prefer what the server tells us."""
if response is not None:
retry_after = response.headers.get("Retry-After")
if retry_after and retry_after.isdigit():
return int(retry_after)
reset = response.headers.get("X-RateLimit-Reset") # unix timestamp (seconds)
if reset and reset.isdigit():
return max(int(reset) - time.time(), 1)
# No headers (network error): exponential backoff + jitter
return min(2 ** attempt, 60) + random.random()
def quota_wait(response):
"""If this response used up our last request, seconds until the window resets."""
if response.headers.get("X-RateLimit-Remaining") != "0":
return 0
reset = response.headers.get("X-RateLimit-Reset", "")
return max(int(reset) - time.time(), 0) if reset.isdigit() else 0
def upload_batch(paths, expiry_hours=24):
"""Upload up to 20 files in ONE request, retrying when rate limited."""
for attempt in range(MAX_RETRIES + 1):
# Re-open the files on every attempt - a file that was already read can't be re-sent
handles = [open(p, "rb") for p in paths]
try:
files = [("files", (os.path.basename(p), h)) for p, h in zip(paths, handles)]
response = requests.post(
API, files=files, data={"expiryHours": str(expiry_hours)}, timeout=300
)
except (requests.ConnectionError, requests.Timeout):
response = None # network error - back off and try again
finally:
for h in handles:
h.close()
if response is not None and response.ok:
result = response.json()
if not result.get("success"):
raise RuntimeError(result.get("error", "Upload failed"))
return result["files"], quota_wait(response)
# Retry on 429 (rate limited) and temporary gateway errors, fail on anything else
status = response.status_code if response is not None else None
if status is not None and status not in (429, 502, 503, 504):
raise RuntimeError(f"HTTP {status}: {response.text}")
if attempt == MAX_RETRIES:
raise RuntimeError(f"Gave up after {MAX_RETRIES} retries (last status: {status})")
delay = retry_delay(response, attempt)
print(f"HTTP {status} - retrying in {delay:.0f}s")
time.sleep(delay)
def upload_many(paths, expiry_hours=24):
"""Upload any number of files, 20 per request."""
uploaded = []
for i in range(0, len(paths), MAX_FILES_PER_REQUEST):
batch = paths[i:i + MAX_FILES_PER_REQUEST]
files, wait = upload_batch(batch, expiry_hours)
uploaded.extend(files)
print(f"Uploaded {len(uploaded)}/{len(paths)} files")
# Quota used up? Wait for the window to reset instead of earning a 429
if wait > 0 and i + MAX_FILES_PER_REQUEST < len(paths):
print(f"Rate limit reached - waiting {wait:.0f}s")
time.sleep(wait)
return uploaded
if __name__ == "__main__":
for f in upload_many(sys.argv[1:]):
print(f["name"], "->", f["url"])
<?php
// Usage: php upload.php file1.pdf file2.png file3.zip ...
$API = 'https://tempfile.org/api/upload/local';
const MAX_FILES_PER_REQUEST = 20; // the API accepts up to 20 files per request
const MAX_RETRIES = 5;
// Seconds to wait before retrying. Prefer what the server tells us.
function retryDelay(?array $headers, int $attempt): float {
if ($headers !== null) {
if (isset($headers['retry-after']) && ctype_digit($headers['retry-after'])) {
return (float) $headers['retry-after'];
}
// unix timestamp (seconds)
if (isset($headers['x-ratelimit-reset']) && ctype_digit($headers['x-ratelimit-reset'])) {
return (float) max((int) $headers['x-ratelimit-reset'] - time(), 1);
}
}
// No headers (network error): exponential backoff + jitter
return min(2 ** $attempt, 60) + mt_rand() / mt_getrandmax();
}
// If this response used up our last request, seconds until the window resets
function quotaWait(array $headers): float {
if (($headers['x-ratelimit-remaining'] ?? '') !== '0') return 0;
$reset = $headers['x-ratelimit-reset'] ?? '';
return ctype_digit($reset) ? (float) max((int) $reset - time(), 0) : 0;
}
// Upload up to 20 files in ONE request, retrying when rate limited
function uploadBatch(string $api, array $paths, int $expiryHours = 24): array {
for ($attempt = 0; $attempt <= MAX_RETRIES; $attempt++) {
// One CURLFile per file, named files[0], files[1], ... (the API accepts this PHP-style naming)
$fields = ['expiryHours' => (string) $expiryHours];
foreach (array_values($paths) as $i => $path) {
$fields["files[$i]"] = new CURLFile($path, mime_content_type($path) ?: 'application/octet-stream', basename($path));
}
$headers = [];
$curl = curl_init($api);
curl_setopt_array($curl, [
CURLOPT_POST => true,
CURLOPT_POSTFIELDS => $fields,
CURLOPT_RETURNTRANSFER => true,
CURLOPT_TIMEOUT => 300,
// Collect the response headers so we can read X-RateLimit-* and Retry-After
CURLOPT_HEADERFUNCTION => function ($ch, $line) use (&$headers) {
$parts = explode(':', $line, 2);
if (count($parts) === 2) {
$headers[strtolower(trim($parts[0]))] = trim($parts[1]);
}
return strlen($line);
},
]);
$response = curl_exec($curl);
$status = curl_getinfo($curl, CURLINFO_RESPONSE_CODE);
$networkError = $response === false;
curl_close($curl);
if (!$networkError && $status >= 200 && $status < 300) {
$result = json_decode($response, true);
if (!($result['success'] ?? false)) {
throw new RuntimeException($result['error'] ?? 'Upload failed');
}
return [$result['files'], quotaWait($headers)];
}
// Retry on 429 (rate limited), temporary gateway errors and network errors
$retryable = $networkError || in_array($status, [429, 502, 503, 504], true);
if (!$retryable || $attempt === MAX_RETRIES) {
throw new RuntimeException("HTTP $status: " . ($response ?: 'no response'));
}
$delay = retryDelay($networkError ? null : $headers, $attempt);
echo "HTTP $status - retrying in " . ceil($delay) . "s\n";
usleep((int) ($delay * 1000000));
}
}
// Upload any number of files, 20 per request
function uploadMany(string $api, array $paths, int $expiryHours = 24): array {
$uploaded = [];
foreach (array_chunk($paths, MAX_FILES_PER_REQUEST) as $i => $batch) {
[$files, $wait] = uploadBatch($api, $batch, $expiryHours);
$uploaded = array_merge($uploaded, $files);
echo 'Uploaded ' . count($uploaded) . '/' . count($paths) . " files\n";
// Quota used up? Wait for the window to reset instead of earning a 429
$isLast = ($i + 1) * MAX_FILES_PER_REQUEST >= count($paths);
if ($wait > 0 && !$isLast) {
echo 'Rate limit reached - waiting ' . ceil($wait) . "s\n";
usleep((int) ($wait * 1000000));
}
}
return $uploaded;
}
foreach (uploadMany($API, array_slice($argv, 1)) as $f) {
echo $f['name'] . ' -> ' . $f['url'] . "\n";
}
Uploads everything the user picked, 20 files per request, with automatic retry when rate limited.
const sleep = (ms) => new Promise((resolve) => setTimeout(resolve, ms));
// fetch() that waits and retries when rate limited (HTTP 429) or on temporary gateway errors
async function fetchWithBackoff(url, makeOptions, maxRetries = 5) {
for (let attempt = 0; ; attempt++) {
let res = null;
try {
// makeOptions() builds a fresh body each attempt - a request body can only be sent once
res = await fetch(url, makeOptions());
if (res.status !== 429 && ![502, 503, 504].includes(res.status)) return res; // done
} catch (err) {
if (attempt >= maxRetries) throw err; // network error, out of retries
}
if (attempt >= maxRetries) return res; // out of retries - let the caller handle the 429
// Prefer the server's instructions, otherwise exponential backoff with jitter.
// (The API exposes these headers to browsers via CORS; the fallback covers network errors.)
const retryAfter = Number(res?.headers.get('Retry-After')); // seconds
const reset = Number(res?.headers.get('X-RateLimit-Reset')); // unix timestamp (seconds)
const delay = retryAfter > 0 ? retryAfter * 1000
: reset > 0 ? Math.max(reset * 1000 - Date.now(), 1000)
: Math.min(2 ** attempt * 1000, 60000) + Math.random() * 1000;
await sleep(delay);
}
}
// Upload everything the user picked, 20 files per request
async function uploadFiles(fileList, expiryHours = 24) {
const files = Array.from(fileList);
const uploaded = [];
for (let i = 0; i < files.length; i += 20) {
const batch = files.slice(i, i + 20);
const res = await fetchWithBackoff('/api/upload/local', () => {
const formData = new FormData();
batch.forEach((file) => formData.append('files', file));
formData.append('expiryHours', String(expiryHours));
return { method: 'POST', body: formData };
});
const result = await res.json().catch(() => ({}));
if (!res.ok || !result.success) throw new Error(result.error || `HTTP ${res.status}`);
uploaded.push(...result.files);
}
return uploaded; // [{ id, name, size, url, expiryTime }, ...]
}
// Usage with <input type="file" id="picker" multiple>:
// const uploaded = await uploadFiles(document.getElementById('picker').files);
// uploaded.forEach((f) => console.log(f.name, f.url));
POST /api/upload/url accepts a single URL per request, so these run one after another (firing them all at once with Promise.all would trigger 429 errors). Each URL uses one request from your hourly 200. When you have many files, download them yourself and upload them in batches of 20 with /api/upload/local instead. This example reuses fetchWithBackoff from the widget above.
// Import several remote files by URL.
// The URL endpoint takes ONE url per request, so go one at a time (not Promise.all)
// and let fetchWithBackoff (above) handle rate limits.
async function uploadFromUrls(urls, expiryHours = 12) {
const uploaded = [];
for (const url of urls) {
const res = await fetchWithBackoff('/api/upload/url', () => ({
method: 'POST',
headers: { 'Content-Type': 'application/json' },
body: JSON.stringify({ url, expiryHours })
}));
const result = await res.json().catch(() => ({}));
if (res.ok && result.success) {
uploaded.push(result.file.url);
} else {
console.error(`Failed to import ${url}:`, result.error || `HTTP ${res.status}`);
}
}
return uploaded;
}
const links = await uploadFromUrls([
'https://example.com/doc1.pdf',
'https://example.com/doc2.pdf'
]);
success field in responsesX-RateLimit-* headers and, on a 429, wait for Retry-After before retryingAll files uploaded through the API are automatically:
Currently, the API operates on a pull-based model. For file status updates:
exists field to detect deletionexpiryTime for proactive cleanupAPI usage is subject to our Terms of Service. Key points:
Developer Support: [email protected]
Feature Requests: [email protected]
Bug Reports: [email protected]
Response Time: 24-48 hours
Include your use case and sample code when reporting issues for faster resolution.