File Upload API: How to Upload Files with cURL, Python & JavaScript

March 31, 2026 · Updated April 23, 2026 · 12 min read

A file upload API is a REST endpoint that accepts a file over HTTP and returns a permanent URL you can share or store. This guide covers how file upload APIs work under the hood (multipart/form-data, presigned URLs, REST design patterns) and gives you working code in cURL, Python, and JavaScript — both browser and Node.js — that you can copy straight into your project.

What Is a File Upload API?

A file upload API is a web service that accepts files over HTTP and stores them for you. Instead of building your own upload server, configuring storage buckets, setting up CDNs, and managing disk space, you send a POST request with your file and get back a URL where that file is permanently hosted.

The typical flow looks like this:

  1. Your application sends an HTTP POST request with the file as multipart/form-data
  2. The API stores the file and returns a JSON response
  3. The response includes a CDN URL you can use immediately

This pattern is used everywhere: profile picture uploads, document attachments, media storage for CMS platforms, receipt uploads in fintech apps, and automated file processing pipelines. If your app handles files, you need an upload mechanism, and an API is the most portable way to build one.

Anatomy of a File Upload HTTP Request

Before looking at the code, it helps to see what actually goes over the wire. A file upload is just an HTTP POST with a specific body format called multipart/form-data. Here is what the raw request looks like when you upload a single PNG:

POST /v1/upload HTTP/1.1
Host: filepost.dev
X-API-Key: fh_abc123...
Content-Type: multipart/form-data; boundary=----WebKitFormBoundary7MA4YWxkTrZu0gW
Content-Length: 84321

------WebKitFormBoundary7MA4YWxkTrZu0gW
Content-Disposition: form-data; name="file"; filename="photo.png"
Content-Type: image/png

<binary file bytes here>
------WebKitFormBoundary7MA4YWxkTrZu0gW--

The important pieces:

You almost never write this by hand. Every HTTP library (cURL, Python requests, browser FormData, Node.js fetch) has a helper that generates the boundary and formats the body for you. The single most common upload bug is setting Content-Type manually and breaking the boundary. More on that in the best-practices section below.

Prerequisites

To follow along with the code examples in this guide, you need:

To get your FilePost API key, sign up with a single request:

curl -X POST https://filepost.dev/v1/signup \
  -H "Content-Type: application/json" \
  -d '{"email": "you@example.com"}'

You will receive your API key in the response. Save it somewhere safe, you will need it for every upload.

Uploading Files with cURL

cURL is the fastest way to test a file upload API. The -F flag handles multipart encoding automatically, so you do not need to think about content types or boundaries.

curl -X POST https://filepost.dev/v1/upload \
  -H "X-API-Key: your_api_key_here" \
  -F "file=@/path/to/photo.png"

That is the entire request. The @ symbol tells cURL to read the file from disk rather than sending the literal string. cURL sets the Content-Type header to multipart/form-data automatically when you use -F.

A successful response looks like this:

{
  "url": "https://cdn.filepost.dev/file/filepost/uploads/a1/a1b2c3.png",
  "file_id": "a1b2c3d4e5f6",
  "size": 84210
}

The url field is a permanent CDN link. You can paste it into a browser, embed it in HTML, or store it in your database. The file_id is useful for listing or deleting the file later. The size is the file size in bytes.

Uploading Multiple Files

If you need to upload several files in a batch, you can loop in bash:

