XFiles

File infrastructure
that ships with your app

XFiles gives your application a complete file system — uploads, image resizing, CDN delivery, and polymorphic attachments — through a single REST API that works with any framework.

Every project needs file handling, and every team ends up building the same pipeline from scratch. We did the hard part so you can ship the rest.

Pay as you go

$0.05/GB/mo
$0.20/GB
Freetransforms

Free to startNo credit card2 GB storage + 10 GB bandwidth

Direct-to-S3 uploadsCloudFront CDN delivery7 image variants on demandPublic + signed private URLsPolymorphic entity attachments
upload demo

Scenario

sneaker-front.jpg

image/jpeg · 2.4 MB

product/gallery

prod_8kx2m

File visibility
Image transforms
Variant visibility

Request body preview

{
  "filename": "sneaker-front.jpg",
  "visibility": "public",
  "entity": {
    "type": "product",
    "id": "prod_8kx2m",
    "role": "gallery"
  },
  "transformations": {
    "image": {
      "enabled": true,
      "visibility": "public"
    }
  }
}
See the API docs
01

Quick Start

Get files uploading in four steps. Create a project, request an upload URL, push the file directly to storage, then confirm.

1Create a Project

Sign in to the dashboard and create a new project. You'll receive an API key in the format xf_AbC12dEfGhIjKlMnOpQrStUvWxYz9012. This key is shown once — copy it immediately.

2Request an Upload URL

POST /api/v1/files/upload-intent
1curl -X POST https://xfiles.dev/api/v1/files/upload-intent \
2 -H "Authorization: Bearer xf_AbC12dEfGhIjKlMnOpQrStUvWxYz9012" \
3 -H "Content-Type: application/json" \
4 -d '{
5 "filename": "photo.jpg",
6 "contentType": "image/jpeg",
7 "size": 204800,
8 "visibility": "public",
9 "ownerUserId": "user_123",
10 "entity": {
11 "type": "post",
12 "id": "post_abc123",
13 "role": "gallery",
14 "position": 0
15 },
16 "transformations": {
17 "image": { "enabled": true, "visibility": "public" }
18 }
19 }'
Response
1{
2 "fileId": "abc123def456",
3 "uploadUrl": "https://storage.example.com/...?X-Amz-Signature=...",
4 "key": "files/originals/public/proj_id/abc123def456/photo-k9x2m7.jpg",
5 "expiresIn": 900
6}

3Upload the File

PUT the file body to the presigned URL. The Content-Type and Content-Length must match exactly what you declared in step 2 — the server will reject mismatches with 403.

PUT to presigned URL
1curl -X PUT "UPLOAD_URL_FROM_STEP_2" \
2 -H "Content-Type: CONTENT_TYPE_FROM_STEP_2" \
3 --data-binary @photo.jpg

4Confirm the Upload

POST /api/v1/files/confirm
1curl -X POST https://xfiles.dev/api/v1/files/confirm \
2 -H "Authorization: Bearer xf_AbC12dEfGhIjKlMnOpQrStUvWxYz9012" \
3 -H "Content-Type: application/json" \
4 -d '{ "fileId": "abc123def456" }'
Response
1{
2 "file": {
3 "id": "abc123def456",
4 "filename": "photo-k9x2m7.jpg",
5 "originalFilename": "photo.jpg",
6 "contentType": "image/jpeg",
7 "size": 204800,
8 "visibility": "public",
9 "uploadStatus": "confirmed",
10 "ownerUserId": "user_123",
11 "entity": {
12 "type": "post",
13 "id": "post_abc123",
14 "role": "gallery",
15 "position": 0
16 },
17 "transformations": {
18 "image": { "enabled": true, "visibility": "public" }
19 },
20 "createdAt": "2026-03-12T10:00:00.000Z"
21 }
22}

How it works: Files never pass through your application server. Uploads go directly to S3 via presigned URLs. Downloads are always served through CloudFront CDN — public files get permanent CDN URLs, private files get signed CDN URLs. Your server only handles the intent and confirmation steps.

02

Authentication

