Skip to Content
API ReferenceMedia API

Media API

The Media API provides file upload, management, and image transformation capabilities. Upload images, videos, documents, and other files, then serve images with on-the-fly CDN transformations.

File Upload

Upload files using the REST endpoint with a multipart form data request.

Endpoint

POST https://api.foir.io/api/files/upload

Request

Send a multipart/form-data request with the file in the file field:

curl -X POST https://api.foir.io/api/files/upload \ -H "x-api-key: sk_live_your_api_key" \ -F "file=@/path/to/image.jpg"
async function uploadFile(file) { const formData = new FormData(); formData.append("file", file); const response = await fetch("https://api.foir.io/api/files/upload", { method: "POST", headers: { "x-api-key": process.env.FOIR_API_KEY, }, body: formData, }); return response.json(); }

Limits

  • Maximum file size: 10 MB
  • Authentication: Requires an API key with files:write scope

Supported File Types

CategoryFormats
ImagesJPEG, PNG, GIF, WebP, AVIF, SVG, TIFF, BMP, ICO
VideoMP4, WebM, MOV, AVI
AudioMP3, WAV, OGG, FLAC, AAC
DocumentsPDF, DOC, DOCX, XLS, XLSX, CSV, TXT, RTF
FontsWOFF, WOFF2, TTF, OTF, EOT

Upload Response

{ "id": "file_abc123", "fileName": "image.jpg", "fileUrl": "https://cdn.foir.io/file_abc123", "contentType": "image/jpeg", "fileSize": 245760, "status": "PROCESSING" }

After upload, files enter a processing pipeline where metadata like dimensions, blur hash, and dominant color are extracted.

File Processing Status

StatusDescription
PENDINGUpload received, waiting for processing
PROCESSINGExtracting metadata and generating variants
READYFully processed and available for delivery
FAILEDProcessing failed (file is still accessible)

Image Transformations

Image URLs include an opaque ?t= token the platform mints at resolution time. The token encodes the size preset and any per-usage crop/focal data. Free-form ?width=…&fit=… query parameters are not honoured — only the platform’s own tokens trigger transforms.

URL format

https://cdn.foir.io/{fileId}?t=eyJzIjoibWVkaXVtIn0

The ?t= value is a base64url-encoded JSON object:

{ "s": "medium", "c": "10,10,80,80", "f": "0.5,0.4" }
FieldMeaningFormat
sSize presetthumbnail (150 px) / small (320) / medium (640) / large (1280) / xlarge (1920)
cCrop rectanglex,y,width,height as 0–100 percentages
fFocal pointx,y as 0–1 floats

All three fields are optional. A token with only s resizes to a fixed width; adding c applies a per-usage crop before resize; f controls smart-crop fallback.

Format negotiation

Output format follows the Accept header. Browsers that send Accept: image/avif get AVIF; everyone else gets the best supported format. There’s no format parameter on the URL.

Why opaque tokens

Free-form transform parameters would let scrapers synthesise arbitrary variants and burn through CDN-side image-resize bills. The token grammar is opaque enough that only URLs the platform emits hit the transform pipeline; unknown query strings return the original image.

Resolved image values

The platform emits transform URLs automatically when you resolve image fields. Don’t construct ?t= tokens client-side — let the API response carry them.

query { product(naturalKey: "blue-widget") { hero { # ImageValue url # https://cdn.foir.io/file_abc?t=… (large default) sources { # responsive sizes the platform pre-emits url media } width height alt blurHash dominantColor } } }
<picture> <source srcset="https://cdn.foir.io/file_abc?t=eyJzIjoibGFyZ2UifQ" media="(min-width: 1024px)" /> <source srcset="https://cdn.foir.io/file_abc?t=eyJzIjoibWVkaXVtIn0" media="(min-width: 640px)" /> <img src="https://cdn.foir.io/file_abc?t=eyJzIjoic21hbGwifQ" alt="..." loading="lazy" /> </picture>

Blurhash Placeholders

Images processed by Foir include a blurHash field — a compact string encoding of a blurred placeholder. Use it to display a low-resolution preview while the full image loads.

import { Blurhash } from "react-blurhash"; import { useState } from "react"; function ImageWithPlaceholder({ file }) { const [loaded, setLoaded] = useState(false); return ( <div style={{ position: "relative" }}> {!loaded && file.blurHash && ( <Blurhash hash={file.blurHash} width={400} height={300} /> )} <img src={file.fileUrl} alt={file.altText} onLoad={() => setLoaded(true)} style={{ display: loaded ? "block" : "none" }} /> </div> ); }

GraphQL File Queries

Get a Single File

query GetFile { file(id: "file_abc123") { id fileName fileUrl contentType fileSize width height blurHash dominantColor altText caption status createdAt } }

Required scope: files:read

List Files

query ListFiles { files( limit: 20 offset: 0 contentType: "image" folder: "banners" ) { items { id fileName fileUrl contentType fileSize width height blurHash } total hasMore } }

Parameters:

ParameterTypeDescription
limitIntMaximum files to return
offsetIntFiles to skip
contentTypeStringFilter by content type prefix (e.g., "image", "video")
folderStringFilter by folder

Check Storage Usage

query StorageUsage { fileStorageUsage { totalBytes fileCount } }

GraphQL File Mutations

Update File Properties

mutation UpdateFile { updateFile(id: "file_abc123", input: { fileName: "updated-name.jpg" folder: "heroes" tags: ["marketing", "homepage"] }) { id fileName folder tags } }

Required scope: files:write

Update File Metadata

Update translatable metadata like alt text and captions:

mutation UpdateMetadata { updateFileMetadata(id: "file_abc123", input: { altText: "A scenic mountain landscape at sunset" caption: "Photo taken in the Swiss Alps" locale: "en" }) { id altText caption } }

Required scope: files:write

Delete a File

mutation DeleteFile { deleteFile(id: "file_abc123") { success message } }

Required scope: files:delete

File Type Reference

type File { id: ID! fileName: String! fileUrl: String! contentType: String! fileSize: Int! width: Int height: Int blurHash: String dominantColor: String altText: String caption: String folder: String tags: [String] status: FileStatus! createdAt: DateTime! updatedAt: DateTime! }
Last updated on