Video Assets Server REST API guide
A small JSON API over HTTPS. No key is needed on https://video.shep.bot. Every endpoint is described in the OpenAPI 3.1 spec, which you can explore and try in the interactive reference.
Base URL: https://video.shep.bot
The flow
GET /v1/search: find candidates across every source.GET /v1/clips/{id}: read the licence verdict, credit line, video facts, renditions and thewatchURL of one result.GET /v1/clips/{id}/download?height=1080: a302to the file on the source.
Plus GET /v1/clips/{id}/credits for ATTRIBUTION.md, and POST /v1/sequences to plan a whole b-roll edit in one call.
No video passes through this server: posters, previews, watch URLs and downloads all point at the source’s own servers.
Search
curl "https://video.shep.bot/v1/search?q=ocean+waves&orientation=portrait&min_resolution=1080&commercial=true&limit=10"
| Parameter | Description |
|---|---|
q |
Free text, written like an editor would: ocean waves at sunset, drone city skyline night, rocket launch slow motion. Resolution (4k, 1080p), orientation (vertical, 9:16, square), length (10s, under 30 seconds, 1-2 min), frame rate (60fps) and style (timelapse) words become filters. A single length like 10s means at least 10 seconds. May be empty when category is set (browse). |
category |
Comma-separated dot-path categories. A parent matches its children: nature matches nature.ocean and nature.sky. See the Category schema for the full list (nature.ocean, nature.sky, city.skyline, aerial, space.earth, space.launch, science, background.abstract, overlay.light, greenscreen, archive.newsreel…). |
style |
Comma-separated: timelapse, slow-motion, loop, animation, visualization, black-white. Ranked first; filtered only with strict=true, because sources rarely tag them. |
orientation |
landscape, portrait or square (comma-separated for several). |
min_resolution |
Minimum short side in pixels: 720, 1080, 1440, 2160 (or hd, fhd, 4k). A vertical 1080×1920 clip counts as 1080. |
min_duration, max_duration |
Length in seconds. |
min_fps |
Minimum frame rate, e.g. 50 for slow-motion headroom. |
audio |
true or false: only clips with or without a sound track. |
commercial |
true: only clips whose licence allows commercial use (verdict yes or yes-with-credit). Editorial-only clips can still appear: check license.editorial. |
free |
true: exclude paid clips. |
downloadable |
true: only clips with direct file links. |
strict |
true: drop clips whose orientation, resolution, length, fps or audio is unknown when you filter on it (by default they are kept, ranked lower). |
providers |
Comma-separated source ids from /v1/providers, e.g. nasa,wikimediacommons. |
limit |
Max results, 1–100 (default 24). |
offset |
Per-source offset for the next page (e.g. 24). |
The response (shortened):
{
"query": "ocean waves",
"filters": { "orientations": ["portrait"], "minResolution": 1080, "commercialOnly": true },
"results": [
{
"id": "nasa:GSFC_20161212_Tidal_m12450_Magnetic",
"provider": "nasa",
"title": "Ocean Tides and Magnetic Fields",
"url": "https://images.nasa.gov/details/GSFC_20161212_Tidal_m12450_Magnetic",
"category": "nature.ocean",
"orientation": "landscape",
"width": 1920, "height": 1080, "fps": 29.97, "durationSec": 53, "hasAudio": true, "year": 2016,
"posterUrl": "https://images-assets.nasa.gov/video/…/…~large.jpg",
"previewUrl": "https://images-assets.nasa.gov/video/…/…~preview.mp4",
"previewType": "video/mp4",
"license": {
"id": "LicenseRef-NASA-Media", "name": "NASA media (public domain in the US)",
"attributionRequired": true, "editorial": true, "assertedBy": "source",
"verdict": "yes-with-credit",
"note": "Generally not copyrighted in the US; credit NASA. Do not imply NASA endorses you…"
},
"attribution": "“Ocean Tides and Magnetic Fields” by NASA GSFC (…) (NASA Image and Video Library), https://images.nasa.gov/details/…",
"downloadable": true
}
],
"providers": [
{ "provider": "nasa", "name": "NASA Image and Video Library", "status": "ok", "count": 6, "tookMs": 412 },
{ "provider": "pexels", "name": "Pexels", "status": "link", "count": 0, "searchUrl": "https://www.pexels.com/search/videos/ocean%20waves/" }
]
}
posterUrl and previewUrl are meant for <img> and <video> tags in a browser: they load straight from the source. Some results have no previewUrl (Internet Archive search results, until their file list is read) or no posterUrl (some NASA visualisation pages).
Licence verdicts
Every license has a verdict that answers one question: can this go into a commercial video (an ad, a monetised channel, a client project)?
verdict |
Meaning |
|---|---|
yes |
Commercial use OK, no credit required (CC0, public domain). |
yes-with-credit |
Commercial use OK with a credit line: use attribution (CC BY, NASA media). |
restricted |
Conditions that usually rule out a normal project (share-alike). |
clearance-required |
Paid stock, or footage that needs releases per use. |
check-terms |
Unclear: no licence stated, uploader-asserted, or possibly third-party footage. |
no |
Non-commercial or no-derivatives only. |
license.editorial: true means people, insignia or brands appear without releases: fine for news, documentary and education, not for advertising. license.assertedBy says who stated the licence: source (the site records it per item), site (one licence for the whole site) or uploader (an unverified claim; such licences are never better than check-terms). More in Video licences explained.
Clip details
curl "https://video.shep.bot/v1/clips/nasa%3AGSFC_20161212_Tidal_m12450_Magnetic"
Ids contain : and sometimes spaces or slashes: URL-encode them. Returns the clip plus:
files[]: every file the source offers, each withurl,filename,format(mp4,webm,ogv,mov,jpg…),mime,role(original,rendition,preview,poster,captions),width,height,fps,codecandsizeByteswhen known.watch:{ url, type, width, height }, the file a browser should play (a browser-playable container close to 720p). Put it in a<video>tag; it streams from the source.shareUrl: a link that opens the clip on the website (/search?clip=<id>), which unfurls with its own preview card.
Choose files
curl "https://video.shep.bot/v1/clips/<id>/files?height=1080&format=mp4"
The file a download would pick: with height, the smallest file whose short side reaches it (a 1080p rendition rather than a 4K master), else the biggest below it; format prefers a container, role a kind of file. Returns totalBytes, the licence and the credit line. all=true returns every file.
Download
curl -L -o clip.mp4 "https://video.shep.bot/v1/clips/<id>/download?height=1080"
Always a 302 redirect to the chosen file on the source’s CDN (so use -L), with X-License and X-Attribution headers. The same height, format and role parameters as /files choose the file. Clips without direct files answer 409 with the clip’s url.
Credits
curl "https://video.shep.bot/v1/clips/<id>/credits" # ATTRIBUTION.md
curl "https://video.shep.bot/v1/clips/<id>/credits?format=json" # credits.json
curl -o credits.zip "https://video.shep.bot/v1/clips/<id>/credits?format=zip"
The zip holds ATTRIBUTION.md and credits.json only, never the video.
Plan a sequence
curl -X POST "https://video.shep.bot/v1/sequences" -H "content-type: application/json" \
-d '{"shots":["city skyline at night (4s)","coffee pour close-up (3s)","people walking in the rain"],"orientation":"portrait","fps":30,"totalDuration":15,"name":"promo"}'
shots is a list, or text with one shot per line (“(3s)” at the end sets a shot’s length; the others share totalDuration, or get 4 s each). Optional: orientation or width/height, fps (default 30), commercialOnly (default true), providers, alternatives (0–5, default 3), name. Up to 24 shots.
The response is { plan, exports }. plan.shots[] has, per shot, index, description, durationSec, inSec (the in-point; long archive films skip their opening), clip, file (the rendition the exports use), alternatives and a note when the pick is shorter, smaller or a different shape. plan.commercialSafe is true when every pick is yes or yes-with-credit. exports holds the text of timeline.otio, concat.sh, remotion.json, ATTRIBUTION.md and credits.json. The sequence builder is a page on top of this endpoint.
List sources
curl "https://video.shep.bot/v1/providers"
Usage statistics and source health
curl "https://video.shep.bot/v1/stats"
Aggregate usage (searches by surface, clip views, downloads, MCP tool calls, top clients and categories) for the last 24 hours and 7 days, plus each source’s success rate and p50/p95 latency. The same data is on the statistics page. GET /v1/catalog returns the daily count of what each source holds.
Examples
JavaScript / TypeScript
const base = "https://video.shep.bot";
const res = await fetch(`${base}/v1/search?` + new URLSearchParams({ q: "clouds timelapse", min_resolution: "1080", commercial: "true" }));
const { results } = await res.json();
const pick = results.find((c) => c.downloadable && !c.license?.editorial);
console.log(pick.title, pick.license.verdict, pick.attribution ?? "no credit needed");
// Play it in the page, straight from the source
const details = await (await fetch(`${base}/v1/clips/${encodeURIComponent(pick.id)}`)).json();
const video = Object.assign(document.createElement("video"), { src: details.watch.url, poster: pick.posterUrl, controls: true, playsInline: true });
document.body.append(video);
Python
import requests
base = "https://video.shep.bot"
r = requests.get(f"{base}/v1/search", params={"q": "rocket launch", "min_resolution": "1080", "commercial": "true"})
clip = next(c for c in r.json()["results"] if c["downloadable"])
# The download endpoint redirects to the file on the source; requests follows it.
with requests.get(f"{base}/v1/clips/{requests.utils.quote(clip['id'], safe='')}/download", params={"height": 1080}, stream=True) as dl:
dl.raise_for_status()
with open("launch.mp4", "wb") as f:
for chunk in dl.iter_content(1 << 20):
f.write(chunk)
print(clip.get("attribution", "no credit needed"))
ffmpeg
# A 5-second cut from the 1080p rendition, read straight from the source (ffmpeg follows the redirect)
ffmpeg -ss 2 -i "https://video.shep.bot/v1/clips/<id>/download?height=1080" -t 5 -c:v libx264 -crf 18 -an cut.mp4
Errors
Errors are JSON: { "error": "message" }.
| Status | Meaning |
|---|---|
400 |
Bad parameter (e.g. an unknown category or an unreadable min_resolution) or unknown source id. |
401 |
API key required (self-hosted servers with VIDEO_SERVER_API_KEY only). |
404 |
Unknown clip, or no file matches the height/format/role. |
409 |
The source has no direct downloads for this clip; use the returned url. |
429 |
Rate limit exceeded; wait Retry-After seconds. |
502 |
The source site failed. |
Authentication (self-hosted)
The public server is open. If you run your own with VIDEO_SERVER_API_KEY, send the key as Authorization: Bearer <key> or x-api-key: <key>. Keys are accepted in headers only, never as a URL parameter, so they stay out of logs, browser history and Referer headers.
Rate limits and versioning
The public server allows 120 requests per minute per client. Every response carries RateLimit-Policy and RateLimit headers (plus RateLimit-Limit, RateLimit-Remaining and RateLimit-Reset); going over returns 429 with Retry-After. The API is versioned in the path (/v1), only grows additively within a version, and announces deprecations with Deprecation and Sunset headers at least 90 days ahead. Details: Versioning, deprecation & rate limits.
Fair use
Cache results on your side for repeated queries, keep requests to a human pace, and respect each clip’s licence and each source’s terms. Don’t use the API to mirror a source’s catalogue. The server already caches source responses for a few minutes and deduplicates identical in-flight requests. Video Assets Server is a free, non-commercial service and does not re-host or resell video.
AI agent? This page is also available as markdown.