All /api/v1/* routes authenticate via Bearer token using project API keys.

Authorization header
1Authorization: Bearer xf_AbC12dEfGhIjKlMnOpQrStUvWxYz9012
Formatxf_AbC12dEfGhIjKlMnOpQrStUvWxYz9012 — 35 characters total
Created viaDashboard — New Project — key shown once, copy immediately
RotationDashboard — Project Settings — Rotate Key — old key immediately invalidated
ErrorsMissing or invalid key returns 401 Unauthorized
03

API Reference

File EndpointsAPI Key Auth

MethodPathDescription
POST/api/v1/files/upload-intentGet presigned upload URL
POST/api/v1/files/confirmConfirm upload after S3 PUT
GET/api/v1/filesList files with pagination, entity attachments, and filters
GET/api/v1/files/{id}Get file details + CDN URLs
PATCH/api/v1/files/{id}Update visibility or owner
DELETE/api/v1/files/{id}Delete single file from S3 + DB
DELETE/api/v1/filesBulk delete by entity filters
POST/api/v1/files/{id}/urlGenerate fresh CDN URLs
POST/api/v1/files/upload-intent

Creates a pending file record and returns a presigned upload URL. The presigned URL locks Content-Type and Content-Length so the client cannot upload a different file than declared.

Request Body
1{
2 "filename": "photo.jpg", // Required — original filename
3 "contentType": "image/jpeg", // Required — locked in presigned URL
4 "size": 204800, // Required — locked in presigned URL
5 "visibility": "public", // Optional — default "private"
6 "ownerUserId": "user_123", // Optional — track who uploaded
7 "entity": { // Optional — attach to an entity
8 "type": "post", // Polymorphic entity type
9 "id": "post_abc123", // Polymorphic entity ID
10 "role": "gallery", // Optional — avatar, logo, gallery, document, etc.
11 "position": 0 // Optional — display order (0 = primary, default: 0)
12 },
13 "transformations": { // Optional — image variant behavior
14 "image": {
15 "enabled": true, // Enable image variant generation (default: false)
16 "visibility": "public" // "public" or "private" (default: "private")
17 }
18 }
19}
Response
1{
2 "fileId": "abc123def456",
3 "uploadUrl": "https://storage.example.com/...?X-Amz-Signature=...",
4 "key": "files/originals/public/proj_id/abc123def456/photo-k9x2m7.jpg",
5 "expiresIn": 900
6}
POST/api/v1/files/confirm

Verifies the file exists in storage, updates status to confirmed, increments storage usage, and auto-creates a file attachment if entity was provided at upload.

Request Body
1{
2 "fileId": "abc123def456"
3}
Response
1{
2 "file": {
3 "id": "abc123def456",
4 "filename": "photo-k9x2m7.jpg",
5 "originalFilename": "photo.jpg",
6 "contentType": "image/jpeg",
7 "size": 204800,
8 "visibility": "public",
9 "uploadStatus": "confirmed",
10 "ownerUserId": "user_123",
11 "entity": {
12 "type": "post",
13 "id": "post_abc123",
14 "role": "gallery",
15 "position": 0
16 },
17 "transformations": {
18 "image": { "enabled": true, "visibility": "public" }
19 }
20 }
21}
GET/api/v1/files

List all confirmed files for the authenticated project. No filters required — call with just your API key to get all files. Always returns 20 files per page. Every file includes its entity attachment (or null). Optional filters narrow the results. When filtering by entity, results are sorted by position ascending.

Example
1# List all files — no filters needed
2curl https://xfiles.dev/api/v1/files \
3 -H "Authorization: Bearer xf_AbC12dEfGhIjKlMnOpQrStUvWxYz9012"
4
5# Page 2
6curl "https://xfiles.dev/api/v1/files?page=2" \
7 -H "Authorization: Bearer xf_AbC12dEfGhIjKlMnOpQrStUvWxYz9012"
8
9# Filter by entity — e.g. all images for a product
10curl "https://xfiles.dev/api/v1/files?entityType=product&entityId=prod_abc123&role=gallery" \
11 -H "Authorization: Bearer xf_AbC12dEfGhIjKlMnOpQrStUvWxYz9012"
12
13# Combine filters — public files for a specific owner
14curl "https://xfiles.dev/api/v1/files?visibility=public&ownerUserId=user_123" \
15 -H "Authorization: Bearer xf_AbC12dEfGhIjKlMnOpQrStUvWxYz9012"
Response
1{
2 "files": [
3 {
4 "id": "abc123def456",
5 "filename": "sneaker-front-k9x2m7.jpg",
6 "originalFilename": "sneaker-front.jpg",
7 "contentType": "image/jpeg",
8 "size": 204800,
9 "visibility": "public",
10 "uploadStatus": "confirmed",
11 "ownerUserId": "user_123",
12 "entity": {
13 "type": "product",
14 "id": "prod_abc123",
15 "role": "gallery",
16 "position": 0
17 },
18 "transformations": {
19 "image": { "enabled": true, "visibility": "public" }
20 },
21 "createdAt": "2026-03-12T10:00:00.000Z"
22 },
23 {
24 "id": "def456ghi789",
25 "filename": "contract-2024-m3x7k.pdf",
26 "originalFilename": "contract-2024.pdf",
27 "contentType": "application/pdf",
28 "size": 4100000,
29 "visibility": "private",
30 "uploadStatus": "confirmed",
31 "ownerUserId": null,
32 "entity": {
33 "type": "order",
34 "id": "ord_k4m91",
35 "role": "invoice",
36 "position": 0
37 },
38 "transformations": {
39 "image": { "enabled": false, "visibility": "private" }
40 },
41 "createdAt": "2026-03-12T09:30:00.000Z"
42 }
43 ],
44 "total": 42,
45 "page": 1,
46 "totalPages": 3
47}
GET/api/v1/files/{id}

Returns full file details and CDN URLs. All files are served through CloudFront — public files get direct CDN URLs, private files get signed CDN URLs. Raster images with transforms enabled include resized variant URLs.

Example
1curl https://xfiles.dev/api/v1/files/abc123def456 \
2 -H "Authorization: Bearer xf_AbC12dEfGhIjKlMnOpQrStUvWxYz9012"
Response
1{
2 "file": {
3 "id": "abc123def456",
4 "filename": "photo-k9x2m7.jpg",
5 "originalFilename": "photo.jpg",
6 "contentType": "image/jpeg",
7 "size": 204800,
8 "visibility": "public",
9 "uploadStatus": "confirmed",
10 "ownerUserId": "user_123",
11 "entity": {
12 "type": "post",
13 "id": "post_abc123",
14 "role": "gallery",
15 "position": 0
16 },
17 "transformations": {
18 "image": { "enabled": true, "visibility": "public" }
19 }
20 },
21 "url": "https://cdn.example.com/files/originals/public/.../photo-k9x2m7.jpg",
22 "variants": [
23 { "width": 100, "url": "https://cdn.example.com/files/variants/public/.../photo-k9x2m7_w100.webp" },
24 { "width": 300, "url": "https://cdn.example.com/files/variants/public/.../photo-k9x2m7_w300.webp" },
25 { "width": 400, "url": "..." },
26 { "width": 600, "url": "..." },
27 { "width": 800, "url": "..." },
28 { "width": 1000, "url": "..." },
29 { "width": 1200, "url": "..." }
30 ]
31}
PATCH/api/v1/files/{id}

Update file visibility, variant settings, owner, or display position. Changing visibility moves the S3 original between public/private paths. Changing variant visibility purges existing variants so they regenerate at the correct path.

Request Body
1{
2 "visibility": "public", // Optional — original file access
3 "ownerUserId": "user_456", // Optional — reassign owner
4 "position": 2, // Optional — display order (0 = primary, max 10000)
5 "transformations": { // Optional — image variant behavior
6 "image": {
7 "enabled": true, // Toggle variant generation on/off
8 "visibility": "public" // "public" or "private" (default: "private")
9 }
10 }
11}
Response
1{
2 "file": { ... }
3}
DELETE/api/v1/files/{id}

Deletes a single file — original, all variants, database record, and decrements storage.

Example
1curl -X DELETE https://xfiles.dev/api/v1/files/abc123def456 \
2 -H "Authorization: Bearer xf_AbC12dEfGhIjKlMnOpQrStUvWxYz9012"
Response
1{
2 "success": true
3}
DELETE/api/v1/files

Bulk delete all files matching entity filters. Use this when deleting a parent entity (e.g. a product) to clean up all attached files in one call. At least one filter is required.

Example
1curl -X DELETE "https://xfiles.dev/api/v1/files?entityType=product&entityId=prod_456" \
2 -H "Authorization: Bearer xf_AbC12dEfGhIjKlMnOpQrStUvWxYz9012"
3
4# With role filter — delete only gallery images, keep documents
5curl -X DELETE "https://xfiles.dev/api/v1/files?entityType=product&entityId=prod_456&role=gallery" \
6 -H "Authorization: Bearer xf_AbC12dEfGhIjKlMnOpQrStUvWxYz9012"
Response
1{
2 "deleted": 5
3}
POST/api/v1/files/{id}/url

Generate fresh CDN URLs for file access. All files are always served through CloudFront. Public files get permanent CDN URLs, private files get signed CDN URLs (valid for 5 minutes). Images with transforms enabled include variant URLs.

Example
1curl -X POST https://xfiles.dev/api/v1/files/abc123def456/url \
2 -H "Authorization: Bearer xf_AbC12dEfGhIjKlMnOpQrStUvWxYz9012"
Response
1// Public file with public variants — permanent CDN URLs:
2{
3 "url": "https://cdn.example.com/files/originals/public/.../photo.jpg",
4 "variants": [
5 { "width": 100, "url": "https://cdn.example.com/files/variants/public/.../photo_w100.webp" },
6 { "width": 300, "url": "..." },
7 ...
8 ]
9}
10
11// Private file with private variants — signed CDN URLs (5 min expiry):
12{
13 "url": "https://cdn.example.com/...?Signature=...&Expires=...",
14 "variants": [
15 { "width": 100, "url": "https://cdn.example.com/...?Signature=...&Expires=..." },
16 ...
17 ],
18 "expiresIn": 300
19}
20
21// SVGs and non-image files — original URL only, no variants:
22{
23 "url": "https://cdn.example.com/...?Signature=...",
24 "expiresIn": 300
25}
04

Upload Security

The presigned URL cryptographically locks the following fields. If the client sends different values, the server rejects the upload with 403 Forbidden.

Locked FieldWhat It Prevents
Content-TypeMust match the contentType declared at intent — server rejects mismatches
Content-LengthMust match the size declared at intent — prevents uploading larger files
VisibilityCannot tamper with visibility flag
Project IDCannot assign file to a different project
File IDCannot reassign to a different database record
ExpiryURL is valid for 15 minutes only

Two-step verification: After the client uploads, the confirm endpoint verifies the file actually exists in storage before marking it as confirmed. This prevents orphaned database records from failed or abandoned uploads.

05

File Visibility

Independent Visibility

All files are always served through CloudFront CDN — never directly from S3. Original files and image variants have independent visibility. Public files get permanent CDN URLs. Private files get signed CDN URLs (5 min expiry). Use transformations.image.visibility to control variant access — either "public" or "private" (defaults to "private").

 PublicPrivate (default)
Original filePermanent CDN URL — no signing neededSigned CDN URL (5 min expiry)
Image variantsPermanent CDN URL — no signing neededSigned CDN URL (5 min expiry)

Use Case: Photo Marketplace

Private high-res originals (paid downloads) with public preview variants (thumbnails for browsing). Set visibility: "private" and transformations.image.visibility: "public".

Changing visibility: Use PATCH /api/v1/files/{id} with visibility and/or transformations.image.visibility. Changing original visibility moves the S3 object between public/private paths. Changing variant visibility purges existing variants so they regenerate at the correct path.

06

Image Variants

Image variants are opt-in — set transformations.image.enabled: true at upload time or toggle it later via PATCH. When enabled, variants are generated on demand the first time they're requested, then cached by the CDN. Raster images (JPEG, PNG, GIF, WebP) are resized and reformatted. SVGs are vector graphics that scale infinitely — they don't get variants and are always served as the original file. When not enabled, no variant URLs are returned and Lambda@Edge will not generate variants.

Variant URL structure
1# Raster images — resized + reformatted, available at predefined widths
2https://cdn.example.com/.../{baseName}_w{width}.{format}
3
4# You don't need to construct these URLs — the API returns them for you.
5# Use GET /api/v1/files/{id} or POST /api/v1/files/{id}/url

Supported Widths

100px300px400px600px800px1000px1200px

Supported Formats

webpjpegjpgpngavif

CDN-only delivery: All files — originals and variants — are always served through CloudFront CDN, never directly from S3. Public variants get permanent CDN URLs. Private variants get signed CDN URLs — use POST /api/v1/files/{id}/url to generate fresh ones.

07

Filename Rules

Filenames you provide are sanitized before being used as storage keys. The original filename is preserved in the database for display.

#Rule
1Convert to lowercase
2Remove special characters (keep alphanumeric, spaces, hyphens)
3Replace spaces with hyphens
4Collapse multiple hyphens
5Preserve the original extension
6Append a unique 6-character suffix to avoid collisions
7Truncate base name to 100 characters max

Example

Input: "Best Ever Photo (2024).JPG"

Output: "best-ever-photo-2024-a7x9k2.jpg"

08

Content Types

You must declare the content type at upload intent time. Only whitelisted MIME types are accepted. The presigned URL locks both Content-Type and Content-Length so S3 rejects mismatches. Maximum file size: 100 MB.

Images

image/jpeg

image/png

image/gif

image/webp

image/svg+xml

image/avif

image/tiff

image/bmp

image/ico

Documents

application/pdf

application/msword

application/vnd.openxmlformats-*

text/plain

text/csv

text/html

text/css

text/javascript

application/json

application/xml

Archives

application/zip

application/gzip

application/x-tar

Audio

audio/mpeg

audio/wav

audio/ogg

audio/webm

Video

video/mp4

video/webm

video/ogg

Fonts

font/woff

font/woff2

font/ttf

font/otf

XFiles

File Management as a Service