Overview

The Locu API 2.0 offers the capability of running queries against the Locu venue and menu data set. The major components are:

Not all functionality is offered to all partners, depending on contract details.

API v1 Documentation - API v1 is now deprecated and we recommend switching to v2.

Venue Object

The Venue object is a collection of facts about a venue, and is the core object of interchange in the Locu API. For matching, partners construct venue objects and send them to the matching API. The matcher then determines the corresponding Locu ID for the venue. For venue search requests, Locu returns Venue objects for venues queried for in a variety of ways, including by Locu IDs, by partner/external IDs, by name, etc.

The attributes are broken down into sub-objects to support fetching collections of attributes at once in search API requests. For example, {"fields": ["location", "menus"]} fetches the location details and also the menus for the venue.

Attribute Name Type Description
locu_id String The Locu ID of the venue; e.g. "150c51458ff903f42695".
name String The name of the venue; e.g. "Gary Danko".
short_name String A short name for the venue, typically used inside human-readable URLs.
description String A brief description of the venue; for example, "The best burgers in all of San Francisco"
website_url String The website URL; for example, http://www.food.com/. The protocol, "http" or "https" is required.
menu_url String The URL of the menu (if applicable).
menus [Menu] List of all menus for this venue.
menu_items [MenuItem] List of menu items in the menus of this venue. At most 15 menu items are returned.
open_hours {String: [[String]]} The hours the business is open. The format is {day : [hours]} where day is one of monday, tuesday, wednesday, thursday, friday, saturday, or sunday; and hours is a tuple of times in 24 hour HH:MM format. For example: { "wednesday": [["11:00", "15:00"], ["17:00", "21:00"]] } is a business open on Wednesday from 11am-3pm and 5-9pm. If the open hours span the day, for example "17:00-02:00", it is assumed the hours cross midnight and go into the subsequent day.
external ExternalIDs The external IDs/URLs of the venue on sites other than Locu; see section below on ExternalIDs Object.
redirected_from [String] If a requested venue was marked as a duplicate on the Locu side, then the returned venue may have a different ID than the one requested. The redirected_from field reflects the originally requested venue.
categories [Category] List of sub-objects with attributes about the venue's category.
location Location Sub-object with attributes about the venue's location.
contact Contact Sub-object with contact details attributes.
locu Locu Sub-object with Locu specific attributes.
delivery Delivery Sub-object describing the venues delivery services.
extended Extended Sub-object describing extra attributes of the venue.
media Media Sub-object with media relating to the menu.

ExternalIDs Object

Attribute Name Type Description
id String The ID of the venue on the external provider.
url String The URL of the venue on the external provider's website.
mobile_url String The URL of the venue on the external provider's mobile website.

Here's an example of a fake top level external IDs object:

{
  "opentable" : {
    "id": "42592",
    "url": "http://www.opentable.com/rest_profile_menu.aspx?rid=42592"
  },
  "tripadvisor" : {
    "id": "abc"
    "mobile_url" : "http://m.tripadvisor.com/mobile_url_here/..."
  }
}

Category Object

Attribute Name Type Description
name String Name of the category. Names of categories can only be picked from this list.
str_id String String ID of the category (specified in parenthesis for each category in this list).

Location Object

Attribute Name Type Description
address1 String The first line of the address of the venue, for example, "560 Sutter Street".
address2 String The second line of the venue's address, for example "Suite 300".
address3 String The third line of the venue's address. This is only needed in rare cases.
locality String The locality, city, or town name; for example, "San Francisco".
region String The region, state, or province; for example, "CA", "MA", or "ON".
postal_code String The postal or zip code; for example, "P1L 1W9", "02142-1023", or "94117".
country String The country; e.g. "United States", "Great Britain", or "Canada".
geo GeoJSON The coordinates of the restaurant, as a GeoJSON object. Most locations will have a point location, as follows: { "type": "Point", "coordinates": [<longitude degrees>, <latitude degrees>] }. WARNING: The coordinate order is longitude, latitude, and not the reverse. The latitude is in the range -90.0 to 90.0, and the longitude in the range -180.0 to 180.0.

Contact Object

