Google Events API: Find Local Events as JSON
Send a GET to /v1/google-events/search with a query such as concerts in austin, and you get Google's event listings back as JSON: title, date, address, venue, ticket links and a thumbnail. The call costs 1 credit, and a failed call costs nothing. This guide covers the parameters, a recorded response, an honest look at the date and location filters, and a small script that builds a city events feed.
What does the Google Events API return?
The endpoint returns the event cards Google shows for a search such as "events in london this weekend", as structured JSON. Each item in events_results has a position, title, a date object, an address array, a link, a venue object, ticket_info entries and a thumbnail. The response also echoes your inputs in search_parameters and includes a pagination object.
That makes it a fit for city guides, "what's on" widgets, newsletters and agents that answer "what can I do this weekend". It isn't a catalogue of every event in a city. You get what Google ranks for the query you send, and the data is collected from publicly available sources and returned as found at the time of the call.
If you need ticketing, inventory or on-sale data from a single organiser, that organiser's own API usually fits better. Ticketmaster, for example, publishes its own Discovery API for its events. For breadth across venues and organisers in one query, a search-based endpoint is the more practical route.
How do you make your first request?
Send the key in the X-API-Key header and put the search in q. Keys start with sk_live_, and you get one free by signing up and verifying an email address. The authentication docs cover the api_key query parameter for places where headers aren't possible.
curl "https://api.perceptlayer.com/v1/google-events/search?q=concerts+in+austin&hl=en&gl=us" \
-H "X-API-Key: $API_KEY"
Every successful response carries X-Credits-Used and X-Credits-Remaining headers, so you can check what the call cost without parsing the body. You can also run the same call from the playground on the Google Events search endpoint page before writing any code.
Which parameters does the search endpoint take?
The endpoint takes one required parameter, q, and six optional ones. Types and length limits are the only documented constraints, so the table sticks to those and adds notes on how each one is meant to be used.
| Parameter | Type | Required | Limit | Notes |
|---|---|---|---|---|
q |
string | yes | 255 characters | The search text, such as events in london this weekend. |
hl |
string | no | none documented | Interface language, for example en. |
gl |
string | no | 2 characters | Country code, for example us. |
location |
string | no | 255 characters | A place name to anchor the search. |
uule |
string | no | 500 characters | Google's encoded location value, for when you already have one. |
htichips |
string | no | 200 characters | A filter chip value. Allowed values aren't documented here. |
start |
integer | no | none documented | Result offset, for paging. |
Defaults and page sizes aren't documented, so don't hard-code either. Read events_results and pagination in the response instead.
Invalid input returns invalid_parameters (422), for example a q over 255 characters. The errors docs list every code, and since a failed call costs nothing, a bad request is only a lost minute.
What does a real response look like?
Here is a recorded response for q=events in london this weekend with hl=en and gl=uk. The excerpt is trimmed with … and shows the first event only.
curl "https://api.perceptlayer.com/v1/google-events/search?q=events+in+london+this+weekend&hl=en&gl=uk" \
-H "X-API-Key: $API_KEY"
{
"search_parameters": {
"engine": "google_events",
"q": "events in london this weekend",
"gl": "uk",
"hl": "en",
"start": 0
},
"search_information": {
"events_results_state": "Results for exact spelling"
},
"events_results": [
{
"position": 1,
"title": "Halloween River Cruise",
"date": {
"when": "Sat, 24 Oct 2026, 19:00",
"start_time": "2026-10-24T18:00:00Z",
"end_time": "2026-10-24T19:00:00Z"
},
"address": [
"Riverside Building, London Eye, County Hall, Westminster Bridge Rd, London SE1 7PB"
],
"description": "A 60-minute London Eye River Cruise along the Thames with live actors telling ghost stories of Jack the Ripper, the ghost of Captain Kidd and the haunted cries …",
"ticket_info": [
{
"source": "Mato Events",
"link": "https://ma.to/event/london-eye-halloween-river-cruise-2026-10-24",
"link_type": "tickets"
},
{
"source": "londoneye.com",
"link": "https://www.londoneye.com/",
"link_type": "more info"
}
],
"venue": {
"name": "London Eye",
"rating": 4.5,
"reviews": 206031,
"link": "https://www.google.com/search?q=London+Eye&ludocid=4796882358840715922"
},
"thumbnail": "https://encrypted-tbn0.gstatic.com/images?q=tbn:ANd9GcSIyGJkdedlIlKt-RDN0ud-FP177CCvK_cM-VDPtGilIsEngLzXTMXfTGmHpgIvc6S0f3hXZaJaUcgcI3w"
}
],
"pagination": {
"current_page": 1,
"start": 0
}
}
A few details in the recording matter when you build on it:
date.whenis display text in local time ("Sat, 24 Oct 2026, 19:00"), whilestart_timeis UTC. The 19:00 event has astart_timeof18:00:00Zbecause London was on summer time that day.- The second event in the recording, a ghost walk, has
whenset to "Sun, 25 Oct 2026" with no clock time. Itsstart_timeandend_timeare both2026-10-24T23:00:00Z. Date-only events can have identical start and end times, so don't treat the pair as a duration. descriptionis cut off with an ellipsis and the same event shows?where the source text had other characters. Show it as a teaser, and link to the event instead of republishing it.ticket_infomixesticketsandmore infolinks, distinguished bylink_type.
How reliable are the date and location filters?
There is no date or from/to parameter. The timeframe lives in the wording of q ("tonight", "this weekend", "in november") and possibly in htichips, whose accepted values aren't documented here. Both steer Google's ranking. Neither guarantees every result falls inside your window, so filter again on your side.
The recorded call above is a fair example: it asked for "this weekend" and the events it holds fall on 24 and 25 October 2026. That is what you'd hope for, but it comes from Google's interpretation of the phrase, and "weekend" depends on the day you run the query.
Location works the same way:
- In
q: "concerts in austin" carries the city in the text, as in the first example. location: a free-text place name up to 255 characters, for when you'd rather keep the city out of the query.uule: Google's encoded location string, useful if you already generate them.gl: a two-character country bias. The recorded call usedgl=uk, and it returned London events. Google's own code for the United Kingdom is usuallygb, and we haven't documented the acceptedglvalues, so trygbifukdoesn't bias results the way you expect.
Compare address against the city you asked for and drop mismatches. Run a few queries in the playground before choosing between location and a city in q, because we haven't documented which wins when they disagree.
How do you build a city events feed?
A city feed is a handful of query phrasings per city, a page or two of each, merged and deduplicated, then filtered to a time window. The script below does that with requests. It pages by advancing start by the number of events it just received, which assumes start is a plain offset. Confirm that in the playground before relying on it.
import os
import time
from datetime import datetime, timedelta, timezone
from zoneinfo import ZoneInfo
import requests
URL = "https://api.perceptlayer.com/v1/google-events/search"
HEADERS = {"X-API-Key": os.environ["API_KEY"]}
def fetch(q, gl, start=0):
for _ in range(3):
r = requests.get(
URL,
params={"q": q, "hl": "en", "gl": gl, "start": start},
headers=HEADERS,
timeout=30,
)
if r.status_code in (429, 503):
time.sleep(int(r.headers.get("Retry-After", "5")))
continue
r.raise_for_status()
return r.json()
raise RuntimeError(f"gave up on {q!r}")
def city_feed(city, gl, tz, days=7, pages=2):
phrasings = [f"events in {city} this weekend", f"concerts in {city}", f"things to do in {city}"]
now = datetime.now(timezone.utc)
cutoff = now + timedelta(days=days)
seen = {}
for q in phrasings:
start = 0
for _ in range(pages):
events = fetch(q, gl, start).get("events_results", [])
if not events:
break
for e in events:
when = e.get("date", {}).get("start_time")
if not when:
continue
begins = datetime.fromisoformat(when.replace("Z", "+00:00"))
if not now <= begins <= cutoff:
continue
key = (e["title"].lower(), when, e.get("venue", {}).get("name"))
seen.setdefault(key, {
"title": e["title"],
"starts_local": begins.astimezone(ZoneInfo(tz)).isoformat(),
"venue": e.get("venue", {}).get("name"),
"address": ", ".join(e.get("address", [])),
"tickets": [t["link"] for t in e.get("ticket_info", []) if t.get("link_type") == "tickets"],
})
start += len(events)
return sorted(seen.values(), key=lambda e: e["starts_local"])
if __name__ == "__main__":
for event in city_feed("london", "uk", "Europe/London"):
print(event["starts_local"], event["title"], "-", event["venue"])
Three details in that script come straight from the recorded response. It filters on start_time because that field is machine-readable UTC. It converts to the city's time zone for display. And it deduplicates on title, start time and venue, because the same event can surface under more than one query. The same idea at larger scale is in deduplicating listings across sources.
The script retries 429 and 503 after the Retry-After seconds. One caution: requests have no idempotency key, so a call that timed out on your side may still have succeeded and been billed. The rate limits docs cover the per-minute counts, and free accounts get 1 request a second.
What does an events feed cost per refresh?
Every successful call costs 1 credit, so a refresh costs queries × pages × cities, in credits. Failed calls cost nothing and return X-Credits-Used: 0. The script above runs 3 phrasings with up to 2 pages each, so one city costs at most 6 credits per refresh.
| Setup | Calls | Credits |
|---|---|---|
| 1 city, 3 phrasings, 2 pages | 6 | 6 |
| 50 cities, 3 phrasings, 1 page | 150 | 150 |
| 50 cities, 3 phrasings, 2 pages, daily for 30 days | 9,000 | 9,000 |
The last row is 50 × 3 × 2 = 300 calls a day, and 300 calls × 30 days × 1 credit = 9,000 credits. That fits inside the Starter pack's 10,000 credits, and purchased credits last 12 months. The monthly free tier of 1,000 credits covers about three days of that feed, or a full test of a few cities. See pricing for what credits cost and how credits are counted for the details.
Cutting cost is mostly a question of calls you don't make. Most event lists don't change by the hour, so a daily or twice-daily refresh is enough for a guide or a newsletter. If you need to schedule the pulls, cron, queues or workflows compares the options.
When should you use this instead of another source?
Use the endpoint when you want what people see in a Google events search, across many venues, without scraping it yourself. Use your own pipeline, or an organiser's API, when you need complete inventory for one venue or one ticketing platform. The endpoint returns ranked search results, not a database of everything on sale.
If you publish events on your own site, Google documents how to mark them up in its event structured data guide. That is the reverse direction: it helps your events appear in Google, while this endpoint reads what Google shows. Both are useful if you run a venue and also want to track what's listed around you.
For agents, the same endpoint is available through the MCP server, so an assistant can answer "what's on in Lisbon on Saturday" with a live call. The AI agents docs show how to add it. For related Google endpoints, Google Hotels pairs naturally with an events feed for trip planning, and the Google Places alternative covers venue data.
Your next step: get a free API key, then run concerts in austin from the playground on the Google Events search page and compare the address fields with the city you asked for.
APIs in this post
Frequently asked questions
How much does a Google Events API call cost?
A successful call to /v1/google-events/search costs 1 credit. Failed calls cost nothing and return X-Credits-Used: 0. Free accounts get 1,000 credits a month after verifying an email address, with no credit card. That's 1,000 calls a month before you buy anything. The pricing page shows what credit packs cost.
Can I filter Google events by date?
There's no date parameter. Put the timeframe in q, such as "this weekend" or "tonight", and then filter the results on date.start_time, which is in UTC. htichips also accepts a filter string, but its allowed values aren't documented, so test any value in the playground first.
How do I get events for a specific city?
Include the city in q, as in concerts in austin, or pass a place name in location. You can also add gl for the country. Then check each result's address array against your city, because these inputs steer the ranking and don't guarantee that every result matches.
What happens when I hit the rate limit?
You get a 429 response with a Retry-After header giving the seconds to wait. Free accounts get 1 request a second, and paid accounts run at the rate of the credits they're using, at least 5 a second. Wait the stated time and retry. The rate limits docs have the details.
Is there a way to call it from an AI agent?
Yes. The MCP server at https://api.perceptlayer.com/mcp exposes search_endpoints, get_endpoint and call_endpoint. The first two only look up the catalogue and cost nothing. call_endpoint runs the Google Events search and spends 1 credit on success. See the AI agents docs.