FilePost API Documentation
Upload any file, get a permanent public CDN URL. Use multipart uploads or base64 JSON for low-code tools. Curl, Python, and Node.js examples for every call.
Quickstart
Sign up, grab your API key, upload a file. The whole flow takes under a minute.
- Get your free API key (15 uploads/month instantly, 50/month after email verification).
- POST any file to
https://filepost.dev/v1/uploadwith the key in theX-API-Keyheader. - Take the
urlfrom the response. It has no automatic expiry by default.
curl -X POST https://filepost.dev/v1/upload \ -H "X-API-Key: YOUR_API_KEY" \ -F "file=@photo.jpg"
import requests r = requests.post( "https://filepost.dev/v1/upload", headers={"X-API-Key": "YOUR_API_KEY"}, files={"file": open("photo.jpg", "rb")}, ) print(r.json()["url"])
import fs from "node:fs"; const form = new FormData(); form.append("file", new Blob([fs.readFileSync("photo.jpg")]), "photo.jpg"); const r = await fetch("https://filepost.dev/v1/upload", { method: "POST", headers: { "X-API-Key": "YOUR_API_KEY" }, body: form, }); console.log((await r.json()).url);
{
"file_id": "abc123def456",
"url": "https://cdn.filepost.dev/file/filepost/uploads/ab/abc123def456.jpg",
"name": "photo.jpg",
"size": 45321,
"content_type": "image/jpeg"
}
Authentication
Every request except POST /v1/signup requires your API key in the X-API-Key request header. Keys are created on signup and can be rotated from your dashboard or via the rotate endpoint.
Base URL
All endpoints live under:
https://filepost.dev
Uploaded files are served from the CDN at https://cdn.filepost.dev. Both are HTTPS-only with HSTS preload.
https://upload.filepost.dev/v1/upload for paid-plan files over 100 MB. It is the same API and response shape, but it bypasses Cloudflare's proxied upload limit.
Files
Headers
Body (multipart/form-data)
30s, 24h, or 7d.curl -X POST https://filepost.dev/v1/upload \ -H "X-API-Key: YOUR_API_KEY" \ -F "file=@photo.jpg"
import requests r = requests.post( "https://filepost.dev/v1/upload", headers={"X-API-Key": "YOUR_API_KEY"}, files={"file": open("photo.jpg", "rb")}, ) r.raise_for_status() print(r.json()["url"])
import fs from "node:fs"; const form = new FormData(); form.append("file", new Blob([fs.readFileSync("photo.jpg")]), "photo.jpg"); const r = await fetch("https://filepost.dev/v1/upload", { method: "POST", headers: { "X-API-Key": "YOUR_API_KEY" }, body: form, }); if (!r.ok) throw new Error(await r.text()); console.log((await r.json()).url);
{
"file_id": "abc123def456",
"url": "https://cdn.filepost.dev/file/filepost/uploads/ab/abc123def456.jpg",
"name": "photo.jpg",
"size": 45321,
"content_type": "image/jpeg",
"expires_at": null
}
Headers
application/jsonBody (application/json)
report.pdf.application/pdf or text/csv.file_base64 or data are also accepted.30s, 24h, or 7d.curl -X POST https://filepost.dev/v1/upload/base64 \ -H "X-API-Key: YOUR_API_KEY" \ -H "Content-Type: application/json" \ -d '{ "filename": "report.pdf", "content_type": "application/pdf", "data_base64": "JVBERi0xLjQK...", "expires_in": "7d" }'
{
"file_id": "abc123def456",
"url": "https://cdn.filepost.dev/file/filepost/uploads/ab/abc123def456.pdf",
"name": "report.pdf",
"size": 10485760,
"content_type": "application/pdf",
"expires_at": "2026-06-24T12:30:00"
}
Query parameters
curl https://filepost.dev/v1/files?page=1&per_page=50 \
-H "X-API-Key: YOUR_API_KEY"
{
"files": [
{
"file_id": "abc123def456",
"url": "https://cdn.filepost.dev/file/filepost/uploads/ab/abc123def456.jpg",
"name": "photo.jpg",
"size": 45321,
"content_type": "image/jpeg",
"created_at": "2026-04-16T18:22:03Z",
"expires_at": null
}
],
"page": 1,
"per_page": 50,
"total": 1
}
Path parameters
curl https://filepost.dev/v1/files/abc123def456 \
-H "X-API-Key: YOUR_API_KEY"
Removes the file from storage and the CDN URL stops resolving. Cannot be undone.
curl -X DELETE https://filepost.dev/v1/files/abc123def456 \
-H "X-API-Key: YOUR_API_KEY"
{ "deleted": true, "file_id": "abc123def456" }
Starter and Pro only. Deletes each file from storage first, then removes the database row. If storage deletion fails for a file, that file stays listed and the response includes an error for that file.
Headers
application/jsonBody (application/json)
curl -X POST https://filepost.dev/v1/files/bulk-delete \ -H "X-API-Key: YOUR_API_KEY" \ -H "Content-Type: application/json" \ -d '{ "file_ids": ["abc123def456", "def456abc123"] }'
{
"deleted": true,
"deleted_count": 2,
"deleted_file_ids": ["abc123def456", "def456abc123"],
"not_deleted_file_ids": [],
"errors": [],
"db_error": null
}
{
"deleted": false,
"deleted_count": 1,
"deleted_file_ids": ["abc123def456"],
"not_deleted_file_ids": ["def456abc123"],
"errors": [
{ "file_id": "def456abc123", "error": "Storage deletion failed" }
],
"db_error": null
}
Unknown or non-owned file IDs are returned in not_deleted_file_ids. Free-plan users receive 403. If every requested storage deletion fails, the endpoint returns 502 and no database rows are removed.
Account
No auth required. If an account already exists for the email, a magic login link is sent instead of returning a key.
Body (application/json)
curl -X POST https://filepost.dev/v1/signup \ -H "Content-Type: application/json" \ -d '{"email": "you@example.com"}'
{
"message": "Account created",
"api_key": "fh_xxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxx",
"plan": "free",
"email": "you@example.com"
}
curl https://filepost.dev/v1/account \
-H "X-API-Key: YOUR_API_KEY"
{
"email": "you@example.com",
"plan": "free",
"uploads_this_month": 42,
"upload_limit": 50,
"storage_used_mb": 18,
"storage_limit_mb": 2048,
"max_file_size_mb": 50
}
Use when a key may be compromised. The old key stops working as soon as the response returns the new one.
curl -X POST https://filepost.dev/v1/rotate-key \
-H "X-API-Key: YOUR_CURRENT_KEY"
Intake links
Intake links let third parties upload files to your account through a public URL, without an API key. Useful for client onboarding, contractor deliverables, or form submissions.
Body (application/json)
["jpg","pdf"].curl -X POST https://filepost.dev/v1/intake-links \ -H "X-API-Key: YOUR_API_KEY" \ -H "Content-Type: application/json" \ -d '{"label":"Client photos","max_files":10,"allowed_types":["jpg","png","heic"]}'
{
"intake_id": "intk_...",
"token": "3L18hphjnmDB",
"upload_url": "https://filepost.dev/u/3L18hphjnmDB"
}
curl https://filepost.dev/v1/intake-links \
-H "X-API-Key: YOUR_API_KEY"
This endpoint is called by whoever you share the intake URL with. No API key is required, only the token from the link.
curl -X POST https://filepost.dev/v1/intake/3L18hphjnmDB/upload \
-F "file=@deliverable.pdf"
Rate limits
Rate limits are enforced per API key. Hitting a limit returns 429 Too Many Requests.
| Account state | Uploads | Rate limit | Max file size | Storage |
|---|---|---|---|---|
| Free, unverified email | 15 / month | 10 req/sec | 50 MB | 2 GB |
| Free, verified email | 50 / month | 10 req/sec | 50 MB | 2 GB |
| Starter ($9/mo) | 1,500 / month | 50 req/sec | 200 MB | Unlimited |
| Pro ($29/mo) | 7,500 / month | 200 req/sec | 500 MB | Unlimited |
All plans include unlimited bandwidth, HTTPS, Cloudflare CDN delivery, and permanent URLs. For Starter and Pro uploads over 100 MB, send the same multipart request to https://upload.filepost.dev/v1/upload.
Error codes
| Status | Meaning | What to do |
|---|---|---|
400 | Bad request (missing file, invalid email) | Check request body and headers. |
401 | Missing or invalid X-API-Key | Confirm the key is set and matches an active account. |
403 | Plan limit reached (uploads or storage) | Upgrade plan or wait for monthly reset. |
404 | File not found | Verify file_id belongs to the authenticated account. |
413 | File too large for plan | Free: 50 MB, Starter: 200 MB, Pro: 500 MB. |
422 | Validation error | Response body lists the invalid field. |
429 | Rate limit exceeded | Back off and retry after a few seconds. |
500 | Server error | Retry with exponential backoff. Email support@filepost.dev if it persists. |
All error responses use the same JSON shape:
{ "detail": "Human-readable error message" }
OpenAPI spec
The raw OpenAPI 3.1 schema is available at /openapi.json. Point your codegen (openapi-generator, Orval, Kiota) at it to generate a typed client in any language.
If you prefer an interactive Swagger UI for testing endpoints manually, we still host it at /swagger.
Ready to ship?
Grab a free API key and make your first upload in under a minute.