Attribute Name Type Description
phone String The phone number of the business; e.g. "(415) 555-5555". The number should be in domestic format, as one would dial from within the country (e.g. international codes omitted).
fax String The fax number of the business; e.g. "(415) 555-4444". The number should be in domestic format, as one would dial from within the country (e.g. international codes omitted).
email String Primary email contact for the restaurant.
phones {String: String} Extended phone numbers. When present, the keys are the type of number and the value is a number like the "phone" attribute above. If the key "others" is present, it is a list of other numbers of unknown type. For example: { "phones": { "reservations": "(415) 222-3333", "orders": "(415) 234-6362", "others": ["(405) 243-13424", "(905) 424-434"] }.
faxes {String: String} Extended fax numbers, similar to extended phone numbers. For example, { "faxes": {"orders": "(415) 234-6362" }.
emails {String: String} Extended email addresses, similar to extended phone and fax numbers.
business_owner String The name of the business owner (not landlord) in the format "First, Last" or "First, Last, Middle".

Locu Object

Attribute Name Type Description
owner_controlled Boolean The venue is owner-controlled on Locu.com. Locu does not make automatic changes to owned venues.
verification_level Integer The verification level of the venue. Higher levels indicate stronger confidence. < 25 - Low confidence; 25 - Venue details and menu should be accurate; 50 - 99 - Locu is confident the venue is accurate; 100 - This venue should be used in preference to any other provider.
last_updated_by_owner String The date the venue was last modified by the owner on Locu, specified as "YYYY-MM-DDTHH:MM:SS".
last_updated_by_locu String The date the venue was last modified by Locu (not by the owner). For example, this happens following a crawl. Same format as last_updated_by_owner.
last_updated_listings String The date the venue's listing information was last updated. Same format as last_updated_by_owner.
last_updated_menu String The date the menu was last updated. Same format as last_updated_by_owner.
last_modified String The date the venue was last modified (in any way). Same format as last_updated_by_owner.
added_to_locu String The date the venue was added by Locu. Same format as last_updated_by_owner.

Delivery Object

Attribute Name Type Description
will_deliver Boolean The business will deliver.
hours {String: [String]} Hours the business does delivery. If hours is missing but "delivery" is true, assume the business delivers during open hours. This is in the same format as the open_hours attribute.
minimum_order Float The minimum order amount for delivery.
areas (TBD) (TDB)

Extended Object

Attribute Name Type Description
established_date String The opening date in "YYYY-MM-DD" format. If the exact date is unknown, include the part that is known; e.g. "2012" if it is only known that the venue opened in 2012, or "2010-04" if it is known that the restaurant was opened in April of 2012.
closed_date String The date the venue closed permanently. This is the same format as "established_date".
closed_permanently Boolean The restaurant is closed forever. Note: Locu does not delete venues. Venues are marked as invalid or closed permanently.
payment_methods {String: Boolean} A bitset of accepted payment methods. Payment types can be one of: visa, mastercard, amex, discover, dinersclub, square, paypal, giftcard, jcb, check, carteblanche and cash. For example: {"visa": True, "mastercard": True}.
cash_only Boolean True if the venue accepts only cash, or false if it accepts some credit cards as well. Sometimes this field is present when payment_methods is missing.
history String A brief history of the venue.
alcohol String One of: beer_and_wine, full_bar, no_alcohol.
parking [String] A list of parking options available at venue. They can be one of garage, lot, street, valet, validated.
wifi String If the venue offers wireless internet. One of: free, paid, no.
corkage String If the venue permits bringing alcohol (usually wine) instead of purchasing it. One of: free, paid, no.
dietary_restrictions [String] The dietary restrictions that are acceptable when eating at this establishment; for example, if "kosher" is in the set, then there are kosher items on the menu. List of: vegetarian, vegan, kosher, halal, dairy-free, gluten-free, soy-free.
music [String] The typical music situation at the venue. List of: dj, live, jukebox, background_music, karaoke, no_music.
sports [String] List of what sports are displayed on TVs at the venue. Can be any of: soccer, basketball, hockey, football, baseball, mma, other.
wheelchair_accessible Boolean The venue is accessible via wheelchair.
reservations Boolean The venue takes reservations.
outdoor_seating Boolean The venue has outdoor seating (e.g. on a patio).
good_for_kids Boolean The venue is good for kids; for example, the exploratorium is good for kids (true) but The Fillmore is not (false).
good_for_groups Boolean The venue is good for groups.
meals [String] A list of the meals offered. Can be any of: breakfast, brunch, dinner, lunch.
takeout Boolean True if the venue does takeout.
smoking String Whether smoking is permitted at the venue. One of: yes, no, outdoor.
noise_level String One of: quiet, typical, loud.
minimum_age Integer Minimum age to enter the venue. If the venue is all ages, put 0.
specialties String Free form text describing the businesses specialities.
attire String One of: dress, formal, casual.
waiter_service Boolean True if the venue has waited tables.
television Boolean True if the venue has televisions.
caters Boolean True if the business will cater.
ambience [String] List containing any of: classy, romantic, upscale, touristy, trendy, casual.
price_range {String: String} Object of the form {high: <high price>, low: <low price>}.
currency Currency Sub-object detailing the currency used by the venue.

Media Object

Attribute Name Type Description
cover_photo String URL for the cover photo for the venue.
venue_photos [String] A list of URL's of images of the venue. Note that there are partner-specific restrictions on the use and display of these images; do not blindly put these URL's on your site!
menu_photos [String] A list of URL's of images of menu items. These are also accessible from the menu object but having the images here enables searching for venues with images.
logos [String] A list of URL's of images of the logo for the business. The first image in the list is usually the best one to display.
videos [String] A list of URL's of videos of or relating to the business.

Currency Object

Attribute Name Type Description
name String Name of the currency.
synbol String Symbol of the currency.

Venue Search API

https://api.locu.com/v2/venue/search

The venue search API supports searching for venues by most of the fields on the venue object. The structure of the search query permits sophisticated searches to avoid unnecessary round trips to the Locu API.

A search request is a disjunction (across venue queries) of conjunctions (of the fields within a venue). This is expressed as a list of venue objects. Each venue object represents a query for venues which match all (i.e. the conjunction) of the specified fields in the venue object. Each venue object is a separate query, and the final result of the search request is the union of the venues that matched each query.

VenueSearchRequest Object

Attribute Name Type Description
api_key String The developer's API key.
fields [String] A list of the fields to fetch for the matching venues. This can be specific fields, or attribute groups, for example location. The default set of fields returned are locu_id, name and location.
offset Integer The 0-based offset in the result set to start fetching venues. Offset support is only offered to some partners. Must not be set if results_key is set.
limit Integer The maximum number of venues to return. Specifying the limit when starting a results session by specifying results_key: "create" sets the result page sizes for the session. Specifying limit when results_key is one of the result page keys (as opposed to "create") is not allowed and will result in an error.
venue_queries [VenueQuery] A list of venue search queries. Each query matches all the venues that have the same value as the specified fields. Some fields have special interpretation; see the query language section below. Must not be set if results_key is set.
menu_item_queries [MenuItemQuery] A list of menu item search queries. Only menu items matching these queries will be populated in the menu_items key in the venue objects returned. Must not be set if results_key is set.

VenueSearchResponse Object

Attribute Name Type Description
status String "success" or "error".
http_status Integer The HTTP status code of the response.
error String An error message if the request did not complete successfully or if there was a problem with the input.
next_results_key String The results key for the next page of results. Specify this as the results_key in the next search request to continue fetching more of the results. The key is only valid for a short time (~5 minutes), but the time is extended with each subsequent page request. See the documentation for results_key in the request section for more details.
venues [Venue] The matching venues, with the requested fields (if available) included.

VenueQuery Object

The VenueQuery object is structurally the same as the Venue object, except that the fields are interpreted as fields to match in a search. The fields that mirror the fields in the Venue object are matched against the venues inside the Locu database in the following way:

  • If the type of the query field (or sub-field) matches the type of the Venue object's parallel field, then a semi-exact match is done.
  • If the type of the query field is a Condition object then more sophisticated matching is done.

Condition Object

When a sophisticated matching condition is needed for a field, use a Condition object. If multiple fields within a condition are present, they are considered a conjunction (e.g. AND). Not all fields support all conditions.

Attribute Name Type Description
$present Boolean If true, the field must be present for a venue to match. If false, the field must be missing from the venue in order for the field to match.
$gt, $lt, $ge, $le String | Float | Integer Comparison operations on numeric or date fields. For example {"gt" : "2012-09-30"} means dates after September 30th, 2012, or {"le": 55} means less than or equal to 55.
$contains_any [<object>] For list fields, matches if the list field (e.g. categories.str_id, contact.phones etc.) contains any of the elements in the array.
$contains_all [<object>] Matches if all of the elements in the list are present in the list field.
$contains_none [<object>] Matches if none of the elements in the list are in the field elements; for example you could use this to find all non-restaurants venues.
$in_lat_lng_bbox [Float, Float, Float, Float] Bounds describing a region. The list is [lat, lng, lat, lng] with the first two coordinates the north-west corner and the second coordinates the south-east corner. Only supported by GeoJSON fields.
$in_lat_lng_radius [Float, Float, Float] Matches locations contained within bounds described as a triple of latitude, longitude, and radius (in meters). Only supported by GeoJSON fields.

Query Language

Querying works by filling in a subset of the fields for the query object with a value the field must match on, or with a condition to filter the objects down by. Some sections can take additional arguments (described in detail below).

The examples should be executable on most unix shells; try copy and pasting a few!

Querying for venues by name

The following example matches the venue by name:

curl -X POST https://api.locu.com/v2/venue/search -d '{
  "api_key" : "f165c0e560d0700288c2f70cf6b26e0c2de0348f",
  "fields" : [ "name", "location", "contact" ],
  "venue_queries" : [
    {
      "name" : "bistro central parc"
    }
  ]
}'

Note that since the field to match is a direct equality match, there is no need for a Condition object.

Querying for venues in a circular area

To search for venues in a circular area, use a $in_lat_lng_radius condition operator on the location.geo field as follows:

curl -X POST https://api.locu.com/v2/venue/search -d '{
  "api_key" : "f165c0e560d0700288c2f70cf6b26e0c2de0348f",
  "fields" : [ "name", "location", "contact" ],
  "venue_queries" : [
    {
      "location" : {
        "geo" : {
          "$in_lat_lng_radius" : [-37.7750, 122.4183, 5000]
        }
      }
    }
  ]
}'

