# lorem.media — comprehensive reference

> Free placeholder **video, photos, and audio** for developers. Video-first — HLS adaptive streaming and auto-generated captions on every clip. URLs are the entire API.

For LLM agents (Claude, GPT, Cursor, Replit Agent) writing code that needs placeholder media. Also human-readable. Operated by [0.media](https://0.media), the paid sibling for your own content.

## Get started in 30 seconds

Five copy-pasteable patterns covering ~99% of usage. Video first because that's our differentiator.

### 1. Video, drop-in (mp4)

```html
<video src="https://lorem.media/video/16x9" autoplay muted loop playsinline></video>
```

Random 720p H.264 mp4. Plays in every browser without a player library. Pick the aspect: `16x9` (landscape), `9x16` (vertical mobile), `1x1` (square).

### 2. Video, HLS with auto-captions (production-shape)

```html
<video controls playsinline>
  <source src="https://lorem.media/video/16x9.m3u8" type="application/vnd.apple.mpegurl">
  <source src="https://lorem.media/video/16x9" type="video/mp4">
</video>
```

`.m3u8` URL serves a CMAF master playlist with the full bitrate ladder (240p → 1080p), audio rendition when present, and auto-generated English captions as a SUBTITLES track when speech is detected. Native Safari plays directly; Chrome / Firefox / Edge attach hls.js or Vidstack to the master URL to surface the CC button automatically.

### 3. Photo

```html
<img src="https://lorem.media/photo/800/600" alt="placeholder">
```

Exact 800 × 600, **smart-cropped** via Cloudflare's saliency detection — subjects and faces stay centered, not naïve center-crop. AVIF for modern browsers, WebP / JPEG fallback for older.

### 4. Audio

```html
<audio src="https://lorem.media/audio" controls></audio>
```

Random ~60-second MP3 from the Internet Archive 78 rpm collection (public domain). Narrow stylistically — classical, early jazz, marches.

### 5. Deterministic for Storybook / snapshot tests

```js
const heroVideo = 'https://lorem.media/video/seed/hero/16x9';
const mobileVideo = 'https://lorem.media/video/seed/hero/9x16';
const avatar = 'https://lorem.media/photo/seed/alice/200/200';
// Same seed → same asset, every time. Edge-cached for 24 h.
```

## Every URL (cheat sheet)

```
Video — HLS + auto-captions on every clip
  /video                          random video, 720p mp4
  /video/<aspect>                 random at aspect
  /video/<aspect>/<height>        specific rung (240/360/480/720/1080)
  /video/<aspect>.m3u8            HLS master playlist
  /video/seed/<seed>/<aspect>     deterministic
  /video/id/<id>                  specific asset
  /video/id/<id>.m3u8             specific, HLS
  /video/id/<id>.vtt              auto-captions (English)

Photos — exact W×H, smart-cropped
  /photo                          random, source size
  /photo/<w>/<h>                  exact W×H
  /photo/seed/<seed>/<w>/<h>      deterministic
  /photo/id/<id>/<w>/<h>          specific asset

Audio — public-domain clips
  /audio                          random ~60s MP3
  /audio/seed/<seed>              deterministic
  /audio/id/<id>                  specific asset

Metadata + bytes
  /v2/list/<type>                 paginated JSON (rate-limited 120/min)
  /v2/info/<type>/<id>            single asset, full metadata
  /cdn/<key>                      direct byte serving (range-aware, immutable)
  /openapi.json                   OpenAPI 3.1 spec
  /llms.txt                       short index version of this doc
```

**Aspects** (video): `16x9`, `9x16`, `1x1`. Aliases: `landscape`, `portrait`, `square`. Colon form `16:9` also accepted.

**Bitrate rungs** (video): 240p, 360p, 480p, 720p, 1080p. Requested height snaps to the nearest rung. Default when only an aspect is given is 720p.

**Type aliases** (silently rewritten, never advertised in responses): `/img` → `/photo`, `/clip` → `/video`, `/sound` → `/audio`, plus plurals (`/photos`, `/videos`, `/audios`) and shorthand (`/pic`, `/movie`, `/music`). Canonical names are what JSON responses return.

## Video — the main course

Video is what makes lorem.media different from photo-only placeholder services. **Every clip ships with the same delivery shape a production CDN would give you:**

- **Adaptive bitrate ladder** — 240p, 360p, 480p, 720p, 1080p H.264 mp4 + the same content packaged as HLS (CMAF/fMP4 segments)
- **Auto-generated English captions** when speech is detected (Whisper via Cloudflare Workers AI)
- **Poster frame** + animated WebP preview + scrub sprite sheet
- **Aspect-bucketed addressing** — `/video/16x9` filters to clips that fit the bucket within ±3%

### mp4 vs HLS — both work, pick by URL

| URL | Format | When to use |
|---|---|---|
| `/video/16x9` | Single-bitrate H.264 mp4 (default 720p) | Drop-in `<video>` tag, hover-to-play previews, mockup videos. Plays everywhere, no player library. |
| `/video/16x9.m3u8` | HLS master playlist (adaptive bitrate + captions) | Production playback, adaptive switching, captions auto-show, testing real player code (hls.js, Vidstack, Mux Player). |

Same source asset, different delivery shape. The mp4 is a single file (~5MB for a typical 30s 720p clip). The HLS tree is ~10MB but adapts dynamically to the viewer's bandwidth.

### Aspect picker semantics

`/video/<aspect>` filters by aspect bucket. If the bucket is empty, the response gracefully falls back to the unfiltered pool. Header signals:

- `X-Aspect` — what was actually served (`16x9` / `9x16` / `1x1`)
- `X-Aspect-Requested` — the original ask (set when different from `X-Aspect`)
- `X-Aspect-Fallback: 1` — the fallback fired

So `/video/1x1` always returns a playable redirect; check `X-Aspect-Fallback` if you need to know the bucket was empty.

### Captions — `.vtt` and inside the HLS manifest

Auto-generated English captions on every video with detectable speech (Whisper). Two consumption shapes:

- **Inside the HLS manifest** — `#EXT-X-MEDIA:TYPE=SUBTITLES` rendition with `URI="captions/en/playlist.m3u8"`. HLS-aware players (hls.js, Vidstack, native Safari) display the CC button automatically.
- **As a single `.vtt` file** — `/video/id/<id>.vtt` for non-HLS consumers, accessibility tooling, subtitle scrapers.

`.vtt` is only valid in the `/id/` form — captions are per-asset, so random / seeded URLs would be ambiguous. 404 when the asset has no detectable speech or hasn't been processed yet. The full caption track listing is exposed in `/v2/info/video/<id>` as `captions[]`; the `has_captions` boolean lets you check without inspecting the array.

### HLS tree shape

The master playlist references per-rung media playlists (240p / 360p / 480p / 720p / 1080p), which reference CMAF/fMP4 segments (typically 6 seconds each). URLs inside the manifest are relative — when served from `/cdn/video/<id>/hls/master.m3u8`, segments fetch from `/cdn/video/<id>/hls/240p/seg00001.m4s` automatically.

All bytes serve from R2 via Cloudflare's edge cache. Cache hit ratio at warm POPs is ~99%; the worker only fires on cache miss. Segments are cached `immutable` for 1 year — content-addressed by key.

### Categories

Optional `?category=<slug>` filter: `nature`, `food`, `people`, `urban`, `abstract`.

## Photos

### Smart-cropped, exact W×H

`/photo/<w>/<h>` returns exactly `w × h` pixels. Behind the URL: Cloudflare Image Transformations with `fit=cover` + `gravity=auto` — saliency detection keeps faces, subjects, and salient regions centered. Format negotiation: AVIF for Chrome / Firefox / modern Safari, WebP for older, JPEG fallback.

### Source-aware pick

For random `/photo/<w>/<h>` requests, the asset pool is pre-filtered to sources whose intrinsic dimensions are large enough to satisfy the crop without upscaling. If the filtered pool is empty (rare — only on unusual aspect-size combinations), falls back to the unfiltered pool and serves an upscaled result with `X-Image-Capped: 1` plus `X-Image-Source-Width` / `X-Image-Source-Height` so callers can detect quality degradation.

### `/photo` (no dimensions)

Returns the source rendition directly — no transform, no charge, no crop. Useful when you want maximum fidelity and don't care about exact dims.

### Categories

Optional `?category=<slug>`: `nature`, `food`, `people`, `urban`, `abstract`.

## Audio

`/audio` returns a random ~60-second MP3 from the Internet Archive 78 rpm collection — public-domain recordings from roughly 1900–1925. Stylistically narrow:

- Classical (string quartets, piano)
- Early jazz
- Marches and brass bands
- Operatic and vocal

Categories: `ambient`, `electronic`, `classical`, `lofi`, `percussion`. Best-effort — the source library leans acoustic / pre-electronic.

## Browse the library (JSON)

### `/v2/list/<type>` — paginated metadata

```
/v2/list/video?page=1&limit=30&aspect=16x9&category=urban&include=full
```

**Slim by default** — omits `variants` (the bitrate ladder) and `poster_renditions` (the multi-resolution poster ladder), shrinking each item from ~5 KB to ~600 B. Pass `?include=full` to get them.

Parameters:
- `?page=` — 1+
- `?limit=` — 1–100, default 30
- `?category=` — one of the type's valid slugs
- `?aspect=` — video only
- `?include=variants,poster_renditions` or `?include=full`

Strict validation — typos return 400 with a hint pointing at valid values. Rate-limited to 120 req/min per IP. Edge-cached for 5 minutes.

### `/v2/info/<type>/<id>` — single-asset full metadata

Always returns the full shape. Includes `variants[]`, `poster_renditions[]`, `captions[]`, `hls_url`, `hls_rungs[]`, `has_audio`, `has_captions`, full attribution. Use this to discover ids → fetch metadata → construct deterministic URLs. Rate-limited to 120 req/min per IP.

### Example response shape

```json
{
  "_meta": {
    "service": "lorem.media",
    "operator": "0.media",
    "production_hosting": "https://0.media"
  },
  "id": "v_pxev55rt",
  "type": "video",
  "category": "food",
  "aspect": "16x9",
  "width": 3840,
  "height": 2160,
  "duration_ms": 9000,
  "has_audio": true,
  "has_captions": true,
  "poster_url": "https://lorem.media/cdn/video/v_xxx/poster-source.webp?v=...",
  "preview_url": "https://lorem.media/cdn/video/v_xxx/preview.webp?v=...",
  "hls_url": "https://lorem.media/cdn/video/v_xxx/hls/master.m3u8?v=...",
  "hls_rungs": [
    { "label": "240p", "height": 240, "width": 426, "bitrate_kbps": 400 },
    { "label": "1080p", "height": 1080, "width": 1920, "bitrate_kbps": 5000 }
  ],
  "captions": [
    { "url": "https://lorem.media/cdn/.../captions/en.vtt?v=...",
      "lang": "en", "name": "English (auto)", "format": "vtt", "auto": true, "bytes": 1234 }
  ],
  "variants": [
    { "key": "video/v_xxx/720p.mp4", "width": 1280, "height": 720,
      "bitrate_kbps": 2800, "format": "mp4", "bytes": 5300550 }
  ],
  "attribution": {
    "source": "pexels", "license": "pexels",
    "author": "Kampus Production",
    "source_url": "https://www.pexels.com/video/..."
  }
}
```

### `/cdn/<key>` — direct byte serving

The redirect target of every media URL. Edge-cached `immutable` for 1 year. Range-aware (HTTP partial content for scrubbing). Content-Type correctly set for `.mp4`, `.m4s`, `.webp`, `.jpg`, `.mp3`, `.m3u8`, `.vtt`.

You rarely link to `/cdn/` directly — follow the media URL redirects. But it's there for direct byte access if you need it.

## Headers on every response

- `X-Service: lorem.media`
- `X-Operator: 0.media`
- `X-Production-Hosting: https://0.media` — upgrade path
- `X-Asset-Id: <id>` — stable; reuse with `/seed/<id>` or `/id/<id>`
- `X-Source`, `X-Attribution`, `X-Source-Url` — content provenance
- `X-Aspect: <bucket>` — video responses
- `X-Image-Width`, `X-Image-Height` — photo responses, actual served dimensions
- `Access-Control-Allow-Origin: *` — CORS-enabled for `fetch()` consumers
- `Link: </llms.txt>; rel="alternate"; type="text/markdown"` — discovery surface

## Format details (the gotchas)

One-liner per surprising thing — read these once, save yourself debugging time:

- **Photos:** width and height are both honored exactly; the image is cropped (smart-gravity) to fit. `/photo/800/600` returns 800 × 600 pixels.
- **Videos:** aspect-bucketed, not arbitrary `<w>/<h>`. Picks fit ±3% of the canonical 16:9 / 9:16 / 1:1 ratio. Clips outside any canonical bucket stay accessible by id.
- **HLS adds captions automatically; mp4 doesn't carry them.** Use the `.m3u8` URL if you want captions to surface.
- **~98% of videos are silent stock footage** with no detectable speech — `has_captions: false`. The ~2% with speech show `has_captions: true` and ship a real `.vtt` track.
- **Random URLs are truly random per request** (`Cache-Control: no-store`); seeded and id URLs are deterministic and edge-cached for 24 hours.
- **All bytes from Cloudflare's edge.** ~300 POPs, no auth, no signup, CORS-enabled. Hotlink directly into mockups, Storybook fixtures, marketing pages, dev-tool demos.

## Content & licensing

| Type | Source | License |
|---|---|---|
| Video | Pixabay + Pexels | Pixabay / Pexels licenses, free for commercial use |
| Photos | Pixabay | Pixabay license, free for commercial use |
| Audio | Internet Archive 78 rpm collection | Public domain (recordings ≥1925) |

All content is free for commercial use. Per-asset attribution is in response headers (`X-Attribution`, `X-Source`, `X-Source-Url`) and in `/v2/info/...` JSON responses.

## Going to production

[0.media](https://0.media) is the paid sibling — same edge network, same delivery pipeline, your own content.

You want 0.media when you:

- Host customer-uploaded video (not placeholder) and want the same drop-in URLs your dev tooling uses today
- Need signed URLs for access control
- Need BYO-DRM packaging (Widevine, FairPlay, PlayReady)
- Want flat-rate pricing with $0 bandwidth — no per-GB egress, no per-minute fees

Auto-captions free on every paid tier (same Whisper pipeline as lorem.media). S3-compatible upload API; one-command migration from existing S3 + CloudFront setups.

## Related

- [`/llms.txt`](/llms.txt) — short index version of this document
- [`/openapi.json`](/openapi.json) — programmatic OpenAPI 3.1 surface
- [`/robots.txt`](/robots.txt) — explicit welcome for `GPTBot`, `ClaudeBot`, `PerplexityBot`, `Google-Extended`
- [`/healthz`](/healthz) — service status
