If you need video results from Google — titles, links, durations, sources, thumbnails — the videos endpoint returns them as structured JSON, one POST per page. Here's the whole flow in Python: query, paginate, flatten to CSV.
The endpoint
POST /google/videos, with a JSON body:
-
q(required) — the query -
hl(language code, defaults toen) -
gl(country code, defaults tous) -
page(1-based, defaults to1)
There's no device parameter here — the docs restrict that one to the web search endpoint. A successful request costs 1 credit, like search and news (images and maps cost 2). The response envelope carries status, request_id, elapsed_ms, credits_charged, and search_type ("videos"), plus query, page and the videos array.
Each item in videos documents these fields: rank, position, title, link, url, source_url, display_url, source, duration, time, published_at, thumbnail_url, thumbnail.
import time
import requests
API_URL = "https://api.serpbase.dev/google/videos"
API_KEY = "YOUR_API_KEY"
def fetch_page(query: str, page: int = 1) -> dict:
resp = requests.post(
API_URL,
headers={"X-API-Key": API_KEY, "Content-Type": "application/json"},
json={"q": query, "hl": "en", "gl": "us", "page": page},
timeout=30,
)
resp.raise_for_status()
return resp.json()
first = fetch_page("python asyncio tutorial")
print(first["search_type"], len(first.get("videos", [])))
The field names and request shape above come from the SerpBase videos endpoint documentation. One note on that page is worth knowing before you debug odd result sets: if no dedicated video cards are parsed for a query, the parser falls back to news-style extraction for compatible layouts — so a result list that reads more like articles than videos isn't a bug in your code.
Collect and export
import csv
FIELDS = ["rank", "title", "link", "source", "duration", "published_at"]
def collect(query: str, pages: int = 2) -> list:
rows = []
for page in range(1, pages + 1):
data = fetch_page(query, page)
items = data.get("videos", [])
if not items:
break
rows.extend(items)
time.sleep(1)
return rows
rows = collect("python asyncio tutorial")
with open("videos.csv", "w", newline="", encoding="utf-8-sig") as f:
writer = csv.DictWriter(f, fieldnames=FIELDS, extrasaction="ignore")
writer.writeheader()
writer.writerows(rows)
print(len(rows), "rows")
extrasaction="ignore" lets the writer take the full API objects while only emitting the columns you asked for. Fields that aren't present on a given item come out as empty cells, so check for blanks before you analyse.
Example rows (illustration, not real data):
rank,title,link,source,duration,published_at
1,Example Video Title,https://www.youtube.com/watch?v=example,Example Channel,12:34,2026-08-30
2,Another Example Clip,https://www.youtube.com/watch?v=example2,Second Channel,04:07,2026-08-28
What it costs
1 credit per successful request. A batch of 10 keywords at 2 pages each is 20 requests, so budget 20 credits. New accounts get 100 free searches, which covers getting the pipeline working. credits_charged in each response tells you what was actually billed, and failed or upstream-timed-out requests are handled by the refund logic.
FAQ
Does device work on this endpoint? No — the docs list it for the search endpoint only. Video results here reflect the default layout.
Why do both time and published_at exist? The docs list both on video items. I store both raw and normalise downstream instead of assuming they mean the same thing — check what they contain for your queries before trusting either.
Do I need to dedupe across pages? Yes if you go past page 1 or reuse queries. Dedupe on link before writing, same as you would with any paginated API.
Point it at your own query list and run it once — the CSV tells you quickly which fields come back populated for your vertical. All the parameters used here are in the documentation linked above.
Top comments (0)