Add a location

Create a location with the fields publishers need, handle validation errors, and confirm it has been queued for submission.

Listings, reviews, posts, and analytics are all keyed to a location ID. POST /api/v4/locations creates the location and queues one listing per publisher in your plan.

1. Gather the required fields

Ten fields are required. A service-area business sends the full address as well: with hideAddress: true, ListingsAPI stores the address but does not submit it to publishers. Publishers render omitted optional fields as blank, so send everything you have.

FieldRequiredNotes
nameYes2 to 150 characters.
descriptionYesMinimum 200 characters (SY10005).
countryIsoYesTwo-letter code of a supported country (SY10036).
cityYesMandatory for countries with city-level addressing (SY10126).
subCategoryIdYesNumeric databaseId from the subcategories list.
streetYes5 to 100 characters (SY10011).
stateIsoYesValidated against the country (SY10010). Leave it out only for a country without states.
postalCodeYesValidated against the country (SY10014).
phoneYesValidated against the country (SY10015).
bizUrlYesPublished as the website. Some publishers refuse a listing without one.
storeIdNoYour own store code, unique across the account. Use it to find the location again without storing our ids.
businessHoursNoOne entry per weekday. See Update a location for the format.

2. Create the location

POST /api/v4/locations takes the fields in an input object in the JSON request body. The Python SDK method locations.add() takes the same fields as keyword arguments and validates the required ones before it sends the request. The Node SDK method createLocation() takes the camelCase object directly.

Create
curl -X POST https://listingsapi.com/api/v4/locations \-H "Authorization: API $LISTINGSAPI_KEY" \-H "Content-Type: application/json" \-d '{  "input": {    "name": "Acme Kitchen",    "storeId": "ACME-NYC-001",    "description": "Acme Kitchen is a family-owned neighborhood restaurant in downtown New York serving wood-fired pizza, hand-rolled pasta, and a short seasonal menu built around produce from nearby farms. Open for lunch and dinner seven days a week, with takeout and delivery available.",    "street": "123 Jump Street",    "city": "New York",    "stateIso": "NY",    "postalCode": "10013",    "countryIso": "US",    "phone": "6443859313",    "bizUrl": "https://acmekitchen.example.com",    "subCategoryId": 1432,    "businessHours": [      { "day": "MONDAY",    "type": "OPEN",   "slots": [{ "start": "11:00am", "end": "10:00pm" }] },      { "day": "TUESDAY",   "type": "OPEN",   "slots": [{ "start": "11:00am", "end": "10:00pm" }] },      { "day": "WEDNESDAY", "type": "OPEN",   "slots": [{ "start": "11:00am", "end": "10:00pm" }] },      { "day": "THURSDAY",  "type": "OPEN",   "slots": [{ "start": "11:00am", "end": "10:00pm" }] },      { "day": "FRIDAY",    "type": "OPEN",   "slots": [{ "start": "11:00am", "end": "11:00pm" }] },      { "day": "SATURDAY",  "type": "OPEN",   "slots": [{ "start": "12:00pm", "end": "11:00pm" }] },      { "day": "SUNDAY",    "type": "CLOSED", "slots": [] }    ]  }}'

3. Read the response

A successful create returns success: true and the new location in data.createLocation.location. Keep both identifiers: id is the base64 value every later write takes, and databaseId is the numeric value shown in dashboards and webhook payloads.

JSON
{  "data": {    "createLocation": {      "success": true,      "errors": null,      "location": {        "id": "TG9jYXRpb246MTgwMDI5MA==",        "databaseId": 1800290,        "storeId": "ACME-NYC-001",        "name": "Acme Kitchen",        "city": "New York",        "stateIso": "NY",        "countryIso": "US",        "status": "PENDING"      }    }  }}

status is PENDING while the location is validated and queued. It advances without a further API call.

4. Handle validation errors

A create can fail in two places. A malformed request or a bad key returns a 4xx with a top-level errors array. A well-formed request with a field the API rejects returns 200 with success: false and the problem inside data.createLocation.errors, so check the body of every write, not just the status code.

JSON
{  "data": {    "createLocation": {      "success": false,      "errors": [        {          "code": "SY10005",          "message": "SY10005: description must be at least 200 characters",          "path": ["input", "description"]        }      ],      "location": null    }  }}

Both SDKs raise on failure. Python's locations.add() checks the required fields locally and raises ValidationError before sending the request. Node's createLocation() throws ValidationError when the API rejects the input, which is why the sample above tests no success flag.

The codes you will meet most often:

CodeCauseFix
SY10005Description missing or under 200 characters.Write a fuller description.
SY10011street missing or shorter than 5 characters.Send street.
SY10010stateIso missing or not valid for the country.Send a state code from the countries endpoint.
SY10036countryIso is not a supported country.Use a code from the countries endpoint.
SY10126city missing for a country that requires it.Send city.
SY90016The key is Read only.Use a Write key.

5. Confirm submission has started

Creating the location queues one listing per publisher in your plan. Each listing starts at AVAILABLE or IN_PROGRESS and moves to SYNCED with a listingUrl once the publisher accepts it.

Listing status
curl https://listingsapi.com/api/v4/locations/TG9jYXRpb246MTgwMDI5MA==/listings/premium \-H "Authorization: API $LISTINGSAPI_KEY"

With webhooks configured, no polling is required. A profile.created event fires when the record exists, and a listing.submission event fires for each publisher as its status changes.

Next steps