Add a location

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

A location is the record every listing, review, post, and analytics figure attaches to. Creating one queues it for submission to every publisher in your plan, so a complete, accurate record on day one saves a round of corrections later.

1. Gather the required fields

Five fields are mandatory. The rest are optional but each one you leave out is a field publishers show as blank.

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). Only a service-area business with hideAddress: true may omit it.
subCategoryIdYesNumeric databaseId from the subcategories list.
street, stateIso, postalCode, phoneNoValidated against the country. Send them for any storefront.
bizUrlNoPublished 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

The REST body wraps every field in an input object. The Python SDK's locations.add() takes the same fields as keyword arguments and validates the mandatory ones before making the call. The Node SDK's 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 a summary of the new location. Keep both ids: the base64 id is what every later write needs, and databaseId is the plain number you will see in dashboards and webhooks.

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 starts as PENDING while the record is validated and queued. It moves to approved on its own; you do not have to act on it.

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    }  }}

The SDKs turn both cases into an exception. Python's locations.add() checks the mandatory fields locally and raises ValidationError before any network call, so a short description never leaves your machine. Node's createLocation() throws ValidationError when the API rejects the input, which is why there is no success flag to test in that sample.

The codes you will meet most often:

CodeCauseFix
SY10005Description missing or under 200 characters.Write a fuller description.
SY10036countryIso is not a supported country.Use a code from the countries endpoint.
SY10126city missing for a country that requires it.Send city, or set hideAddress: true for a service-area business.
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. Read them back and you will see each start at AVAILABLE or IN_PROGRESS, then move to SYNCED with a listingUrl as publishers accept it.

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

If you have webhooks configured you do not need to poll. A profile.created event fires when the record exists, and a listing.submission event fires for each publisher as its status changes.

Next steps