FilePost API Documentation
Follow one file from upload to deletion, then use the same verified requests in cURL, JavaScript, Python, or PHP.
Quickstart
FilePost turns a file into a public CDN URL. This guide follows one realistic file through its complete lifecycle so you can see how the endpoints fit together.
An invoice sent from your application
Your backend generates invoice-2026-1048.pdf, uploads it to
FilePost, and places the returned URL in a customer email. You then inspect
it, choose whether its name appears in the URL, list it, and delete it when
your retention period ends.
- File
- invoice-2026-1048.pdf
- Content type
- application/pdf
- Visibility
- public
Before running an example, save your key in an environment variable:
export FILEPOST_API_KEY="fh_your_api_key"// macOS/Linux:
// export FILEPOST_API_KEY="fh_your_api_key"
//
// Windows PowerShell:
// $env:FILEPOST_API_KEY="fh_your_api_key"# macOS/Linux:
# export FILEPOST_API_KEY="fh_your_api_key"
#
# Windows PowerShell:
# $env:FILEPOST_API_KEY="fh_your_api_key"// macOS/Linux:
// export FILEPOST_API_KEY="fh_your_api_key"
//
// Windows PowerShell:
// $env:FILEPOST_API_KEY="fh_your_api_key"Keep API keys on your server. Do not embed them in browser JavaScript, public repositories, mobile applications, or frontend bundles.
Authentication
Every authenticated request sends the API key in this header:
X-API-Key: fh_your_api_key
Upload and account URLs are public only after FilePost returns them. API management endpoints still require your key. A file URL can be opened by anyone who has it, regardless of whether its filename is visible in that URL.
Base URL
Use this base URL for normal API requests:
https://filepost.dev
Uploads go to https://upload.filepost.dev/v1/upload, /v1/upload/base64, or
/v1/upload/url for every plan and file size up to your plan limit (500 MB on
Pro). Uploaded files are served from https://cdn.filepost.dev.
Files
Use the file endpoints to upload, inspect, list, change filename visibility,
and delete files. The stable file_id returned at upload is used for later
management calls.
Step 1 of 6 Upload the invoice
POST/v1/uploadUpload a file
Send a multipart/form-data request. The file part supplies the stored and
downloaded filename.
| Field | Type | Required | Description |
|---|---|---|---|
file |
file | yes | File bytes and filename. |
expires_in |
string | no | Auto-delete period such as 30s, 15m, 24h, or 7d. |
filename_mode |
string | no | download keeps the name out of the URL. public includes it. The account default is used when omitted. |
The invoice is safe to show in the URL because its name does not contain a
customer name or other private information. We therefore choose public.
curl -X POST https://upload.filepost.dev/v1/upload \
-H "X-API-Key: $FILEPOST_API_KEY" \
-F "file=@invoice-2026-1048.pdf;type=application/pdf" \
-F "filename_mode=public" \
-F "expires_in=30d"import { readFile } from "node:fs/promises";
const bytes = await readFile("invoice-2026-1048.pdf");
const form = new FormData();
form.append(
"file",
new Blob([bytes], { type: "application/pdf" }),
"invoice-2026-1048.pdf"
);
form.append("filename_mode", "public");
form.append("expires_in", "30d");
const response = await fetch("https://upload.filepost.dev/v1/upload", {
method: "POST",
headers: { "X-API-Key": process.env.FILEPOST_API_KEY },
body: form
});
if (!response.ok) throw new Error(await response.text());
const uploaded = await response.json();
console.log(uploaded.url);import os
import requests
with open("invoice-2026-1048.pdf", "rb") as invoice:
response = requests.post(
"https://upload.filepost.dev/v1/upload",
headers={"X-API-Key": os.environ["FILEPOST_API_KEY"]},
files={"file": (
"invoice-2026-1048.pdf",
invoice,
"application/pdf",
)},
data={"filename_mode": "public", "expires_in": "30d"},
timeout=60,
)
response.raise_for_status()
uploaded = response.json()
print(uploaded["url"])<?php
$file = new CURLFile(
"invoice-2026-1048.pdf",
"application/pdf",
"invoice-2026-1048.pdf"
);
$ch = curl_init("https://upload.filepost.dev/v1/upload");
curl_setopt_array($ch, [
CURLOPT_POST => true,
CURLOPT_HTTPHEADER => [
"X-API-Key: " . getenv("FILEPOST_API_KEY"),
],
CURLOPT_POSTFIELDS => [
"file" => $file,
"filename_mode" => "public",
"expires_in" => "30d",
],
CURLOPT_RETURNTRANSFER => true,
]);
$body = curl_exec($ch);
if ($body === false) throw new Exception(curl_error($ch));
$status = curl_getinfo($ch, CURLINFO_RESPONSE_CODE);
curl_close($ch);
if ($status >= 400) throw new Exception($body);
$uploaded = json_decode($body, true, 512, JSON_THROW_ON_ERROR);
echo $uploaded["url"];Response
{
"file_id": "a84f2c6d921a4f53a1e7c8d9b04e6210",
"url": "https://cdn.filepost.dev/file/filepost/uploads/a8/a84f2c6d921a4f53a1e7c8d9b04e6210/invoice-2026-1048.pdf",
"name": "invoice-2026-1048.pdf",
"size": 248193,
"content_type": "application/pdf",
"expires_at": "2026-08-28T10:30:00",
"provisional": false,
"filename_mode": "public"
}
Save both file_id and url. The URL is what you send to the customer. The
ID is what your application uses for later management calls.
An unverified Free account can create one file up to 10 MB. That response has
provisional: true and an expires_at 24 hours in the future. Verify the
account email before that time to make the file permanent and unlock 50 uploads
per month with files up to 50 MB. Custom expires_in values and intake links
require verification.
Once a verified free account has used 60% of its monthly allowance, successful
multipart, Base64, and URL uploads also include an additive quota object:
{
"quota": {
"code": "monthly_upload_limit_approaching",
"message": "You have 20 monthly uploads remaining. Upgrade before automated uploads begin returning HTTP 403.",
"used": 30,
"limit": 50,
"remaining": 20,
"reset_at": "2026-09-01T00:00:00Z",
"upgrade": {
"plan": "starter",
"url": "https://filepost.dev/#pricing",
"promotion_code": "PEERPUSH",
"offer": "30% off Starter for the first 3 months",
"uploads_per_month": 1500
}
}
}
The upload still succeeded. Treat this object as an early warning and keep
processing the returned file URL. On the final successful slot, code becomes
monthly_upload_limit_reached, remaining is 0, and subsequent uploads
return HTTP 403 until the allowance resets or the account upgrades.
After three successful uploads or the first confirmed CDN delivery, the upload
response includes a no-card Lite-trial action. API clients can execute the
returned action directly; opening the dashboard is optional:
{
"conversion": {
"code": "lite_trial_available",
"message": "Your workflow qualifies for a free seven-day Lite trial. No card required; it returns to Free automatically.",
"action_url": "https://filepost.dev/dashboard",
"action": {
"label": "Start free 7-day Lite trial",
"method": "POST",
"url": "https://filepost.dev/v1/account/lite-trial?offer_kind=activated_api",
"auth_header": "X-API-Key"
},
"lite_trial": {
"eligible": true,
"active": false,
"expired": false,
"days": 7,
"activation_reason": "three_uploads",
"started_at": null,
"ends_at": null
}
}
}
Use the same API key from the upload request in the action's auth_header.
The trial returns to Free automatically and does not create a Stripe checkout.
POST/v1/upload/base64Upload Base64 data
Use this endpoint when your automation or application produces Base64 text instead of a multipart file. The URL and headers stay the same as other API calls. Only the JSON body differs.
| Field | Type | Required | Description |
|---|---|---|---|
filename |
string | yes | Stored and downloaded filename, including its extension. |
content_type |
string | no | MIME type. Defaults to application/octet-stream. |
data_base64 |
string | yes | Base64 file bytes. file_base64 and data are accepted aliases. |
expires_in |
string | no | Auto-delete period such as 24h or 30d. |
filename_mode |
string | no | download or public. |
BASE64_DATA=$(base64 < invoice-2026-1048.pdf | tr -d '\n')
curl -X POST https://upload.filepost.dev/v1/upload/base64 \
-H "X-API-Key: $FILEPOST_API_KEY" \
-H "Content-Type: application/json" \
-d "{
\"filename\": \"invoice-2026-1048.pdf\",
\"content_type\": \"application/pdf\",
\"data_base64\": \"$BASE64_DATA\",
\"expires_in\": \"30d\",
\"filename_mode\": \"public\"
}"import { readFile } from "node:fs/promises";
const bytes = await readFile("invoice-2026-1048.pdf");
const response = await fetch("https://upload.filepost.dev/v1/upload/base64", {
method: "POST",
headers: {
"X-API-Key": process.env.FILEPOST_API_KEY,
"Content-Type": "application/json"
},
body: JSON.stringify({
filename: "invoice-2026-1048.pdf",
content_type: "application/pdf",
data_base64: bytes.toString("base64"),
expires_in: "30d",
filename_mode: "public"
})
});
if (!response.ok) throw new Error(await response.text());
const uploaded = await response.json();
console.log(uploaded.url);import base64
import os
import requests
with open("invoice-2026-1048.pdf", "rb") as invoice:
encoded = base64.b64encode(invoice.read()).decode("ascii")
response = requests.post(
"https://upload.filepost.dev/v1/upload/base64",
headers={
"X-API-Key": os.environ["FILEPOST_API_KEY"],
"Content-Type": "application/json",
},
json={
"filename": "invoice-2026-1048.pdf",
"content_type": "application/pdf",
"data_base64": encoded,
"expires_in": "30d",
"filename_mode": "public",
},
timeout=60,
)
response.raise_for_status()
uploaded = response.json()
print(uploaded["url"])<?php
$payload = [
"filename" => "invoice-2026-1048.pdf",
"content_type" => "application/pdf",
"data_base64" => base64_encode(
file_get_contents("invoice-2026-1048.pdf")
),
"expires_in" => "30d",
"filename_mode" => "public",
];
$ch = curl_init("https://upload.filepost.dev/v1/upload/base64");
curl_setopt_array($ch, [
CURLOPT_POST => true,
CURLOPT_HTTPHEADER => [
"X-API-Key: " . getenv("FILEPOST_API_KEY"),
"Content-Type: application/json",
],
CURLOPT_POSTFIELDS => json_encode($payload, JSON_THROW_ON_ERROR),
CURLOPT_RETURNTRANSFER => true,
]);
$body = curl_exec($ch);
if ($body === false) throw new Exception(curl_error($ch));
$status = curl_getinfo($ch, CURLINFO_RESPONSE_CODE);
curl_close($ch);
if ($status >= 400) throw new Exception($body);
$uploaded = json_decode($body, true, 512, JSON_THROW_ON_ERROR);
echo $uploaded["url"];The response is identical to the multipart upload response.
POST/v1/upload/urlCopy a public URL
Use this endpoint when an automation gives you a temporary or expiring file URL instead of raw bytes. FilePost downloads the source and returns the same permanent public URL response as a multipart upload. The account must be email verified.
| Field | Type | Required | Description |
|---|---|---|---|
url |
string | yes | Public http or https source URL. Ports other than 80 and 443 are rejected. |
filename |
string | no | Stored filename. Defaults to the final source URL path. |
content_type |
string | no | MIME type override. Defaults to the source response header. |
expires_in |
string | no | Auto-delete period such as 24h or 30d. |
filename_mode |
string | no | download or public. |
The source must be downloadable without cookies or custom authorization headers. Each redirect is checked again. Private, loopback, link-local, reserved, and mixed public/private DNS destinations are rejected. URL uploads have a 60-second total fetch timeout, a maximum of three redirects, a separate 30-per-minute limit, bounded concurrent fetch capacity, and the normal plan quota, storage, and file-size limits.
curl -X POST https://upload.filepost.dev/v1/upload/url \
-H "X-API-Key: $FILEPOST_API_KEY" \
-H "Content-Type: application/json" \
-d '{
"url": "https://example.com/temporary/invoice.pdf",
"filename": "invoice-2026-1048.pdf",
"expires_in": "30d",
"filename_mode": "public"
}'const response = await fetch("https://upload.filepost.dev/v1/upload/url", {
method: "POST",
headers: {
"X-API-Key": process.env.FILEPOST_API_KEY,
"Content-Type": "application/json"
},
body: JSON.stringify({
url: "https://example.com/temporary/invoice.pdf",
filename: "invoice-2026-1048.pdf",
expires_in: "30d",
filename_mode: "public"
})
});
if (!response.ok) throw new Error(await response.text());
console.log((await response.json()).url);import os
import requests
response = requests.post(
"https://upload.filepost.dev/v1/upload/url",
headers={"X-API-Key": os.environ["FILEPOST_API_KEY"]},
json={
"url": "https://example.com/temporary/invoice.pdf",
"filename": "invoice-2026-1048.pdf",
"expires_in": "30d",
"filename_mode": "public",
},
timeout=75,
)
response.raise_for_status()
print(response.json()["url"])<?php
$payload = [
"url" => "https://example.com/temporary/invoice.pdf",
"filename" => "invoice-2026-1048.pdf",
"expires_in" => "30d",
"filename_mode" => "public",
];
$ch = curl_init("https://upload.filepost.dev/v1/upload/url");
curl_setopt_array($ch, [
CURLOPT_POST => true,
CURLOPT_HTTPHEADER => [
"X-API-Key: " . getenv("FILEPOST_API_KEY"),
"Content-Type: application/json",
],
CURLOPT_POSTFIELDS => json_encode($payload, JSON_THROW_ON_ERROR),
CURLOPT_RETURNTRANSFER => true,
]);
$body = curl_exec($ch);
if ($body === false) throw new Exception(curl_error($ch));
$status = curl_getinfo($ch, CURLINFO_RESPONSE_CODE);
curl_close($ch);
if ($status >= 400) throw new Exception($body);
echo json_decode($body, true, 512, JSON_THROW_ON_ERROR)["url"];The remote file is downloaded before it is written to FilePost storage. A failed source request does not consume the monthly upload slot.
Step 2 of 6 Put the returned URL in the customer email
The url is already a public HTTPS URL. Your email system can use it directly:
Your invoice is ready:
https://cdn.filepost.dev/file/filepost/uploads/a8/a84f2c6d921a4f53a1e7c8d9b04e6210/invoice-2026-1048.pdf
No FilePost authentication is required when the customer opens the file URL. Do not send your API key to the customer.
Step 3 of 6 Inspect the stored file
GET/v1/files/{file_id}Get file metadata
Use the stable file_id from the upload response.
curl https://filepost.dev/v1/files/a84f2c6d921a4f53a1e7c8d9b04e6210 \
-H "X-API-Key: $FILEPOST_API_KEY"const fileId = "a84f2c6d921a4f53a1e7c8d9b04e6210";
const response = await fetch(
`https://filepost.dev/v1/files/${fileId}`,
{ headers: { "X-API-Key": process.env.FILEPOST_API_KEY } }
);
if (!response.ok) throw new Error(await response.text());
const file = await response.json();
console.log(file);import os
import requests
file_id = "a84f2c6d921a4f53a1e7c8d9b04e6210"
response = requests.get(
f"https://filepost.dev/v1/files/{file_id}",
headers={"X-API-Key": os.environ["FILEPOST_API_KEY"]},
timeout=30,
)
response.raise_for_status()
print(response.json())<?php
$fileId = "a84f2c6d921a4f53a1e7c8d9b04e6210";
$ch = curl_init("https://filepost.dev/v1/files/" . $fileId);
curl_setopt_array($ch, [
CURLOPT_HTTPHEADER => [
"X-API-Key: " . getenv("FILEPOST_API_KEY"),
],
CURLOPT_RETURNTRANSFER => true,
]);
$body = curl_exec($ch);
if ($body === false) throw new Exception(curl_error($ch));
$status = curl_getinfo($ch, CURLINFO_RESPONSE_CODE);
curl_close($ch);
if ($status >= 400) throw new Exception($body);
print_r(json_decode($body, true, 512, JSON_THROW_ON_ERROR));Response
{
"file_id": "a84f2c6d921a4f53a1e7c8d9b04e6210",
"url": "https://cdn.filepost.dev/file/filepost/uploads/a8/a84f2c6d921a4f53a1e7c8d9b04e6210/invoice-2026-1048.pdf",
"name": "invoice-2026-1048.pdf",
"size": 248193,
"content_type": "application/pdf",
"created_at": "2026-07-29T10:30:00",
"expires_at": "2026-08-28T10:30:00",
"filename_mode": "public"
}
Step 4 of 6 Choose whether the filename appears in the URL
PATCH/v1/files/{file_id}Change filename visibility
This changes only the canonical URL:
publicincludes the existing filename in the URL.downloadkeeps the filename out of the URL while downloads still use the correct filename.
It does not rename or re-upload the file. The bytes, file_id, expiration,
download filename, and upload usage remain unchanged.
curl -X PATCH \
https://filepost.dev/v1/files/a84f2c6d921a4f53a1e7c8d9b04e6210 \
-H "X-API-Key: $FILEPOST_API_KEY" \
-H "Content-Type: application/json" \
-d '{"filename_mode":"download"}'const fileId = "a84f2c6d921a4f53a1e7c8d9b04e6210";
const response = await fetch(
`https://filepost.dev/v1/files/${fileId}`,
{
method: "PATCH",
headers: {
"X-API-Key": process.env.FILEPOST_API_KEY,
"Content-Type": "application/json"
},
body: JSON.stringify({ filename_mode: "download" })
}
);
if (!response.ok) throw new Error(await response.text());
const updated = await response.json();
console.log(updated.url);import os
import requests
file_id = "a84f2c6d921a4f53a1e7c8d9b04e6210"
response = requests.patch(
f"https://filepost.dev/v1/files/{file_id}",
headers={"X-API-Key": os.environ["FILEPOST_API_KEY"]},
json={"filename_mode": "download"},
timeout=30,
)
response.raise_for_status()
print(response.json()["url"])<?php
$fileId = "a84f2c6d921a4f53a1e7c8d9b04e6210";
$ch = curl_init("https://filepost.dev/v1/files/" . $fileId);
curl_setopt_array($ch, [
CURLOPT_CUSTOMREQUEST => "PATCH",
CURLOPT_HTTPHEADER => [
"X-API-Key: " . getenv("FILEPOST_API_KEY"),
"Content-Type: application/json",
],
CURLOPT_POSTFIELDS => json_encode(
["filename_mode" => "download"],
JSON_THROW_ON_ERROR
),
CURLOPT_RETURNTRANSFER => true,
]);
$body = curl_exec($ch);
if ($body === false) throw new Exception(curl_error($ch));
$status = curl_getinfo($ch, CURLINFO_RESPONSE_CODE);
curl_close($ch);
if ($status >= 400) throw new Exception($body);
$updated = json_decode($body, true, 512, JSON_THROW_ON_ERROR);
echo $updated["url"];Response
{
"file_id": "a84f2c6d921a4f53a1e7c8d9b04e6210",
"url": "https://cdn.filepost.dev/file/filepost/uploads/a8/a84f2c6d921a4f53a1e7c8d9b04e6210",
"name": "invoice-2026-1048.pdf",
"size": 248193,
"content_type": "application/pdf",
"created_at": "2026-07-29T10:30:00",
"expires_at": "2026-08-28T10:30:00",
"filename_mode": "download",
"changed": true,
"previous_url": "https://cdn.filepost.dev/file/filepost/uploads/a8/a84f2c6d921a4f53a1e7c8d9b04e6210/invoice-2026-1048.pdf",
"previous_url_preserved": false
}
Hiding the filename revokes the previous filename-visible URL. Showing it preserves the previous opaque URL so existing integrations continue to work. Browser history, logs, analytics, and copies outside FilePost cannot be erased.
Step 5 of 6 Find the invoice in your file list
GET/v1/filesList files
Results are newest first. page starts at 1 and per_page can be between 1
and 100.
curl "https://filepost.dev/v1/files?page=1&per_page=50" \
-H "X-API-Key: $FILEPOST_API_KEY"const query = new URLSearchParams({ page: "1", per_page: "50" });
const response = await fetch(
`https://filepost.dev/v1/files?${query}`,
{ headers: { "X-API-Key": process.env.FILEPOST_API_KEY } }
);
if (!response.ok) throw new Error(await response.text());
const page = await response.json();
console.log(page.files);import os
import requests
response = requests.get(
"https://filepost.dev/v1/files",
headers={"X-API-Key": os.environ["FILEPOST_API_KEY"]},
params={"page": 1, "per_page": 50},
timeout=30,
)
response.raise_for_status()
print(response.json()["files"])<?php
$query = http_build_query(["page" => 1, "per_page" => 50]);
$ch = curl_init("https://filepost.dev/v1/files?" . $query);
curl_setopt_array($ch, [
CURLOPT_HTTPHEADER => [
"X-API-Key: " . getenv("FILEPOST_API_KEY"),
],
CURLOPT_RETURNTRANSFER => true,
]);
$body = curl_exec($ch);
if ($body === false) throw new Exception(curl_error($ch));
$status = curl_getinfo($ch, CURLINFO_RESPONSE_CODE);
curl_close($ch);
if ($status >= 400) throw new Exception($body);
$page = json_decode($body, true, 512, JSON_THROW_ON_ERROR);
print_r($page["files"]);Response
{
"total": 1,
"page": 1,
"per_page": 50,
"files": [
{
"file_id": "a84f2c6d921a4f53a1e7c8d9b04e6210",
"url": "https://cdn.filepost.dev/file/filepost/uploads/a8/a84f2c6d921a4f53a1e7c8d9b04e6210",
"name": "invoice-2026-1048.pdf",
"size": 248193,
"content_type": "application/pdf",
"created_at": "2026-07-29T10:30:00",
"expires_at": "2026-08-28T10:30:00",
"filename_mode": "download"
}
]
}
Step 6 of 6 Delete the invoice when retention ends
DELETE/v1/files/{file_id}Delete a file
Deletion removes the database record and stored object. The public URL stops working. This action cannot be undone.
curl -X DELETE \
https://filepost.dev/v1/files/a84f2c6d921a4f53a1e7c8d9b04e6210 \
-H "X-API-Key: $FILEPOST_API_KEY"const fileId = "a84f2c6d921a4f53a1e7c8d9b04e6210";
const response = await fetch(
`https://filepost.dev/v1/files/${fileId}`,
{
method: "DELETE",
headers: { "X-API-Key": process.env.FILEPOST_API_KEY }
}
);
if (!response.ok) throw new Error(await response.text());
console.log(await response.json());import os
import requests
file_id = "a84f2c6d921a4f53a1e7c8d9b04e6210"
response = requests.delete(
f"https://filepost.dev/v1/files/{file_id}",
headers={"X-API-Key": os.environ["FILEPOST_API_KEY"]},
timeout=30,
)
response.raise_for_status()
print(response.json())<?php
$fileId = "a84f2c6d921a4f53a1e7c8d9b04e6210";
$ch = curl_init("https://filepost.dev/v1/files/" . $fileId);
curl_setopt_array($ch, [
CURLOPT_CUSTOMREQUEST => "DELETE",
CURLOPT_HTTPHEADER => [
"X-API-Key: " . getenv("FILEPOST_API_KEY"),
],
CURLOPT_RETURNTRANSFER => true,
]);
$body = curl_exec($ch);
if ($body === false) throw new Exception(curl_error($ch));
$status = curl_getinfo($ch, CURLINFO_RESPONSE_CODE);
curl_close($ch);
if ($status >= 400) throw new Exception($body);
print_r(json_decode($body, true, 512, JSON_THROW_ON_ERROR));Response
{
"deleted": true,
"file_id": "a84f2c6d921a4f53a1e7c8d9b04e6210"
}
The invoice lifecycle is complete. In a production workflow, store file_id,
url, and expires_at alongside your own invoice record.
POST/v1/files/bulk-deleteDelete multiple files
Starter and Pro accounts can delete up to 100 files per request.
curl -X POST https://filepost.dev/v1/files/bulk-delete \
-H "X-API-Key: $FILEPOST_API_KEY" \
-H "Content-Type: application/json" \
-d '{"file_ids":["a84f2c6d921a4f53a1e7c8d9b04e6210","b10e7fd581ef4c5c9822868358684b63"]}'const response = await fetch(
"https://filepost.dev/v1/files/bulk-delete",
{
method: "POST",
headers: {
"X-API-Key": process.env.FILEPOST_API_KEY,
"Content-Type": "application/json"
},
body: JSON.stringify({
file_ids: [
"a84f2c6d921a4f53a1e7c8d9b04e6210",
"b10e7fd581ef4c5c9822868358684b63"
]
})
}
);
if (!response.ok) throw new Error(await response.text());
console.log(await response.json());import os
import requests
response = requests.post(
"https://filepost.dev/v1/files/bulk-delete",
headers={"X-API-Key": os.environ["FILEPOST_API_KEY"]},
json={"file_ids": [
"a84f2c6d921a4f53a1e7c8d9b04e6210",
"b10e7fd581ef4c5c9822868358684b63",
]},
timeout=30,
)
response.raise_for_status()
print(response.json())<?php
$payload = ["file_ids" => [
"a84f2c6d921a4f53a1e7c8d9b04e6210",
"b10e7fd581ef4c5c9822868358684b63",
]];
$ch = curl_init("https://filepost.dev/v1/files/bulk-delete");
curl_setopt_array($ch, [
CURLOPT_POST => true,
CURLOPT_HTTPHEADER => [
"X-API-Key: " . getenv("FILEPOST_API_KEY"),
"Content-Type: application/json",
],
CURLOPT_POSTFIELDS => json_encode($payload, JSON_THROW_ON_ERROR),
CURLOPT_RETURNTRANSFER => true,
]);
$body = curl_exec($ch);
if ($body === false) throw new Exception(curl_error($ch));
$status = curl_getinfo($ch, CURLINFO_RESPONSE_CODE);
curl_close($ch);
if ($status >= 400) throw new Exception($body);
print_r(json_decode($body, true, 512, JSON_THROW_ON_ERROR));Account
POST/v1/signupCreate an account
Create a free account. New email addresses receive an API key. If the email already belongs to an account, FilePost sends a secure login link instead of revealing its key.
curl -X POST https://filepost.dev/v1/signup \
-H "Content-Type: application/json" \
-d '{"email":"developer@example.com"}'const response = await fetch("https://filepost.dev/v1/signup", {
method: "POST",
headers: { "Content-Type": "application/json" },
body: JSON.stringify({ email: "developer@example.com" })
});
if (!response.ok) throw new Error(await response.text());
console.log(await response.json());import requests
response = requests.post(
"https://filepost.dev/v1/signup",
json={"email": "developer@example.com"},
timeout=30,
)
response.raise_for_status()
print(response.json())<?php
$ch = curl_init("https://filepost.dev/v1/signup");
curl_setopt_array($ch, [
CURLOPT_POST => true,
CURLOPT_HTTPHEADER => ["Content-Type: application/json"],
CURLOPT_POSTFIELDS => json_encode(
["email" => "developer@example.com"],
JSON_THROW_ON_ERROR
),
CURLOPT_RETURNTRANSFER => true,
]);
$body = curl_exec($ch);
if ($body === false) throw new Exception(curl_error($ch));
$status = curl_getinfo($ch, CURLINFO_RESPONSE_CODE);
curl_close($ch);
if ($status >= 400) throw new Exception($body);
print_r(json_decode($body, true, 512, JSON_THROW_ON_ERROR));GET/v1/accountRead account usage
Returns the plan, email verification state, default filename mode, monthly usage, and current limits.
curl https://filepost.dev/v1/account \
-H "X-API-Key: $FILEPOST_API_KEY"const response = await fetch("https://filepost.dev/v1/account", {
headers: { "X-API-Key": process.env.FILEPOST_API_KEY }
});
if (!response.ok) throw new Error(await response.text());
console.log(await response.json());import os
import requests
response = requests.get(
"https://filepost.dev/v1/account",
headers={"X-API-Key": os.environ["FILEPOST_API_KEY"]},
timeout=30,
)
response.raise_for_status()
print(response.json())<?php
$ch = curl_init("https://filepost.dev/v1/account");
curl_setopt_array($ch, [
CURLOPT_HTTPHEADER => [
"X-API-Key: " . getenv("FILEPOST_API_KEY"),
],
CURLOPT_RETURNTRANSFER => true,
]);
$body = curl_exec($ch);
if ($body === false) throw new Exception(curl_error($ch));
$status = curl_getinfo($ch, CURLINFO_RESPONSE_CODE);
curl_close($ch);
if ($status >= 400) throw new Exception($body);
print_r(json_decode($body, true, 512, JSON_THROW_ON_ERROR));When the account is eligible, lite_trial.start_action contains the same
copy-ready authenticated POST instruction returned after an activating upload.
POST/v1/account/lite-trialStart the earned Lite trial
Start the seven-day Lite trial directly from your application. The account must be verified and must have completed three uploads or delivered its first CDN URL. No card is collected. The request is authenticated with the same API key as every other account request.
curl -X POST \
"https://filepost.dev/v1/account/lite-trial?offer_kind=activated_api" \
-H "X-API-Key: $FILEPOST_API_KEY"const response = await fetch(
"https://filepost.dev/v1/account/lite-trial?offer_kind=activated_api",
{
method: "POST",
headers: { "X-API-Key": process.env.FILEPOST_API_KEY }
}
);
if (!response.ok) throw new Error(await response.text());
const trial = await response.json();
console.log(trial.lite_trial.ends_at);import os
import requests
response = requests.post(
"https://filepost.dev/v1/account/lite-trial",
headers={"X-API-Key": os.environ["FILEPOST_API_KEY"]},
params={"offer_kind": "activated_api"},
timeout=30,
)
response.raise_for_status()
trial = response.json()
print(trial["lite_trial"]["ends_at"])<?php
$ch = curl_init(
"https://filepost.dev/v1/account/lite-trial?offer_kind=activated_api"
);
curl_setopt_array($ch, [
CURLOPT_POST => true,
CURLOPT_HTTPHEADER => [
"X-API-Key: " . getenv("FILEPOST_API_KEY"),
],
CURLOPT_RETURNTRANSFER => true,
]);
$body = curl_exec($ch);
if ($body === false) throw new Exception(curl_error($ch));
$status = curl_getinfo($ch, CURLINFO_RESPONSE_CODE);
curl_close($ch);
if ($status >= 400) throw new Exception($body);
$trial = json_decode($body, true, 512, JSON_THROW_ON_ERROR);
echo $trial["lite_trial"]["ends_at"];Response
{
"started": true,
"message": "Your seven-day Lite trial is active. No card was required.",
"lite_trial": {
"eligible": false,
"active": true,
"expired": false,
"days": 7,
"activation_reason": "three_uploads",
"started_at": "2026-08-22T10:00:00+00:00",
"ends_at": "2026-08-29T10:00:00+00:00"
}
}
Calling the endpoint while the trial is already active is safe and returns
started: false. An ineligible account receives HTTP 403; an account that has
already used and finished its one-time trial receives HTTP 409.
POST/v1/account/filename-modeSet the upload default
This changes the default for future uploads. A filename_mode supplied during
an upload overrides the account default for that upload only.
curl -X POST https://filepost.dev/v1/account/filename-mode \
-H "X-API-Key: $FILEPOST_API_KEY" \
-H "Content-Type: application/json" \
-d '{"filename_mode":"download"}'const response = await fetch(
"https://filepost.dev/v1/account/filename-mode",
{
method: "POST",
headers: {
"X-API-Key": process.env.FILEPOST_API_KEY,
"Content-Type": "application/json"
},
body: JSON.stringify({ filename_mode: "download" })
}
);
if (!response.ok) throw new Error(await response.text());
console.log(await response.json());import os
import requests
response = requests.post(
"https://filepost.dev/v1/account/filename-mode",
headers={"X-API-Key": os.environ["FILEPOST_API_KEY"]},
json={"filename_mode": "download"},
timeout=30,
)
response.raise_for_status()
print(response.json())<?php
$ch = curl_init("https://filepost.dev/v1/account/filename-mode");
curl_setopt_array($ch, [
CURLOPT_POST => true,
CURLOPT_HTTPHEADER => [
"X-API-Key: " . getenv("FILEPOST_API_KEY"),
"Content-Type: application/json",
],
CURLOPT_POSTFIELDS => '{"filename_mode":"download"}',
CURLOPT_RETURNTRANSFER => true,
]);
$body = curl_exec($ch);
if ($body === false) throw new Exception(curl_error($ch));
$status = curl_getinfo($ch, CURLINFO_RESPONSE_CODE);
curl_close($ch);
if ($status >= 400) throw new Exception($body);
print_r(json_decode($body, true, 512, JSON_THROW_ON_ERROR));POST/v1/rotate-keyRotate the API key
Rotation immediately invalidates the old key. Store the returned key before making another API request.
curl -X POST https://filepost.dev/v1/rotate-key \
-H "X-API-Key: $FILEPOST_API_KEY"const response = await fetch("https://filepost.dev/v1/rotate-key", {
method: "POST",
headers: { "X-API-Key": process.env.FILEPOST_API_KEY }
});
if (!response.ok) throw new Error(await response.text());
const result = await response.json();
console.log(result.api_key);import os
import requests
response = requests.post(
"https://filepost.dev/v1/rotate-key",
headers={"X-API-Key": os.environ["FILEPOST_API_KEY"]},
timeout=30,
)
response.raise_for_status()
print(response.json()["api_key"])<?php
$ch = curl_init("https://filepost.dev/v1/rotate-key");
curl_setopt_array($ch, [
CURLOPT_POST => true,
CURLOPT_HTTPHEADER => [
"X-API-Key: " . getenv("FILEPOST_API_KEY"),
],
CURLOPT_RETURNTRANSFER => true,
]);
$body = curl_exec($ch);
if ($body === false) throw new Exception(curl_error($ch));
$status = curl_getinfo($ch, CURLINFO_RESPONSE_CODE);
curl_close($ch);
if ($status >= 400) throw new Exception($body);
$result = json_decode($body, true, 512, JSON_THROW_ON_ERROR);
echo $result["api_key"];Intake links
An intake link lets another person upload to your account without receiving
your API key. Share the returned upload_url, not the authenticated management
endpoint.
POST/v1/intake-linksCreate an intake link
curl -X POST https://filepost.dev/v1/intake-links \
-H "X-API-Key: $FILEPOST_API_KEY" \
-H "Content-Type: application/json" \
-d '{
"label":"Accounts payable",
"allowed_types":["pdf"],
"max_file_size_mb":20,
"max_files":10,
"expires_in":"7d"
}'const response = await fetch("https://filepost.dev/v1/intake-links", {
method: "POST",
headers: {
"X-API-Key": process.env.FILEPOST_API_KEY,
"Content-Type": "application/json"
},
body: JSON.stringify({
label: "Accounts payable",
allowed_types: ["pdf"],
max_file_size_mb: 20,
max_files: 10,
expires_in: "7d"
})
});
if (!response.ok) throw new Error(await response.text());
console.log(await response.json());import os
import requests
response = requests.post(
"https://filepost.dev/v1/intake-links",
headers={"X-API-Key": os.environ["FILEPOST_API_KEY"]},
json={
"label": "Accounts payable",
"allowed_types": ["pdf"],
"max_file_size_mb": 20,
"max_files": 10,
"expires_in": "7d",
},
timeout=30,
)
response.raise_for_status()
print(response.json())<?php
$payload = [
"label" => "Accounts payable",
"allowed_types" => ["pdf"],
"max_file_size_mb" => 20,
"max_files" => 10,
"expires_in" => "7d",
];
$ch = curl_init("https://filepost.dev/v1/intake-links");
curl_setopt_array($ch, [
CURLOPT_POST => true,
CURLOPT_HTTPHEADER => [
"X-API-Key: " . getenv("FILEPOST_API_KEY"),
"Content-Type: application/json",
],
CURLOPT_POSTFIELDS => json_encode($payload, JSON_THROW_ON_ERROR),
CURLOPT_RETURNTRANSFER => true,
]);
$body = curl_exec($ch);
if ($body === false) throw new Exception(curl_error($ch));
$status = curl_getinfo($ch, CURLINFO_RESPONSE_CODE);
curl_close($ch);
if ($status >= 400) throw new Exception($body);
print_r(json_decode($body, true, 512, JSON_THROW_ON_ERROR));GET/v1/intake-linksList intake links
curl https://filepost.dev/v1/intake-links \
-H "X-API-Key: $FILEPOST_API_KEY"const response = await fetch("https://filepost.dev/v1/intake-links", {
headers: { "X-API-Key": process.env.FILEPOST_API_KEY }
});
if (!response.ok) throw new Error(await response.text());
console.log(await response.json());import os
import requests
response = requests.get(
"https://filepost.dev/v1/intake-links",
headers={"X-API-Key": os.environ["FILEPOST_API_KEY"]},
timeout=30,
)
response.raise_for_status()
print(response.json())<?php
$ch = curl_init("https://filepost.dev/v1/intake-links");
curl_setopt_array($ch, [
CURLOPT_HTTPHEADER => [
"X-API-Key: " . getenv("FILEPOST_API_KEY"),
],
CURLOPT_RETURNTRANSFER => true,
]);
$body = curl_exec($ch);
if ($body === false) throw new Exception(curl_error($ch));
$status = curl_getinfo($ch, CURLINFO_RESPONSE_CODE);
curl_close($ch);
if ($status >= 400) throw new Exception($body);
print_r(json_decode($body, true, 512, JSON_THROW_ON_ERROR));POST/v1/intake/{token}/uploadUpload through an intake link
This is a public multipart upload. It uses the token from the intake link and does not send your API key.
curl -X POST https://filepost.dev/v1/intake/3L18hphjnmDB/upload \
-F "file=@supplier-invoice.pdf;type=application/pdf"import { readFile } from "node:fs/promises";
const bytes = await readFile("supplier-invoice.pdf");
const form = new FormData();
form.append(
"file",
new Blob([bytes], { type: "application/pdf" }),
"supplier-invoice.pdf"
);
const response = await fetch(
"https://filepost.dev/v1/intake/3L18hphjnmDB/upload",
{ method: "POST", body: form }
);
if (!response.ok) throw new Error(await response.text());
console.log(await response.json());import requests
with open("supplier-invoice.pdf", "rb") as invoice:
response = requests.post(
"https://filepost.dev/v1/intake/3L18hphjnmDB/upload",
files={"file": (
"supplier-invoice.pdf",
invoice,
"application/pdf",
)},
timeout=60,
)
response.raise_for_status()
print(response.json())<?php
$file = new CURLFile(
"supplier-invoice.pdf",
"application/pdf",
"supplier-invoice.pdf"
);
$ch = curl_init(
"https://filepost.dev/v1/intake/3L18hphjnmDB/upload"
);
curl_setopt_array($ch, [
CURLOPT_POST => true,
CURLOPT_POSTFIELDS => ["file" => $file],
CURLOPT_RETURNTRANSFER => true,
]);
$body = curl_exec($ch);
if ($body === false) throw new Exception(curl_error($ch));
$status = curl_getinfo($ch, CURLINFO_RESPONSE_CODE);
curl_close($ch);
if ($status >= 400) throw new Exception($body);
print_r(json_decode($body, true, 512, JSON_THROW_ON_ERROR));DELETE/v1/intake-links/{intake_id}Deactivate an intake link
Deactivation prevents new uploads. It does not delete files already received.
curl -X DELETE https://filepost.dev/v1/intake-links/3L18hphjnmDB \
-H "X-API-Key: $FILEPOST_API_KEY"const intakeId = "3L18hphjnmDB";
const response = await fetch(
`https://filepost.dev/v1/intake-links/${intakeId}`,
{
method: "DELETE",
headers: { "X-API-Key": process.env.FILEPOST_API_KEY }
}
);
if (!response.ok) throw new Error(await response.text());
console.log(await response.json());import os
import requests
intake_id = "3L18hphjnmDB"
response = requests.delete(
f"https://filepost.dev/v1/intake-links/{intake_id}",
headers={"X-API-Key": os.environ["FILEPOST_API_KEY"]},
timeout=30,
)
response.raise_for_status()
print(response.json())<?php
$intakeId = "3L18hphjnmDB";
$ch = curl_init(
"https://filepost.dev/v1/intake-links/" . $intakeId
);
curl_setopt_array($ch, [
CURLOPT_CUSTOMREQUEST => "DELETE",
CURLOPT_HTTPHEADER => [
"X-API-Key: " . getenv("FILEPOST_API_KEY"),
],
CURLOPT_RETURNTRANSFER => true,
]);
$body = curl_exec($ch);
if ($body === false) throw new Exception(curl_error($ch));
$status = curl_getinfo($ch, CURLINFO_RESPONSE_CODE);
curl_close($ch);
if ($status >= 400) throw new Exception($body);
print_r(json_decode($body, true, 512, JSON_THROW_ON_ERROR));Webhooks
Set one webhook URL per account and FilePost sends a signed file.uploaded
event to it after every successful API upload (/v1/upload and
/v1/upload/base64). This lets your n8n, Zapier, Make, or custom backend react
to uploads without polling.
Delivery is retried with backoff until your endpoint responds successfully. Signatures let you confirm a request really came from FilePost.
PUT /v1/webhook: Set the webhook URL
Replaces the current webhook URL. Only https:// URLs are accepted in
production.
curl -X PUT https://filepost.dev/v1/webhook \
-H "X-API-Key: $FILEPOST_API_KEY" \
-H "Content-Type: application/json" \
-d '{"webhook_url":"https://your-app.example.com/hooks/filepost"}'const response = await fetch("https://filepost.dev/v1/webhook", {
method: "PUT",
headers: {
"X-API-Key": process.env.FILEPOST_API_KEY,
"Content-Type": "application/json"
},
body: JSON.stringify({
webhook_url: "https://your-app.example.com/hooks/filepost"
})
});
if (!response.ok) throw new Error(await response.text());
console.log(await response.json());import os
import requests
response = requests.put(
"https://filepost.dev/v1/webhook",
headers={"X-API-Key": os.environ["FILEPOST_API_KEY"]},
json={"webhook_url": "https://your-app.example.com/hooks/filepost"},
timeout=30,
)
response.raise_for_status()
print(response.json())<?php
$ch = curl_init("https://filepost.dev/v1/webhook");
curl_setopt_array($ch, [
CURLOPT_CUSTOMREQUEST => "PUT",
CURLOPT_HTTPHEADER => [
"X-API-Key: " . getenv("FILEPOST_API_KEY"),
"Content-Type: application/json",
],
CURLOPT_POSTFIELDS => json_encode([
"webhook_url" => "https://your-app.example.com/hooks/filepost",
]),
CURLOPT_RETURNTRANSFER => true,
]);
$body = curl_exec($ch);
if ($body === false) throw new Exception(curl_error($ch));
$status = curl_getinfo($ch, CURLINFO_RESPONSE_CODE);
curl_close($ch);
if ($status >= 400) throw new Exception($body);
print_r(json_decode($body, true, 512, JSON_THROW_ON_ERROR));GET/v1/webhookRead the webhook URL
Returns the configured URL, or null when none is set.
curl https://filepost.dev/v1/webhook \
-H "X-API-Key: $FILEPOST_API_KEY"const response = await fetch("https://filepost.dev/v1/webhook", {
headers: { "X-API-Key": process.env.FILEPOST_API_KEY }
});
if (!response.ok) throw new Error(await response.text());
console.log(await response.json());import os
import requests
response = requests.get(
"https://filepost.dev/v1/webhook",
headers={"X-API-Key": os.environ["FILEPOST_API_KEY"]},
timeout=30,
)
response.raise_for_status()
print(response.json())<?php
$ch = curl_init("https://filepost.dev/v1/webhook");
curl_setopt_array($ch, [
CURLOPT_HTTPHEADER => ["X-API-Key: " . getenv("FILEPOST_API_KEY")],
CURLOPT_RETURNTRANSFER => true,
]);
$body = curl_exec($ch);
if ($body === false) throw new Exception(curl_error($ch));
$status = curl_getinfo($ch, CURLINFO_RESPONSE_CODE);
curl_close($ch);
if ($status >= 400) throw new Exception($body);
print_r(json_decode($body, true, 512, JSON_THROW_ON_ERROR));DELETE/v1/webhookRemove the webhook
Stops all future events. Files already uploaded are unaffected.
curl -X DELETE https://filepost.dev/v1/webhook \
-H "X-API-Key: $FILEPOST_API_KEY"const response = await fetch("https://filepost.dev/v1/webhook", {
method: "DELETE",
headers: { "X-API-Key": process.env.FILEPOST_API_KEY }
});
if (!response.ok) throw new Error(await response.text());
console.log(await response.json());import os
import requests
response = requests.delete(
"https://filepost.dev/v1/webhook",
headers={"X-API-Key": os.environ["FILEPOST_API_KEY"]},
timeout=30,
)
response.raise_for_status()
print(response.json())<?php
$ch = curl_init("https://filepost.dev/v1/webhook");
curl_setopt_array($ch, [
CURLOPT_CUSTOMREQUEST => "DELETE",
CURLOPT_HTTPHEADER => ["X-API-Key: " . getenv("FILEPOST_API_KEY")],
CURLOPT_RETURNTRANSFER => true,
]);
$body = curl_exec($ch);
if ($body === false) throw new Exception(curl_error($ch));
$status = curl_getinfo($ch, CURLINFO_RESPONSE_CODE);
curl_close($ch);
if ($status >= 400) throw new Exception($body);
print_r(json_decode($body, true, 512, JSON_THROW_ON_ERROR));POST/v1/webhook/testSend a test event
Sends an immediate ping event so you can verify your receiver before the next
upload. The response reports the receiver's HTTP status code.
curl -X POST https://filepost.dev/v1/webhook/test \
-H "X-API-Key: $FILEPOST_API_KEY"const response = await fetch("https://filepost.dev/v1/webhook/test", {
method: "POST",
headers: { "X-API-Key": process.env.FILEPOST_API_KEY }
});
if (!response.ok) throw new Error(await response.text());
console.log(await response.json());import os
import requests
response = requests.post(
"https://filepost.dev/v1/webhook/test",
headers={"X-API-Key": os.environ["FILEPOST_API_KEY"]},
timeout=30,
)
response.raise_for_status()
print(response.json())<?php
$ch = curl_init("https://filepost.dev/v1/webhook/test");
curl_setopt_array($ch, [
CURLOPT_CUSTOMREQUEST => "POST",
CURLOPT_HTTPHEADER => ["X-API-Key: " . getenv("FILEPOST_API_KEY")],
CURLOPT_RETURNTRANSFER => true,
]);
$body = curl_exec($ch);
if ($body === false) throw new Exception(curl_error($ch));
$status = curl_getinfo($ch, CURLINFO_RESPONSE_CODE);
curl_close($ch);
if ($status >= 400) throw new Exception($body);
print_r(json_decode($body, true, 512, JSON_THROW_ON_ERROR));Event payload
Every event is a JSON POST with Content-Type: application/json.
{
"event": "file.uploaded",
"file_id": "8f3kX9aQ2v",
"url": "https://cdn.filepost.dev/8f3kX9aQ2v/report.pdf",
"size": 48291,
"content_type": "application/pdf",
"original_name": "report.pdf",
"filename_mode": "download",
"upload_source": "api",
"expires_at": null,
"uploaded_at": "2026-08-07T12:34:56+00:00"
}
The test event uses "event": "ping" with a sent_at timestamp instead.
Verifying signatures
Each request includes an X-FilePost-Signature header:
X-FilePost-Signature: sha256=<hex-digest>
The digest is an HMAC-SHA256 of the raw request body, keyed with the API key that performed the upload (or the key used to call the test endpoint). Any API key on the account verifies the same event if that key made the upload.
import hashlib
import hmac
def is_valid_filepost_signature(body: bytes, signature: str, api_key: str) -> bool:
expected = hmac.new(
api_key.encode(),
body,
hashlib.sha256,
).hexdigest()
return hmac.compare_digest(f"sha256={expected}", signature)
Retry policy
Delivery runs after the upload response is sent, so a slow receiver never slows
down your uploads. FilePost retries up to 4 times total with backoff. Retries
stop as soon as the receiver answers 2xx or any 4xx (a permanent error such
as a wrong path); 5xx responses and network timeouts are retried.
Rate limits and plans
Rate limits are enforced per API key. Exceeding the rate limit returns
429 Too Many Requests.
| Account state | Uploads | Rate limit | Maximum file size | Storage |
|---|---|---|---|---|
| Free, unverified email | 1 provisional upload | 10 requests/second | 10 MB | 10 MB |
| Free, verified email | 50 per month | 10 requests/second | 50 MB | 2 GB |
| Lite, $4/month | 300 per month | 30 requests/second | 100 MB | 10 GB |
| Starter, $9/month | 1,500 per month | 50 requests/second | 200 MB | Unlimited |
| Pro, $29/month | 7,500 per month | 200 requests/second | 500 MB | Unlimited |
All plans include HTTPS, Cloudflare CDN delivery, and unlimited bandwidth. Verified accounts receive permanent URLs by default. An unverified account's first file expires after 24 hours unless the email is verified in time.
Errors
All API errors use a JSON detail field:
{
"detail": "Human-readable error message"
}
| Status | Meaning | What to do |
|---|---|---|
400 |
Invalid request or file data | Check the documented body, filename, and content type. |
401 |
Missing or invalid API key | Confirm the X-API-Key header and active key. |
403 |
Plan or usage limit reached | Check account usage and plan limits. |
404 |
File or intake link not found | Confirm the ID belongs to the authenticated account. |
409 |
Conflicting account value | Use a different value or finish the pending change. |
410 |
Intake link expired, full, or inactive | Create or activate a usable intake link. |
413 |
File is too large | Use a smaller file or a plan with a larger limit. |
422 |
Validation failed | Read detail and correct the named field. |
429 |
Rate limit exceeded | Back off before retrying. |
500 |
Internal error | Retry with exponential backoff. |
502 |
Storage or CDN operation could not be confirmed | Keep the previous state and retry. Contact support if it persists. |
OpenAPI and interactive testing
The raw OpenAPI 3.1 schema is available at
/openapi.json. Use it with OpenAPI Generator, Orval, Kiota,
or another code generator to create a typed client.
Use the Swagger UI to inspect schemas and send individual requests interactively.
Documentation source
This reference is rendered from Markdown. The prose, tables, scenario, and
examples live in content/api-reference.md. Each :::code-tabs block must
contain cURL, JavaScript, Python, and PHP in that order. Updating the Markdown
updates the public documentation without editing page layout or JavaScript.
Ready to try the complete example?
Get a free API key and upload your first real file.