Archive API
Free, read-only access to the archive's records: the same clips, captions, dates, sources and locations you see on this site, in a form that programs, researchers and journalists can work with.
No account, no key, no sign-up. Every answer is plain JSON that you can open in a browser or read from a script in any language.
Counts at the time of writing. The live figures are always at /v1/meta.
Contents
- Getting started
- Endpoints
- Searching and filtering
- What a record contains
- Pages and large results
- Withdrawn clips
- Errors
- Code examples
- Fair use, caching and limits
- Privacy
- Terms of use
- Changes and stability
Getting started
Every address starts with:
Open any of these in your browser to see a real answer:
- /v1/meta
- /v1/clips?q=hospital&per_page=5
- /v1/clips?from=2023-10-07&to=2023-10-31&has_geo=1&sort=date
- /v1/clips/66454
- /v1/archives
Using it from an AI assistant or code tool
A machine-readable description of the whole API is at api.archivegenocide.com/openapi.json (OpenAPI 3.1). AI assistants, agent frameworks and code generators can read it to learn every search option and field on their own. For example, add it as an action in a custom assistant, or give your assistant this page and the description and ask it to look something up. Answers come with each clip's date, source, place and a link to check the footage yourself.
Endpoints
All endpoints answer GET only.
| Address | What it returns |
|---|---|
/v1/clips | A page of clips, newest first, filtered by the parameters below. |
/v1/clips/{id} | One clip by its id. A withdrawn clip answers 410 Gone; an unknown id answers 404. |
/v1/archives | Every source collection, with how many clips each holds. Use the exact names in the archive filter. |
/v1/withdrawn | The ids of clips removed from the archive. See Withdrawn clips. |
/v1/meta | Total counts and the time the data was last built. |
Searching and filtering
All parameters for /v1/clips are optional and can be combined. An unknown or repeated parameter is
refused with a clear message rather than silently ignored, so a typo never returns the wrong data.
| Parameter | Meaning | Example |
|---|---|---|
q | Words that must all appear in the caption, in English or in the original language (Arabic works). Up to 200 characters. Operators such as OR or quotes are treated as plain words. | q=al shifa |
type | video or photo. | type=video |
from, to | Dates as YYYY-MM-DD. Both ends are included. | from=2024-05-06&to=2024-05-31 |
archive | One source collection, named exactly as in /v1/archives. | archive=Children of Gaza (Oct 2023-Apr 2025) |
has_geo | 1 for clips with a map location, 0 for clips without. | has_geo=1 |
sort | -date (newest first, the default), date (oldest first) or id. | sort=date |
page | Page number, from 1. | page=3 |
per_page | Results per page, 1 to 100. Default 50. | per_page=100 |
Remember to URL-encode values with spaces or non-Latin text; most tools do this for you.
What a record contains
A real record, as returned by /v1/clips/66454:
{
"id": 66454,
"type": "photo",
"date": "2023-11-12T20:20:42+03:00",
"duration": "",
"caption": "💔🇵🇸Heartbreaking...\n\"Babies in the neonatal intensive care unit at Shifa Hospital ...",
"caption_original": "💔🇵🇸Heartbreaking...\n\"Babies in the neonatal intensive care unit at Shifa Hospital ...",
"source_archive": "Children of Gaza (Oct 2023-Apr 2025)",
"source_post": "",
"media_url": "https://media.archivegenocide.com/children-of-gaza-2/photos/photo_181%4012-11-2023_20-20-42.jpg",
"thumbnail_url": "https://media.archivegenocide.com/children-of-gaza-2/photos/photo_181%4012-11-2023_20-20-42_thumb.jpg",
"page_url": "https://archivegenocide.com/v/66454.html",
"confidence": "medium",
"duplicate_of": null,
"geo": { "lat": 31.524155, "lon": 34.443834, "precision_level": 3, "exact": false }
}
| Field | Meaning |
|---|---|
id | Permanent id. The same id is used on the site, in share links and in the downloads. |
type | video or photo. |
date | When the clip was posted by its source, with the source's time zone. This is the posting time, not necessarily the time of the event. |
duration | Video length as mm:ss; empty for photos. |
caption | The caption in English (translated where the original was in another language). |
caption_original | The caption as the source wrote it. |
source_archive | The collection the clip came from (a channel or account and its date range). |
source_post | Link to the original post, when known. |
media_url, thumbnail_url | The footage and its preview image. |
page_url | A shareable page for the clip on this site. |
confidence | How closely the clip matches what the archive documents: high, medium or low. It reflects review status, not a judgement about whether an event happened. |
duplicate_of | If the same footage is already in the archive, the id of that copy; otherwise null. |
geo | null, or a location from the Geolocation map. Read precision_level before using the point; only exact: true marks a specific site. |
precision_level | What the point stands for |
|---|---|
1 | Only the town or city the caption names. The point is the centre of that area, not where the clip was filmed. |
2 | Only the neighbourhood or camp the caption names. Again an area, not a filming position. |
3 | The caption names a building (a hospital, a school). Footage is often filmed from a street away, or shows people brought from the site; not checked on the ground. |
4 or more | An exact site, geolocated from the footage or taken from cited reporting. exact is true. |
Captions come from the channels and journalists who posted the footage, not from us. A record shows what was posted and when; it is a primary source for you to examine, not a finding.
Pages and large results
A list answer looks like this:
{
"page": 1,
"per_page": 50,
"total": 49,
"total_is_lower_bound": false,
"pages": 1,
"results": [ ... ]
}
To keep the service fast for everyone, a single search can be read 10,000 results deep. For a
broad search, total stops counting at 10,000 and total_is_lower_bound is
true. To go further, split the work by date: ask for one month (or one week) at a time with
from and to. The Python example below does exactly that.
If you want the footage itself in bulk, use the signed torrents on the Downloads page. That is far quicker than fetching files one by one.
Withdrawn clips
Occasionally a clip is removed from the archive. Its id then disappears from every listing,
/v1/clips/{id} answers 410 Gone, and the id is added to
/v1/withdrawn.
If you keep copies of records, check this list from time to time and delete any withdrawn clip you hold.
Errors
Errors are JSON with a plain-English message, for example {"error": "type must be video or photo"}.
| Status | Meaning | What to do |
|---|---|---|
400 | A parameter is wrong, unknown or repeated; or a query took too long. | Read the message and fix the request; narrow a slow search. |
404 | No such address or clip. | Check the id or path. |
405 | Only GET is allowed. | Use GET. |
410 | The clip was withdrawn. | Delete your copy. |
414 | The address is too long. | Shorten the search. |
429 | More than 120 requests in a minute. | Wait the seconds given in the Retry-After header, then continue more slowly. |
503 | The service is busy. | Wait a few seconds and try again. |
Code examples
Command line (curl)
curl "https://api.archivegenocide.com/v1/clips?q=rafah&type=video&per_page=10"
JavaScript (browser or Node)
The API allows calls from any website, so this works straight from a web page.
const url = new URL("https://api.archivegenocide.com/v1/clips");
url.searchParams.set("q", "hospital");
url.searchParams.set("has_geo", "1");
url.searchParams.set("per_page", "100");
const res = await fetch(url);
if (!res.ok) throw new Error((await res.json()).error);
const data = await res.json();
for (const clip of data.results) {
console.log(clip.id, clip.date, clip.geo.lat, clip.geo.lon, clip.page_url);
}
Python: every located clip for one month, safely past the 10,000 limit
import json, time, urllib.error, urllib.parse, urllib.request
from datetime import date, timedelta
API = "https://api.archivegenocide.com/v1/clips"
HEADERS = {"User-Agent": "my-research-script/1.0"} # please name your script
def get(params):
url = API + "?" + urllib.parse.urlencode(params)
for attempt in range(5):
try:
req = urllib.request.Request(url, headers=HEADERS)
with urllib.request.urlopen(req, timeout=60) as r:
return json.load(r)
except urllib.error.HTTPError as e:
if e.code in (429, 503): # too fast or busy: wait as asked, then retry
time.sleep(int(e.headers.get("Retry-After", 2 * (attempt + 1))))
continue
raise
raise RuntimeError("gave up on " + url)
def clips_between(start, end):
"""Walk one day at a time so no single query reaches the 10,000 limit."""
day = start
while day <= end:
page = 1
while True:
data = get({"from": day.isoformat(), "to": day.isoformat(),
"has_geo": 1, "sort": "date", "per_page": 100, "page": page})
yield from data["results"]
if page >= data["pages"]:
break
page += 1
time.sleep(0.5) # stays within 120 requests a minute
day += timedelta(days=1)
rows = list(clips_between(date(2023, 11, 1), date(2023, 11, 30)))
print(len(rows), "located clips in November 2023")
Please send a User-Agent header that names your script, as the example does. It is
not required, but it helps us tell research tools apart from abuse.
Saving results as a spreadsheet
Add these lines to the end of the Python example to write a CSV file that opens in Excel, LibreOffice or Google Sheets:
import csv
with open("clips.csv", "w", newline="", encoding="utf-8-sig") as f:
w = csv.writer(f)
w.writerow(["id", "date", "type", "lat", "lon", "precision_level", "source_archive", "page_url", "caption"])
for c in rows:
g = c["geo"] or {}
w.writerow([c["id"], c["date"], c["type"], g.get("lat"), g.get("lon"), g.get("precision_level"),
c["source_archive"], c["page_url"], c["caption"]])
Fair use, caching and limits
- Answers are cached for five minutes. Asking the same question twice in a row is cheap; the data itself changes only when the archive is updated.
- Each visitor can make up to 120 requests a minute. Over that, the API answers
429with aRetry-Afterheader giving the seconds to wait. The count is kept only in memory for about a minute and is never stored. Very heavy searches may also be answered with503while the service is busy. - Do not use the API to bulk-download footage. Use the Downloads page and torrents, which exist for exactly that.
- A search is limited to a few seconds of work. If one is refused as too slow, add a date range or more words.
Privacy
The API server keeps no request logs: it does not record your address, what you searched or when. It sets no cookies and needs no account. Like the rest of this site, requests reach it through Cloudflare, which protects the service from attacks and caches answers.
Terms of use
- The records (ids, dates, captions as collected, sources, locations) are free to use, share and
build on. Please credit Archive Genocide (archivegenocide.com) and link to the clip's
page_urlwhere you can. - The footage belongs to the people who filmed it. Captions belong to the people who wrote them. Use them in ways that respect that, and credit the original source where it is known.
- Respect the people shown. Many clips show victims, including children. Do not use the records to harass, mock or expose anyone, and do not try to identify private individuals except to document crimes against them.
- Remove withdrawn clips from any copy you keep (see Withdrawn clips).
- The data is provided as it is, without guarantees. Read the About and Verify pages to understand how the archive is collected and checked.
Changes and stability
The /v1/ addresses are stable. New endpoints and new fields may be added, but existing fields will
not be renamed or removed. If something ever has to change in a way that breaks programs, it will appear under a new
version (/v2/) and /v1/ will keep working for a long notice period.
Questions, bugs or requests: contact us through the links on the About page.