Blog ยท Tutorials

Venue Search API: Find and Filter Venues by Foot Traffic

Find venues with a text query, poll the search progress, and filter the results by predicted foot traffic, day and hour with the BestTime API.

Updated October 20, 2026 9 min read
Venue search for cocktail bars in Lower Manhattan returning 20 venues with Friday forecasts

To filter venues by predicted foot traffic, first find them with the Venue Search API, then filter the results with Venue Filter. Venue Search takes a query such as "bars in Austin TX", finds matching places and forecasts each one. The finished search gives you a collection_id and a ready-made Venue Filter link, so you can keep only the venues that are busy, or quiet, on the day and hours you choose.

This tutorial covers the full flow with Python and cURL: the search request, polling the progress endpoint, reading the results, and filtering them. If you are new to the API, start with the beginner's guide to the foot traffic data API.

How Venue Search works

Venue Search is a background job with three parts:

  1. Search. POST /api/v1/venues/search starts the job and returns a job_id, a collection_id and a progress link right away.
  2. Forecast. In the background, BestTime finds venues that match your query and runs a forecast for each one. Venues with enough data are added to your account and to the collection.
  3. Progress and results. GET /api/v1/venues/progress reports how far the job is. When it finishes, it returns the venues, their hourly forecasts and links to filter them.

Use Venue Search when you do not know the venue names yet, or when you want a whole category in an area. For one known venue, the New Forecast endpoint (POST /api/v1/forecasts with venue_name and venue_address) is faster.

Week heatmap for a bar in New York City, busiest on Friday and Saturday nights Every venue the search returns comes with its own forecast, like this week heatmap for a New York bar.

Send a POST request with your private key and a query. The query combines a venue type or name with a location.

import os
import requests

BASE = "https://besttime.app/api/v1"
KEY = os.environ["BESTTIME_PRIVATE_KEY"]

r = requests.post(
    f"{BASE}/venues/search",
    params={
        "api_key_private": KEY,
        "q": "supermarkets in sydney australia",
        "num": 40,
        "format": "raw",
    },
    timeout=30,
)
job = r.json()
print(job)

The same with cURL:

curl --request POST "https://besttime.app/api/v1/venues/search?api_key_private=$BESTTIME_PRIVATE_KEY&q=supermarkets%20in%20sydney%20australia&num=40&format=raw"

The response arrives in about a second:

{
  "_links": {
    "venue_search_progress": "https://besttime.app/api/v1/venues/progress?job_id=e0880f28-3a19-4871-a355-4ca21f10c2c8&collection_id=col_ac734e76ad2d4696a5a66541c67587e8&format=raw"
  },
  "collection_id": "col_ac734e76ad2d4696a5a66541c67587e8",
  "job_id": "e0880f28-3a19-4871-a355-4ca21f10c2c8",
  "status": "OK"
}

Search parameters

Parameter Required What it does
api_key_private yes Your private key.
q yes Venue name or type plus a location, up to 128 characters. Examples: "Whole Foods Market San Francisco", "museums in Amsterdam", "gyms in Brooklyn".
num no Maximum results: 20, 40 or 60. Default 20. More results take longer.
format no Forecast format in the final results: raw (hourly values for every day, the default), all (full analysis per venue) or none (no forecast, smaller response).
opened no all (default), 24 for venues open 24 hours, or now for venues open at this moment.
collection_id no Add the results to an existing collection instead of a new one.
lat, lng, radius no Search around a point, radius in meters. Use this to search near a user, with q set to just "bars" or "pharmacy".

A location is needed in q unless you pass lat, lng and radius.

Step 2: Add filters in plain language

Venue Search reads simple filter phrases from the query. It removes them from the search text and turns them into Venue Filter parameters on the result links. For example:

busy bars in Austin TX on friday from 9 pm until 11 pm

searches for "bars in Austin TX" and sets these filters:

Phrase Filter
busy (or crowded) busy_min=50, busy_max=100
quiet busy_min=0, busy_max=50
minimum 70% busy busy_min=70
on friday day_int=4
from 9 pm until 11 pm hour_min=21, hour_max=23
friday morning day_int=4, hour_min=6, hour_max=11
friday afternoon hour_min=12, hour_max=17
friday evening hour_min=18, hour_max=0
friday night hour_min=21, hour_max=5

So "quiet supermarkets in Sydney Australia on sunday morning" finds supermarkets in Sydney and prepares a filter for venues at or below 50% of their peak between 6 AM and 11 AM on Sunday.

