URL Media Inspector — Complete API Reference
Purpose: This is a single-page, self-contained reference for the URL Media Inspector API. It contains everything needed to understand, integrate, and use the API programmatically.
Overview
URL Media Inspector is a REST API that inspects public URLs and returns structured media intelligence — MIME type, media category, playback strategy, metadata, thumbnails, blurhash placeholders, color palettes, and provider-native intelligence (YouTube, Vimeo, etc.).
Base URL:
https://api.urlmediainspector.devSwagger UI: https://api.urlmediainspector.dev/api-docs
OpenAPI Spec: https://api.urlmediainspector.dev/openapi.json
Authentication
No authentication is required. The API is publicly accessible.
Endpoints
POST /api/v1/inspect
Inspect a public URL and receive structured media metadata.
Query Parameters
| Parameter | Type | Default | Description |
|---|---|---|---|
profile | string | standard | Enrichment depth: fast, standard, full, or embed |
fields | string | — | Comma-separated list of data namespaces to include (e.g., identity,semantic) |
expand | string | — | Comma-separated list of extra data to include (e.g., rawHeaders) |
debug | boolean | false | Include debug/network information when true |
Request Body
{
"url": "https://example.com/media.mp4",
"webhookUrl": "https://your-server.com/webhook"
}| Field | Type | Required | Description |
|---|---|---|---|
url | string (URI) | ✅ Yes | The public URL to inspect. Must be http: or https:. |
webhookUrl | string (URI) | No | Optional webhook URL to receive async enrichment completion events. |
Profiles
Profiles control payload richness and latency behavior. They are a pure view layer — they never mutate the underlying resource state.
| Profile | Latency | HTTP Status | Behavior |
|---|---|---|---|
fast | Minimal | 200 | Returns immediately with lightweight metadata. Hides heavy preview fields for direct media. Provider-native previews (e.g., YouTube thumbnails) are preserved. |
standard | Progressive | 200 or 202 | Returns partial results immediately. If heavy enrichments are pending, returns 202 Accepted with a job reference. Pending fields show as null. |
full | Deterministic | 200 | Blocks synchronously until ALL enrichments complete. Always returns completion: 1. Includes diagnostics. |
embed | Minimal | 200 | Specialized profile for UI rendering. Returns embed capabilities, iframe parameters, and verification status rather than media enrichments. |
Fields Parameter
Use fields to request only specific data namespaces. The value is a comma-separated list of top-level data namespace names.
Available namespaces: identity, semantic, provider, playback, metadata, preview, diagnostics
Example: ?profile=full&fields=identity,metadata returns only data.identity and data.metadata.
When fields is omitted, all namespaces applicable to the profile are returned.
GET /api/v1/job/{id}
Check the status of an asynchronous enrichment job.
Path Parameters
| Parameter | Type | Description |
|---|---|---|
id | string | The job ID returned in a 202 response |
Response
{
"success": true,
"job": {
"id": "abc123def456",
"status": "completed",
"progress": 100,
"createdAt": "2025-01-01T00:00:00.000Z",
"completedAt": "2025-01-01T00:00:05.000Z"
},
"data": { }
}Job status values: waiting, active, completed, failed, delayed.
Response Envelope
Every successful inspect response follows this structure:
{
// Layer 1: Lifecycle Truth
"success": true,
"responseSource": "fresh", // "fresh" | "cache" | "inflight"
"cached": false,
// Layer 2: Processing State (canonical, profile-independent)
"processing": {
"status": "complete", // "partial" | "complete"
"completion": 1, // 0.0 → 1.0
"enrichmentsPending": false,
"completedStages": ["semantic", "mediaFormat", "metadata:image", "preview:thumbnail", "preview:blurhash", "preview:palette"],
"pendingStages": []
},
// Layer 3: Data Payload (profile-projected)
"data": {
"identity": { },
"semantic": { },
"provider": { }, // only for provider-detected URLs (YouTube, Vimeo, etc.)
"playback": { },
"metadata": { },
"preview": { },
"diagnostics": { } // only in FULL profile
},
// Layer 4: Enrichment Provenance (immutable)
"enrichmentSources": {
"semantic": "fresh",
"mediaFormat": "fresh"
},
// Layer 5: Projection Metadata
"projection": {
"profile": "full",
"hiddenFields": []
},
// Layer 6: Async Job (only for 202 responses)
"job": {
"id": "job_abc123",
"status": "processing"
}
}Data Namespaces
identity
Always present. Contains URL resolution information.
{
"identity": {
"url": "https://youtu.be/GLTTI1NFHus", // original input
"finalUrl": "https://youtu.be/GLTTI1NFHus", // after redirect resolution
"canonicalUrl": "https://www.youtube.com/watch?v=GLTTI1NFHus", // normalized (provider URLs only)
"mime": "text/html"
}
}semantic
Always present. Classifies the media type.
{
"semantic": {
"kind": "direct-media", // "direct-media" | "embed-media" | "webpage"
"semanticType": "video", // "video" | "audio" | "image" | "embedded-video" | "embedded-audio"
"title": "Video Title" // when available (provider-native or extracted)
}
}provider
Present only for provider-detected URLs (YouTube, Vimeo, Spotify, etc.).
{
"provider": {
"name": "youtube",
"confidence": 1,
"detectionMethod": "url-parser"
}
}playback
Always present. Describes how to render/play the media.
{
"playback": {
"strategy": "native", // "native" (direct playback) | "iframe" (embedded)
"component": "video", // "video" | "audio" | "img" | "iframe"
"embedUrl": "https://...", // for iframe strategy only
"streamable": true, // supports HTTP Range requests
"downloadable": true // direct download possible
}
}metadata
Always present. Contains file info and exactly one category-specific sub-object.
// Image
{
"metadata": {
"filename": "photo.jpg",
"size": { "bytes": 204800, "formatted": "200 KB" },
"image": { "colorSpace": "srgb", "hasAlpha": false }
}
}
// Video
{
"metadata": {
"filename": "video.mp4",
"size": { "bytes": 56733664, "formatted": "54.1 MB" },
"video": {
"duration": { "seconds": 596, "formatted": "9:56" },
"fps": 24,
"bitrate": { "bps": 1000000, "formatted": "1 Mbps" }
}
}
}
// Audio
{
"metadata": {
"filename": "song.mp3",
"size": { "bytes": 5242880, "formatted": "5 MB" },
"audio": {
"duration": { "seconds": 180, "formatted": "3:00" },
"bitrate": { "bps": 128000, "formatted": "128 kbps" },
"sampleRate": 44100
}
}
}Cross-category sub-objects are mutually exclusive — video never has metadata.image, audio never has metadata.video.
preview
Contains visual enrichments. Varies by media category and profile.
// Image/Video (FULL profile, complete)
{
"preview": {
"thumbnail": { "url": "/public/thumbnails/generated.jpg", "dimensions": "320x180" },
"placeholder": { "type": "blurhash", "value": "LEHV6nWB2yk8pyo0adR*.7kCMdnj" },
"palette": { "dominant": "#112233" },
"dominantColor": "#112233"
}
}
// YouTube (provider-native thumbnail)
{
"preview": {
"thumbnail": { "url": "https://i.ytimg.com/vi/GLTTI1NFHus/hqdefault.jpg" }
}
}
// Audio (visual enrichments omitted, future fields are null)
{
"preview": {
"waveform": null,
"spectrogram": null
}
}Preview visibility by profile:
| Profile | Direct Media | Provider Media |
|---|---|---|
fast | preview is hidden (hiddenFields: ["preview"]) | Provider-native thumbnails are preserved |
standard | null if pending, populated if complete | Populated |
full | Always populated (blocks until complete) | Populated |
diagnostics
Only included in full profile. Contains performance timings.
{
"diagnostics": {
"timings": {
"total": 1240,
"semantic": 12,
"mediaFormat": 85,
"preview": 890
}
}
}Nullability Semantics
The API uses strict nullability rules:
| Value | Meaning |
|---|---|
null | Supported enrichment, but not yet available (pending or planned). |
| Omitted | Unsupported for this media category (e.g., thumbnail on audio). |
Empty {} | FORBIDDEN — never returned. |
| Populated | Enrichment complete, data available. |
HTTP Status Codes
| Status | Condition |
|---|---|
200 | Inspection complete (or served from cache) |
202 | Enrichments in progress, job reference included (STANDARD profile, heavy media) |
400 | Invalid URL (INVALID_URL) or malformed payload (INVALID_PAYLOAD) |
403 | SSRF protection blocked the request (SSRF_BLOCKED) |
404 | Remote server returned 404 (RESOURCE_NOT_FOUND) |
408 | Remote server timeout (UPSTREAM_TIMEOUT) |
413 | Remote file exceeds size limit (PAYLOAD_TOO_LARGE) |
502 | Remote server returned invalid response (BAD_GATEWAY) |
Error Response Format
{
"status": "error",
"error": {
"code": "INVALID_URL",
"message": "The provided URL string was malformed."
}
}Media Categories
The API detects and handles these media categories with category-specific enrichments:
| Category | Enrichments | Example MIME |
|---|---|---|
| Image | thumbnail, blurhash, palette, image metadata | image/jpeg, image/png, image/webp |
| Video | thumbnail, blurhash, palette, video metadata (duration, fps, bitrate) | video/mp4, video/webm |
| Audio | audio metadata (duration, bitrate, sample rate). No visual enrichments. | audio/mpeg, audio/ogg |
| Embedded Video | Provider-native thumbnail, oEmbed data, embed URL | text/html (YouTube, Vimeo) |
Supported Providers
Provider-detected URLs receive enhanced intelligence via oEmbed and URL parsing:
| Provider | Detection | Intelligence |
|---|---|---|
| YouTube | URL pattern matching | Canonical URL, native thumbnail, oEmbed title, embed URL |
| Vimeo | URL pattern matching | Native thumbnail, oEmbed data, embed URL |
| Spotify | URL pattern matching | oEmbed data, embed URL |
Provider-native data (thumbnails, titles) is available immediately — even in fast profile — because it doesn't require heavy enrichment processing.
curl Examples
Inspect an image (FAST)
curl -X POST "https://api.urlmediainspector.dev/api/v1/inspect?profile=fast" \
-H "Content-Type: application/json" \
-d '{"url": "https://images.unsplash.com/photo-1707343843437-caacff5cfa74"}'Inspect a video (FULL, with all enrichments)
curl -X POST "https://api.urlmediainspector.dev/api/v1/inspect?profile=full" \
-H "Content-Type: application/json" \
-d '{"url": "https://download.blender.org/peach/bigbuckbunny_movies/BigBuckBunny_320x180.mp4"}'Inspect a YouTube URL
curl -X POST "https://api.urlmediainspector.dev/api/v1/inspect?profile=fast" \
-H "Content-Type: application/json" \
-d '{"url": "https://www.youtube.com/watch?v=dQw4w9WgXcQ"}'Inspect with specific fields only
curl -X POST "https://api.urlmediainspector.dev/api/v1/inspect?profile=full&fields=identity,metadata" \
-H "Content-Type: application/json" \
-d '{"url": "https://example.com/video.mp4"}'Check async job status
curl "https://api.urlmediainspector.dev/api/v1/job/abc123def456"Complete Response Examples
Image — FULL Profile (Complete)
{
"success": true,
"responseSource": "fresh",
"cached": false,
"processing": {
"status": "complete",
"completion": 1,
"enrichmentsPending": false,
"completedStages": ["semantic", "mediaFormat", "metadata:image", "preview:thumbnail", "preview:blurhash", "preview:palette"],
"pendingStages": []
},
"data": {
"identity": {
"url": "https://www.geekygallery.in/cdn/shop/files/god_16.jpg?v=1726037386&width=1946",
"finalUrl": "https://www.geekygallery.in/cdn/shop/files/god_16.jpg?v=1726037386&width=1946",
"mime": "image/jpeg"
},
"semantic": { "kind": "direct-media", "semanticType": "image" },
"playback": { "strategy": "native", "component": "img", "streamable": false, "downloadable": true },
"metadata": {
"filename": "god_16.jpg",
"size": { "bytes": 204800, "formatted": "200 KB" },
"image": { "colorSpace": "srgb", "hasAlpha": false }
},
"preview": {
"thumbnail": { "url": "/public/thumbnails/generated.jpg", "dimensions": "320x180" },
"placeholder": { "type": "blurhash", "value": "LEHV6nWB2yk8pyo0adR*.7kCMdnj" },
"palette": { "dominant": "#112233" },
"dominantColor": "#112233"
},
"diagnostics": {
"timings": { "total": 1240, "semantic": 12, "mediaFormat": 85 }
}
},
"enrichmentSources": {
"semantic": "fresh",
"mediaFormat": "fresh",
"metadata:image": "fresh",
"preview:thumbnail": "fresh",
"preview:blurhash": "fresh",
"preview:palette": "fresh"
},
"projection": { "profile": "full", "hiddenFields": [] }
}YouTube — FAST Profile (Provider-Native)
{
"success": true,
"responseSource": "fresh",
"cached": false,
"processing": {
"status": "complete",
"completion": 1,
"enrichmentsPending": false,
"completedStages": ["semantic", "mediaFormat", "oembed"],
"pendingStages": []
},
"data": {
"identity": {
"url": "https://youtu.be/GLTTI1NFHus?si=okdNMy6_p0VSqZJG",
"finalUrl": "https://youtu.be/GLTTI1NFHus?si=okdNMy6_p0VSqZJG",
"canonicalUrl": "https://www.youtube.com/watch?v=GLTTI1NFHus",
"mime": "text/html"
},
"semantic": { "kind": "embed-media", "semanticType": "embedded-video", "title": "Provider Native Title" },
"provider": { "name": "youtube", "confidence": 1, "detectionMethod": "url-parser" },
"playback": {
"strategy": "iframe",
"component": "iframe",
"embedUrl": "https://www.youtube.com/embed/GLTTI1NFHus",
"streamable": false,
"downloadable": false
},
"preview": {
"thumbnail": { "url": "https://i.ytimg.com/vi/GLTTI1NFHus/hqdefault.jpg" }
}
},
"enrichmentSources": { "semantic": "fresh", "mediaFormat": "fresh", "oembed": "fresh" },
"projection": { "profile": "fast", "hiddenFields": [] }
}Video — STANDARD Profile (Async / 202 Response)
{
"success": true,
"responseSource": "fresh",
"processing": {
"status": "partial",
"completion": 0.33,
"enrichmentsPending": true,
"completedStages": ["semantic", "mediaFormat"],
"pendingStages": ["metadata:video", "preview:thumbnail", "preview:blurhash", "preview:palette"]
},
"data": {
"identity": {
"url": "https://download.blender.org/peach/bigbuckbunny_movies/BigBuckBunny_320x180.mp4",
"finalUrl": "https://download.blender.org/peach/bigbuckbunny_movies/BigBuckBunny_320x180.mp4",
"mime": "video/mp4"
},
"semantic": { "kind": "direct-media", "semanticType": "video" },
"playback": { "strategy": "native", "component": "video", "streamable": true, "downloadable": true },
"metadata": { "filename": "BigBuckBunny_320x180.mp4", "size": { "bytes": 56733664, "formatted": "54.1 MB" } }
},
"message": "Initial inspection complete. Heavy enrichments started in background.",
"job": { "id": "a1b2c3d4e5", "status": "processing" },
"projection": { "profile": "standard", "hiddenFields": [] }
}Audio — FULL Profile (Complete)
{
"success": true,
"responseSource": "fresh",
"processing": {
"status": "complete",
"completion": 1,
"enrichmentsPending": false,
"completedStages": ["semantic", "mediaFormat", "metadata:audio"],
"pendingStages": []
},
"data": {
"identity": {
"url": "http://commondatastorage.googleapis.com/codeskulptor-assets/Epoq-Lepidoptera.ogg",
"finalUrl": "http://commondatastorage.googleapis.com/codeskulptor-assets/Epoq-Lepidoptera.ogg",
"mime": "audio/ogg"
},
"semantic": { "kind": "direct-media", "semanticType": "audio" },
"playback": { "strategy": "native", "component": "audio", "streamable": true, "downloadable": true },
"metadata": {
"filename": "Epoq-Lepidoptera.ogg",
"size": { "bytes": 1024, "formatted": "1 KB" },
"audio": {
"duration": { "seconds": 30, "formatted": "0:30" },
"bitrate": { "bps": 128000, "formatted": "128 kbps" },
"sampleRate": 44100
}
},
"preview": {
"waveform": null,
"spectrogram": null
}
},
"projection": { "profile": "full", "hiddenFields": [] }
}YouTube — EMBED Profile (Renderability Context)
{
"success": true,
"profile": "embed",
"data": {
"embedType": "embedded-video",
"mime": "text/html",
"provider": "youtube",
"renderability": {
"native": false,
"iframe": { "supported": "verified", "confidence": 1 },
"embeddable": { "status": "verified" }
},
"verification": {
"providerVerified": true,
"registryVerified": true,
"networkVerified": true,
"method": "registry"
},
"render": {
"strategy": "iframe",
"safeEmbed": true,
"renderConfidence": { "level": "verified", "score": 0.95 },
"tag": "iframe",
"attributes": {
"src": "https://www.youtube.com/embed/GLTTI1NFHus",
"allowfullscreen": true
}
}
}
}Security Constraints
- SSRF Protection: All target URLs are resolved and verified against an IP denylist (localhost, private subnets, metadata endpoints). Redirect chains are re-verified at every hop.
- Protocols: Only
http:andhttps:URLs are accepted. - Size Limit: Remote files exceeding 50 MB are rejected.
- Timeouts: Outbound requests timeout after 10 seconds.
- Headers: Identifying headers are stripped from outbound requests.
Integration Patterns
Pattern 1: Quick metadata check (FAST)
Use fast profile when you only need basic media type detection, MIME, and playback strategy. Ideal for URL previews and media classification.
Pattern 2: Full enrichment (FULL)
Use full profile when you need complete metadata including thumbnails, blurhash, palettes, and codec details. The request blocks until everything is ready.
Pattern 3: Async enrichment (STANDARD)
Use standard profile for heavy media (video, large images). The API returns partial results immediately (202) with a job.id. Poll GET /api/v1/job/{id} for completion, or provide a webhookUrl to receive a callback when enrichments finish.
POST /api/v1/inspect?profile=standard → 202 { job: { id: "xyz" } }
GET /api/v1/job/xyz → 200 { job: { status: "completed" }, data: { ... } }