total gifs archived
all v2 routes mirror Tenor's own API, so you can usually just swap the base URL and remove the key. cors is set to allow any origin. to get a single GIF (in MP4 format, it's way more efficient), take the number off the end of any tenor.com/view link and put it after the slash.
.mp4 suffix is optional.pos is the cursor that
came back as next. add contentfilter=high,
medium or low to trim ratings. nothing is filtered by default.
ratelimit: 200 reqs / minute / IP
the archive comes in two halves: the metadata, which is small enough to query on a laptop, and the media, which is about 7 TiB and lives in over 13,559 big files.
one Parquet file per month of uploads, about 160 files and a few gigabytes in total. index.json lists every file with its size and row count.
https://bigdata.tiago.zip/dumps/index.json
https://bigdata.tiago.zip/dumps/2025-06/gifs.parquet
the parquets are relatively simple. a row is a gif, with all of the tenor metadata.
all gifs are stored as mp4s in large blobs, called backs. three more columns on every row
say where that GIF's bytes are:
pack is which blob, pack_offset is where it starts and
pack_length is how long it is. ask for exactly those bytes with a range request
and you get the MP4 back.
import requests
row = ... # one row from the parquet
start = row["pack_offset"]
end = start + row["pack_length"] - 1
r = requests.get(f"https://bigdata.tiago.zip/packs/{row['pack']}.bin",
headers={"Range": f"bytes={start}-{end}"})
r.raise_for_status()
mp4 = r.content
note: sort your rows by pack and walk each blob once
instead of firing random ranges across the whole set, and if you want most of a blob anyway,
just download all of it. and a blob that has gone cold in storage needs a moment to wake
up, so a 503 here means retry in a few seconds, not failure.
contact and removals: hi@tiago.zip