These phrases do not shrink the search itself. Venue Search still finds and forecasts every matching venue. The filters apply when you open the results through Venue Filter in step 4. That way you can change the filter later without searching again.

Step 3: Poll the progress endpoint

Call the progress link until job_finished is true. The endpoint takes the job_id, and optionally the collection_id and format.

import time

progress_url = job["_links"]["venue_search_progress"]

while True:
    progress = requests.get(progress_url, timeout=30).json()
    if progress.get("job_finished"):
        break
    print(progress["count_completed"], "of", progress["count_total"], "processed")
    time.sleep(5)

While the job runs, the response shows counters:

{
  "collection_id": "col_ac734e76ad2d4696a5a66541c67587e8",
  "count_completed": 12,
  "count_failure": 0,
  "count_forecast": 10,
  "count_total": 40,
  "job_finished": false,
  "job_id": "e0880f28-3a19-4871-a355-4ca21f10c2c8",
  "status": "OK"
}
  • count_total: venues found so far. It can grow until it reaches num, or stop lower if fewer venues match.
  • count_completed: venues processed.
  • count_forecast: venues with a forecast.
  • count_failure: venues that hit an error. Venues without enough data are not counted here.

Poll every 3 to 5 seconds. Venue Search allows 30 requests per minute and 300 per hour, so do not start new searches in a tight loop.

Step 4: Read the results

When the job is done, the same endpoint returns the venues. This trimmed example from the API documentation is a finished search for "quiet supermarkets in sydney australia sunday morning":

{
  "job_finished": true,
  "collection_id": "col_5c546908473645c1b9bad36b7fef7765",
  "count_total": 200,
  "count_forecast": 181,
  "venues_n": 200,
  "bounding_box": {
    "lat": -33.8627418, "lng": 151.2165809,
    "lat_min": -33.8877752, "lat_max": -33.8377084,
    "lng_min": 151.1962989, "lng_max": 151.2368629,
    "map_zoom": 14, "radius": 6712
  },
  "venues": [
    {
      "forecast": false,
      "processed": true,
      "venue_id": "ven_6372542d36476a8759686d52676b646155646e713661514a496843",
      "venue_name": "Kings Wharf Supermarket",
      "venue_address": "21 Shelley St, Sydney NSW 2000, Australia"
    },
    {
      "forecast": true,
      "processed": true,
      "venue_id": "ven_9372542d36476a8759686d52676b646155646e713661514a496847",
      "venue_name": "Maloneys Grocer",
      "venue_address": "4/490 Crown St, Surry Hills NSW 2010, Australia",
      "venue_foot_traffic_forecast": [
        {
          "day_int": 0,
          "day_info": { "day_text": "Monday", "day_max": 25, "day_mean": 12, "day_rank_max": 6, "day_rank_mean": 7 },
          "day_raw": [5, 5, 5, 10, 15, 20, 20, 25, 25, 20, 15, 10, 5, 5, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0]
        }
      ]
    }
  ],
  "_links": {
    "venue_filter_api": "https://besttime.app/api/v1/venues/filter?lat_min=-33.8877752&lat_max=-33.8377084&lng_min=151.1962989&lng_max=151.2368629&collection_id=col_5c546908473645c1b9bad36b7fef7765&api_key_private=...&busy_min=0&busy_max=50&day_int=6&hour_min=6&hour_max=11",
    "radar_tool": "https://besttime.app/api/v1/radar/filter?..."
  }
}

(That example ran with an older, higher num limit. Today a search returns up to 60 venues.)

What each part is for:

  • venues lists every match. Check forecast: Kings Wharf Supermarket has false, so it was found but has too little data for a forecast.
  • venue_foot_traffic_forecast holds day_info and 24 day_raw values per day when you used format=raw. Index 0 is 6 AM, index 23 is 5 AM the next morning.
  • bounding_box fits all results, with a center point, zoom and radius you can pass straight to a map.
  • _links.venue_filter_api opens the results in Venue Filter. Notice that the words "quiet", "sunday" and "morning" became busy_max=50, day_int=6, hour_min=6 and hour_max=11. _links.radar_tool opens the same results on the map in your account.

To print the five quietest supermarkets at 9 AM on Sunday from the raw results:

SUNDAY, NINE_AM_INDEX = 6, 3  # 9 AM is 3 hours after 6 AM

rows = []
for v in progress["venues"]:
    if not v.get("forecast"):
        continue
    sunday = next(d for d in v["venue_foot_traffic_forecast"] if d["day_int"] == SUNDAY)
    rows.append((sunday["day_raw"][NINE_AM_INDEX], v["venue_name"]))

