API Reference

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.

API v1.0 Base URL: https://filepost.dev Auth: X-API-Key header

Quickstart

Sign up, grab your API key, upload a file. The whole flow takes under a minute.

  1. Get your free API key (15 uploads/month instantly, 50/month after email verification).
  2. POST any file to https://filepost.dev/v1/upload with the key in the X-API-Key header.
  3. Take the url from 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);
200 OK
{
  "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.

Keep your key server-side. An API key grants full access to uploads, listings, and deletes on your account. Never commit it to a public repo or ship it in a frontend bundle. If exposed, rotate immediately.

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.

Large uploads: use 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

POST /v1/upload Upload a file and get a public URL

Headers

X-API-Key requiredstringYour API key.

Body (multipart/form-data)

file requiredfileThe file to upload. Free plan: 50 MB max. Starter: 200 MB. Pro: 500 MB.
expires_instringOptional auto-delete period, e.g. 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);
200 OK
{
  "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
}
POST /v1/upload/base64 Upload a base64 file from JSON

Headers

X-API-Key requiredstringYour API key.
Content-Type requiredstringapplication/json

Body (application/json)

filename requiredstringOriginal filename, e.g. report.pdf.
content_typestringMIME type, e.g. application/pdf or text/csv.
data_base64 requiredstringBase64-encoded file bytes. file_base64 or data are also accepted.
expires_instringOptional auto-delete period, e.g. 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"
  }'
200 OK
{
  "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"
}
GET /v1/files List your uploaded files

Query parameters

pageintegerPage number, defaults to 1.
per_pageintegerItems per page, defaults to 50, max 200.
curl https://filepost.dev/v1/files?page=1&per_page=50 \
  -H "X-API-Key: YOUR_API_KEY"
200 OK
{
  "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
}
GET /v1/files/{file_id} Get file details

Path parameters

file_id requiredstringThe file ID returned by the upload endpoint.
curl https://filepost.dev/v1/files/abc123def456 \
  -H "X-API-Key: YOUR_API_KEY"
DELETE /v1/files/{file_id} Delete a file permanently

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"
200 OK
{ "deleted": true, "file_id": "abc123def456" }
POST /v1/files/bulk-delete Delete multiple files in one request

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

X-API-Key requiredstringYour API key.
Content-Type requiredstringapplication/json

Body (application/json)

file_ids requiredarray<string>File IDs to delete. Duplicate IDs are ignored.
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"]
  }'
200 OK
{
  "deleted": true,
  "deleted_count": 2,
  "deleted_file_ids": ["abc123def456", "def456abc123"],
  "not_deleted_file_ids": [],
  "errors": [],
  "db_error": null
}
207 PARTIAL SUCCESS
{
  "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

POST /v1/signup Create an account and get an API key

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)

email requiredstringA real, deliverable email address.
curl -X POST https://filepost.dev/v1/signup \
  -H "Content-Type: application/json" \
  -d '{"email": "you@example.com"}'
200 OK
{
  "message": "Account created",
  "api_key": "fh_xxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxx",
  "plan": "free",
  "email": "you@example.com"
}
GET /v1/account Get account info and current usage
curl https://filepost.dev/v1/account \
  -H "X-API-Key: YOUR_API_KEY"
200 OK
{
  "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
}
POST /v1/rotate-key Generate a new API key and invalidate the old one

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.

POST /v1/intake-links Create a public upload link

Body (application/json)

labelstringHuman-readable name shown to uploaders, e.g. "Client photos".
max_filesintegerCap on uploads through this link. Optional.
max_file_size_mbintegerPer-file size cap in MB.
allowed_typesarrayFile extensions to allow, e.g. ["jpg","pdf"].
expires_in_daysintegerAuto-deactivate after N days. Defaults to 30.
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"]}'
200 OK
{
  "intake_id": "intk_...",
  "token": "3L18hphjnmDB",
  "upload_url": "https://filepost.dev/u/3L18hphjnmDB"
}
GET /v1/intake-links List your active intake links
curl https://filepost.dev/v1/intake-links \
  -H "X-API-Key: YOUR_API_KEY"
POST /v1/intake/{token}/upload Upload a file via an intake link (public, no auth)

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 stateUploadsRate limitMax file sizeStorage
Free, unverified email15 / month10 req/sec50 MB2 GB
Free, verified email50 / month10 req/sec50 MB2 GB
Starter ($9/mo)1,500 / month50 req/sec200 MBUnlimited
Pro ($29/mo)7,500 / month200 req/sec500 MBUnlimited

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

StatusMeaningWhat to do
400Bad request (missing file, invalid email)Check request body and headers.
401Missing or invalid X-API-KeyConfirm the key is set and matches an active account.
403Plan limit reached (uploads or storage)Upgrade plan or wait for monthly reset.
404File not foundVerify file_id belongs to the authenticated account.
413File too large for planFree: 50 MB, Starter: 200 MB, Pro: 500 MB.
422Validation errorResponse body lists the invalid field.
429Rate limit exceededBack off and retry after a few seconds.
500Server errorRetry 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.