This matches venues within a 5km (expressed as 5000 meters in the request) radius of the given latitude/longitude.

Querying for presence or absence of fields

You can query for the presence or absence with the $present condition operator. This query will look for all venues without a menu:

curl -X POST https://api.locu.com/v2/venue/search -d '{
  "api_key" : "f165c0e560d0700288c2f70cf6b26e0c2de0348f",
  "fields" : [ "locu_id" ],
  "venue_queries" : [
    {
      "menus" : { "$present" : false }
    }
  ]
}'

Querying for venues open during certain hours

For the open hours, venues that are open during the specified time windows are considered matching. For example, if a venue was open M-F 5pm-11pm, then the query:

curl -X POST https://api.locu.com/v2/venue/search -d '{
  "api_key" : "f165c0e560d0700288c2f70cf6b26e0c2de0348f",
  "venue_queries" : [
    {
      "open_hours" : { "monday" : ["18:00", "20:00"]}
    }
  ]
}'

would match the venue. In addition, you can search for venues that are open at a particular time of day. For example:

curl -X POST https://api.locu.com/v2/venue/search -d '{
  "api_key" : "f165c0e560d0700288c2f70cf6b26e0c2de0348f",
  "venue_queries" : [
    {
      "open_hours" : { "monday" : ["22:00"] }
    }
  ]
}'