for value, name in sorted(rows)[:5]:
    print(f"{value:>3}  {name}")

This works for a quick look. For real filtering and sorting, let the API do it in the next step.

Step 5: Filter the results by predicted foot traffic

Venue Filter returns only the venues that match your conditions, already sorted. Pass the collection_id to limit it to your search results:

r = requests.get(
    f"{BASE}/venues/filter",
    params={
        "api_key_private": KEY,
        "collection_id": progress["collection_id"],
        "day_int": 6,         # Sunday
        "hour_min": 8,        # 8 AM
        "hour_max": 11,       # 11 AM
        "busy_max": 40,       # at most 40% of the venue's weekly peak
        "busy_conf": "all",   # in every selected hour
        "order_by": "9",      # sort by the forecast at 9 AM
        "order": "asc",
    },
    timeout=60,
)
result = r.json()
for v in result["venues"]:
    print(v["venue_name"], v["day_raw"])

Each venue comes back with day_raw for 8 AM to 11 AM only, plus rating, reviews, price level, type, coordinates and dwell time. The window object tells you which day and hours the values cover.

Flip it to find lively places. On a search for bars, day_int=4, hour_min=21, hour_max=23 and busy_min=70 keep only bars forecast at 70% or more of their peak at some point between 9 PM and 11 PM on Friday. With the default busy_conf=any, one matching hour is enough. With all, every hour must match.

Venue Filter has many more options: venue types, rating, reviews, price, bounding boxes, sorting by any hour, paging, and combining collections. They are all in the Venue Filter API tutorial. For use-case recipes, see the guide to filtering public businesses by foot traffic.

Step 6: Get the full analysis for one venue

The search results include hourly values, not peaks or quiet hours. For one venue's complete forecast, query it by venue_id with your public key:

curl --request GET "https://besttime.app/api/v1/forecasts/week?api_key_public=$BESTTIME_PUBLIC_KEY&venue_id=ven_9372542d36476a8759686d52676b646155646e713661514a496847"

This query is free within one day after the venue was forecast. Or run the search with format=all to get the full analysis for every venue in the results.

Part Credits
The search itself a small charge per 20 results found (see the credits table)
Each venue that gets a forecast 2
Venues you forecast recently not charged again
Polling progress free
Venue Filter on the results 1 per 10 venues returned

A 20-venue search stays under about 45 credits in total, and less when some venues have no data. On Basic Usage ($29 per month including 1,200 credits, then $0.04 per credit) that is under $2. If you track a fixed set of venues, Pro Venues gives unlimited forecasts for that list from $96 per month for 50 venues. See pricing.

Tips for better searches

  • Search small areas. Each search returns at most 60 venues. For a big city, search neighborhood by neighborhood with the same collection_id, so all results end up in one collection.
  • Search once, filter often. Searches are slower and cost forecasts. Filtering is fast and cheap. Build your venue set first, then filter it as often as you need.
  • Refresh every few weeks. Venue Filter reads stored forecasts. Refresh them every 2 to 4 weeks with the New Forecast endpoint, which costs 1 credit per venue.
  • Check names. Venue Search returns the names and addresses it found. Spot-check them before you show them to users.
  • Keep the private key on your server. Venue Search and Venue Filter both need it.

Next steps

Questions

How do I filter venues by predicted foot traffic?

Search for venues with the Venue Search API, then pass the returned collection_id to the Venue Filter API with busy_min or busy_max, day_int, hour_min and hour_max. You can also type the filter into the search query, such as "busy bars in Austin on Friday from 9 PM until 11 PM".

How many venues does one Venue Search return?

Between 20 and 60 per search, set with the num parameter in steps of 20. Run more searches for smaller areas to cover a larger city.

How long does a Venue Search take?

It runs in the background because each venue found is also forecast. Poll the progress endpoint every few seconds until job_finished is true.

What does Venue Search cost?

A small search charge per 20 results, plus 2 credits for each venue that gets a forecast. Venues you forecast recently are not charged again.

BestTime heatmap of busy areas across New York City

BestTime in 47 seconds

See how busy any place gets, hour by hour

BestTime forecasts how busy a venue gets for every hour of the week, from anonymized, aggregated phone location data combined with weather, events and holidays. Look up one venue, compare a whole city, or use the API.

Try BestTime for free

Create a free account without a credit card, forecast a few venues you know, and see their busy and quiet hours for every day of the week.

Start free See pricing