for file in ./uploads/*; do
  curl -s -X POST https://filepost.dev/v1/upload \
    -H "X-API-Key: your_api_key_here" \
    -F "file=@$file"
  echo ""
done

Uploading Files with Python

Python's requests library makes file uploads straightforward. The files parameter handles multipart encoding, and the library reads the file from disk for you.

import requests

api_key = "your_api_key_here"
file_path = "/path/to/photo.png"

with open(file_path, "rb") as f:
    response = requests.post(
        "https://filepost.dev/v1/upload",
        headers={"X-API-Key": api_key},
        files={"file": f}
    )

data = response.json()
print(f"URL: {data['url']}")
print(f"File ID: {data['file_id']}")
print(f"Size: {data['size']} bytes")

A few things to note here:

Uploading In-Memory Files

Sometimes your file does not exist on disk. Maybe you generated a PDF, downloaded something from another API, or captured a screenshot. You can upload bytes directly:

import requests
from io import BytesIO

# Suppose you have raw bytes from somewhere
image_bytes = b"\x89PNG\r\n..."  # your actual bytes

response = requests.post(
    "https://filepost.dev/v1/upload",
    headers={"X-API-Key": "your_api_key_here"},
    files={"file": ("generated.png", BytesIO(image_bytes), "image/png")}
)

print(response.json()["url"])

The tuple format (filename, file_object, content_type) gives you full control over the upload metadata without needing a file on disk.

Uploading Files from JavaScript (Browser and Node.js)

JavaScript has two very different upload contexts: a web page where the file comes from an <input type="file"> element, and a Node.js script reading from disk. The HTTP request is identical, but the way you get the file bytes differs.

Browser: HTML Form and fetch

In a browser, the user picks a file through a file input. You grab the File object from input.files[0], wrap it in a FormData, and POST it with fetch. No libraries needed.

<input type="file" id="fileInput">
<button id="uploadBtn">Upload</button>

<script>
document.getElementById("uploadBtn").addEventListener("click", async () => {
  const input = document.getElementById("fileInput");
  const file = input.files[0];
  if (!file) return;

  const form = new FormData();
  form.append("file", file);

  const response = await fetch("https://filepost.dev/v1/upload", {
    method: "POST",
    headers: { "X-API-Key": "your_api_key_here" },
    body: form,
  });

  const data = await response.json();
  console.log("Uploaded to:", data.url);
});
</script>

A few important points for browser uploads:

Node.js: readFile and fetch

Node.js 18 and later ship with a built-in fetch API and FormData support, so you do not need any third-party packages. However, reading a file from disk still requires the fs module.

import { readFile } from "fs/promises";

const apiKey = "your_api_key_here";
const filePath = "/path/to/photo.png";

const fileBuffer = await readFile(filePath);
const file = new File([fileBuffer], "photo.png", { type: "image/png" });

const form = new FormData();
form.append("file", file);

const response = await fetch("https://filepost.dev/v1/upload", {
  method: "POST",
  headers: { "X-API-Key": apiKey },
  body: form,
});

const data = await response.json();
console.log(`URL: ${data.url}`);
console.log(`File ID: ${data.file_id}`);
console.log(`Size: ${data.size} bytes`);

Key details for Node.js uploads:

Handling API Responses

A well-built upload client handles both success and failure cases. Here is a more robust Python example with error handling:

import requests

def upload_file(file_path, api_key):
    try:
        with open(file_path, "rb") as f:
            response = requests.post(
                "https://filepost.dev/v1/upload",
                headers={"X-API-Key": api_key},
                files={"file": f},
                timeout=30
            )

        if response.status_code == 200:
            data = response.json()
            return data["url"]
        elif response.status_code == 401:
            raise Exception("Invalid API key")
        elif response.status_code == 413:
            raise Exception("File too large for your plan")
        elif response.status_code == 429:
            raise Exception("Upload limit reached for this month")
        else:
            raise Exception(f"Upload failed: {response.status_code}")

    except requests.exceptions.Timeout:
        raise Exception("Upload timed out, check your connection")
    except FileNotFoundError:
        raise Exception(f"File not found: {file_path}")

The most common HTTP status codes you will encounter with file upload APIs:

Start Uploading Files in 30 Seconds

FilePost gives you 300 free uploads per month. No credit card, no configuration, no storage management.

Get Your Free API Key

Best Practices for API File Uploads

After building dozens of integrations that handle file uploads, here are the practices that prevent headaches:

1. Always Set a Timeout

File uploads can hang indefinitely if the network connection drops mid-transfer. Set a timeout proportional to the expected file size. For most uploads under 50MB, 30 seconds is a reasonable starting point.

2. Validate Before Uploading

Check file size and type on the client side before sending the request. Uploading a 500MB file only to get a 413 response wastes time and bandwidth. If your API has a 200MB limit, reject anything larger before the upload starts.

3. Use Streaming for Large Files

If you are uploading files larger than a few hundred megabytes, avoid reading the entire file into memory. Most HTTP libraries support streaming uploads that read the file in chunks. In Python, requests streams file uploads by default when you pass a file object to the files parameter.

4. Store the File ID, Not Just the URL

The URL is what you will use most of the time, but the file ID is essential for management operations like listing and deleting files. Store both in your database.

5. Handle Retries Carefully

File uploads are not idempotent, retrying a failed upload may create a duplicate. If you need retry logic, implement it at the application level with deduplication checks rather than using generic HTTP retry middleware.

6. Do Not Set Content-Type Manually

This is the single most common mistake. When sending multipart/form-data, the Content-Type header includes a boundary string that separates the parts. If you set the header yourself, the boundary will not match the body, and the server will reject the request. Let your HTTP library set it automatically.

REST API Design Patterns for File Uploads

When you design or choose a file upload API, there are three common patterns. Each has tradeoffs around performance, security, and implementation complexity.

Pattern 1: Direct multipart/form-data upload

The client POSTs the file directly to the API server, which handles storage itself. This is what the examples above use. It is the simplest pattern: one request, one response, no coordination between the client and a storage backend.

Good for: small-to-medium files (under 100 MB), APIs that need to inspect or transform files during upload, simple integrations where one HTTP call is easier than two.

Bad for: very large files (the API server has to proxy every byte), high-throughput systems (upload traffic competes with regular API traffic on the same infrastructure).

Pattern 2: Base64-encoded JSON

The client reads the file, encodes it as base64, and sends it as a JSON field:

{
  "filename": "photo.png",
  "content_type": "image/png",
  "data": "iVBORw0KGgoAAAANSUhEUgAA..."
}

Good for: schemas that are already JSON end-to-end, environments where you cannot easily send multipart (some legacy SOAP-to-REST bridges, certain embedded systems).

Bad for: almost everything else. Base64 adds ~33% overhead, the entire file sits in memory as a string on both client and server, and JSON parsers have to process every byte. Avoid this pattern unless you have a specific reason.

Pattern 3: Presigned URLs (two-step upload)

The client makes an API call asking "where should I upload this?", the API returns a short-lived presigned URL pointing directly at object storage (S3, B2, GCS), and the client PUTs the file to that URL. The API server never handles the file bytes.

// Step 1: ask the API for an upload URL
POST /v1/uploads/presign
  → { "upload_url": "https://...signed...", "file_id": "abc123" }

// Step 2: PUT the file directly to the presigned URL
PUT <upload_url>
Content-Type: image/png
<binary bytes>

Good for: large files, high-throughput systems, browser uploads where you do not want to proxy through your own servers, any case where you want upload traffic to go straight to storage.

Bad for: APIs that need to validate or process files during upload (you can still do it after, via a webhook or storage event), simple use cases where the extra round trip is not worth the complexity.

For most applications, direct multipart is the right default. Switch to presigned URLs when files get large (above ~100 MB) or when upload volume becomes significant.

Popular File Upload APIs Compared

If you are evaluating options, here is a quick comparison of the file upload APIs developers most commonly consider in 2026:

API Free tier Max file size (paid) Pricing model Best for
FilePost 300 uploads/mo 500 MB Flat monthly ($9 / $29) Predictable billing, simple REST API, permanent URLs
file.io 2 GB/file (ephemeral) Up to 5 GB Per-download One-time file transfers, self-destructing links
UploadThing 2 GB storage Varies by plan Storage + bandwidth tiers Next.js apps, React component integration
Cloudinary 25 GB storage Varies by media type Credits (storage + transforms) Image/video transformation pipelines
AWS S3 + presigned URLs 5 GB (12 months) 5 TB Per-GB storage + egress Very large files, custom pipelines, enterprise scale

See the UploadThing alternative, file.io alternative, and FilePost vs Cloudinary comparisons for deeper breakdowns of each.

Getting Started with FilePost

If you are looking for a file upload API that just works, FilePost is built for exactly this use case. There is no bucket configuration, no IAM policies, no region selection, and no SDK to install. You make one HTTP request and get back a CDN URL.

Here is what you get:

Paid plans include unlimited storage, unlimited bandwidth, CDN delivery, and files that live forever. The free tier includes 2GB storage. There are no per-GB storage fees or egress charges. If you want to compare FilePost with other options, see our FilePost vs Cloudinary and uploadtourl alternative comparisons.

You can also manage your files with the API. List all your uploaded files:

curl https://filepost.dev/v1/files \
  -H "X-API-Key: your_api_key_here"

Delete a file by ID:

curl -X DELETE https://filepost.dev/v1/files/a1b2c3d4e5f6 \
  -H "X-API-Key: your_api_key_here"

The full API documentation includes interactive Swagger docs where you can test every endpoint directly in your browser.