API Reference

FilePost API Documentation

Follow one file from upload to deletion, then use the same verified requests in cURL, JavaScript, Python, or PHP.

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

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.

Follow this example

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
  1. 1Upload
  2. 2Use URL
  3. 3Inspect
  4. 4Visibility
  5. 5List
  6. 6Delete

Before running an example, save your key in an environment variable:

export 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 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"

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",
  "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.

Once a verified free account has used 60% of its monthly allowance, successful multipart and Base64 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.

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\"
  }"

The response is identical to the multipart upload response.

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"

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:

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"}'

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"

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"

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"]}'

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"}'

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"

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"}'

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"

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"
  }'

GET/v1/intake-linksList intake links

curl https://filepost.dev/v1/intake-links \
  -H "X-API-Key: $FILEPOST_API_KEY"

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"

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"

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"}'

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"

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"

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"

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 15 per month 10 requests/second 50 MB 2 GB
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, permanent URLs by default, and unlimited bandwidth.

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.