would match any venue that is open at 10pm on Mondays.

Querying for external ID fields

Note: This functionality is only enabled for certain API keys, depending on contractual details.

Searching for a venue by the external ID field (e.g. for a partner fetching the details of a venue via their own ID for the venue) requires that the partner has previously sent the venue to the matching/ingest API and that we have identified a positive match on the venue. For example, the query:

curl -X POST https://api.locu.com/v2/venue/search -d '{
  "api_key": "........",
  "venue_queries": [
    {
      "external": {
        "opentable": { "id" : "38617" }
      }
    }
  ]
}'

would only succeed in finding the specified OpenTable restaurant provided the restaurant details were previously sent to the matching/ingest API. To query for all the venues that have a certain associated partner ID, it is as simple as doing a query like:

curl -X POST https://api.locu.com/v2/venue/search -d '{
  "api_key": "........",
  "venue_queries": [
    {
      "external": {
        "opentable": { "$present" : true }
      }
    }
  ]
}'

Querying for image fields

Querying for image URLs such as venue_images or menu_item_images is not supported; instead, the image fields are treated as booleans, and the typical search is for venues that have images; for example:

curl -X POST https://api.locu.com/v2/venue/search -d '{
  "api_key": "f165c0e560d0700288c2f70cf6b26e0c2de0348f",
  "venue_queries": [
    {
      "media" : { "$present" : true }
    }
  ]
}'

Querying for all restaurants in Boston that serve pizza

Here we need to specify both a VenueQuery and a MenuItemQuery object in the VenueSearchRequest object. The following example will match this query:

curl -X POST https://api.locu.com/v2/venue/search -d '{
  "api_key" : "f165c0e560d0700288c2f70cf6b26e0c2de0348f",
  "fields" : [ "name", "menu_items" ],
  "venue_queries" : [
    {
      "location" : {
        "locality": "Boston"
      }
    }
  ],
  "menu_item_queries" : [
    {
      "name" : "pizza"
    }
  ]
}'

The response will look something like:

{
  "status": "success",
  "http_status": 200,
  "venues": [
    {
      "locu_id": "ac1cda48ff99d7b543fe",
      "name": "Boston Beer Garden",
      "menu_items": [
        {
          "name": "Buffalo Chicken Pizza",
          "price": "10.99",
          "menu_name": "Menu",
          "section_name": "Appetizers‏",
          "type": "ITEM",
          "description": "Shredded chicken tossed in our spicy buffalo sauce with mozzarella and blue cheese"
        },
        {
          "name": "Margherita Pizza",
          "price": "9.99",
          "menu_name": "Menu",
          "section_name": "Appetizers‏",
          "type": "ITEM",
          "description": "Tomato sauce, mozzarella, oregano, basil and shaved parmesan"
        },
        ... (more MenuItem objects)
      ]
    },
    ... (more Venue objects)
  ]
}

Querying for many attributes

To query for many attributes, simply extend the "fields" portion of the request. For example:

curl -X POST https://api.locu.com/v2/venue/search -d '{
  "api_key" : "f165c0e560d0700288c2f70cf6b26e0c2de0348f",
  "fields" : [
    "locu_id",
    "name",
    "description",
    "website_url",
    "location",
    "contact",
    "categories",
    "menus",
    "open_hours",
    "extended",
    "description",
    "short_name"
  ],
  "venue_queries" : [
    {
      "name": "bistro central parc",
      "menus" : { "$present" : true }
    }
  ]
}'

Paginating results

Note: This functionality is only enabled for certain API keys, depending on contractual details.

In order to paginate results, we require that you paginate within a session rather than use the offset/limit` keys in the VenueSearchRequest object. Locu's search index is constantly changing, and without a session, it is impossible to guarantee that you get all the venues that match a query at a given moment in time. The results_key search parameter and the next_results_key response parameter enable this pagination. The steps to make a paginated query are:

  1. Create a new result set with "results_key" : "create"
  2. Iteratively download the rest of the results one page at a time
  3. Stop when the "venues" field in the results is an empty list.

Step 1. Create new result set with "create"

If results_key is "create", the search request will start a result session on the server. The session captures a complete result set for a query, which is often too large to return in a single request. You should also specify the limit parameter to set response sizes. The returned search response will have the next_results_key set, possibly in addition to an initial page of venues.

For example, here is one initial request:

curl -X POST https://api.locu.com/v2/venue/search -d '{
  "api_key" : "....",
  "fields" : [ "locu_id", "location" ],
  "results_key" : "create",
  "limit" : 3,
  "venue_queries" : [
    {
      "locu": {
        "last_modified": {
          "$ge": "2000-01-30T00:00:00"
        }
      }
    }
  ]
}'

In the result:

{
  "status":"success",
  "next_results_key":"c2Nhbjs2OzEw.....jQ7",
  "venues":[
    {
      "locu_id":"e86dc6ed95d355a3c606",
      "location":{
        "geo" : {
          "type" : "Point",
          "coordinates" : [
            40.843701,
            -73.937428
          ],
        },
        "postal_code":"10032",
        ...

there is the next_results_key.

Step 2. Previously-received "next_results_key"

By specifying a previously-received next_results_key (from a search response), you can gradually access the entire set of search results. A new next_results_key is sent with every response, so you can access the entire result set by iteratively querying for the latest result keys.

If results_key is specified and is not "create", then the only fields allowed are api_key and results_key; the requested fields and search terms are stored on the server with the rotating result_set key.

There are no more results once a returned page has a "venues" field that is the empty list.

Note: Searches done with a session are not scored or sorted! Result sessions are for bulk fetching of data; not realtime ranked searches.

Here is a sample request for the previously shown example:

curl -X POST https://api.locu.com/v2/venue/search -d '{
  "api_key" : ".......",
  "results_key" : "c2Nhbjs2OzEwfasKHDKJHFfasdjQ7"
}'

Extra-large requests

Note: This functionality is only enabled for certain API keys, depending on contractual details.

When requesting only the fields locu_id or locu_id and locu.last_modified, the limit on the number of results returned (or returned per page for paginated queries) is dramatically increased. For example, setting limit as high as 20,000 is supported.

curl -X POST https://api.locu.com/v2/venue/search -d '{
  "api_key" : ".......",
  "fields" : [ "locu_id", "locu.last_modified" ],
  "results_key" : "create",
  "limit" : 20000,
  "venue_queries" : [
    {
      "locu": {
        "last_modified": {
          "$ge": "2000-01-30T00:00:00"
        }
      }
    }
  ]
}'

Note that paginating these results is still necessary, because occasionally Locu might run data normalization scripts that can modify every venue in the database. Partners doing regular scraping should be prepared for an extra-large set of results when requesting the latest venues.

Workflow for tracking all venue updates from Locu

The Locu-suggested workflow for tracking venue updates on Locu is to regularly poll our API for changed venues. Since Locu may occasionally make changes to a large number of venues as part of our data quality processes, it is important to use a robust scraping strategy. The strategy we suggest is as follows (we go into detail on each step below):

  1. Create a queue of venues to ingest from Locu
  2. Create a script to get the list of venue IDs that changed after a specific time, and put them on the queue
  3. Create a script to get venue details (menu, listings data, extended attributes, etc) for blocks of venues from the queue and ingest their data
  4. Poll Locu every minute for updated venues, adding them to the ingestion queue

Step 1: Create the queue of venues to ingest

Typically the queue is stored in a database or asynchronous messaging system, but a simple text file is sufficient if there are not multiple concurrent ingestion workers.

The queue should include both Locu's venue ID (locu_id) and the last modified field (locu.last_modified). The last modified date is necessary to keep track of the newest last-modified date to query for at each update. The queue should support figuring out what the most recent last-modified date is of all the venues in the queue.

Step 2: Write a script to find updated venues

Write a script to query Locu for venues updated after a certain date. The query should be similar to the one in the section "Extra-large requests", in that it should have a very high limit (20,000 or so) and should use pagination, even though most of the time there will not be many venues updated. The last step of the script is to add the returned venues to the "venues to ingest" queue, ignoring ones that are duplicated. Example query:

curl -X POST https://api.locu.com/v2/venue/search -d '{
  "api_key" : ".......",
  "fields" : [ "locu_id", "locu.last_modified" ],
  "results_key" : "create",
  "limit" : 20000,
  "venue_queries" : [
    {
      "locu": {
        "last_modified": {
          "$ge": "2000-01-30T00:00:00"
        }
      }
    }
  ]
}'

Note: This is a paginated query, so multiple requests with subsequent result_key arguments may be necessary to get all the results. See the above section Paginating Results.

Determining the date to specify for locu.last_modified To pick the locu.last_modified date after which to request modified venues, take the newest date among:

  • The newest locu.last_modified date of any venue in your custom to-ingest queue
  • The newest locu.last_modified date for any venue previously ingested (and is thus not in your ingest queue)

By selecting the date this way, you will never re-fetch venues you already fetched. However, venues can get modified while they are already in the ingest queue, so be prepared to update the last modified date for a venue already in the queue.

Step 3: Write a script to ingest venues from the queue

Write another script to pull venues from the queue in batches of ~250, and fetch all the information for them and store it. An example query for one batch might be:

curl -X POST https://api.locu.com/v2/venue/search -d '{
  "api_key" : ".......",
  "fields" : [ "locu_id", "location", "menus"  ],
  "limit" : "250",
  "venue_queries" : [
    { "locu_id":"245995e54ca1274a8ea6" },
    { "locu_id":"b690bad62d6b2dc91712" },
    { "locu_id":"ba6aef1b05051f4bd24f" },
    { "locu_id":"af95b1013e3a47e221ad" },
    { "locu_id":"3939e00ddc8fb4a2784e" },
    { "locu_id":"62aacb16d0b8ab084c8e" },
    { "locu_id":"453e4b7e0b401a3cb978" }
    ...
    (up to 250 venues)
  ]
}'

As each batch finishes, remove those venues from the queue. Note that the queue can get emptied in parallel; there can be multiple workers flushing the queue.

Step 4: Poll regularly and empty the queue

At regular intervals poll Locu using script #2 to update the queue with the set of venues to ingest, and then run script #3 repeatedly to empty the queue.

Backend suggestions

At Locu, if we were to scrape our own API we would use normal unix cron or our compute cluster scheduler in order to poll for updates every minute, and create a SQS/RabbitMQ queue for queueing tasks to asynchronous ingestion workers.

A simpler approach is to combine all the functionality into one script. The risk with using one script is that with huge numbers of venues to ingest, the ingest script can fall behind.