GuideGuides/Recipes/Create a location
How do I create a business location via the API?

Create a Location with the Synup API

Create a business location in one POST, with or without a client, get back a locationId, and know which fields matter and which can wait.

At a glance

What this guide accomplishes
Create one location, under a client or on its own, and store the returned locationId against your own record.
APIs used
Reference
Locations
Prerequisites
  • An API key with write access
  • The clientId to file it under, if it belongs to a client
Typical use cases
  • Onboarding a new business into your product
  • Backfilling locations from an existing database
  • Adding a store to a multi-location account

A location needs a name, a country (countryIso) and a category to exist. Send those, plus whatever address you have, to POST /api/v1/locations and you get back a 201 and a locationId. Everything else on the profile can be patched in afterwards, so don't hold up the create call waiting for hours or photos.

curl -X POST https://ai.synup.com/api/v1/locations \
  -H "Authorization: Bearer $SYNUP_API_KEY" \
  -H "Content-Type: application/json" \
  -d '{
    "clientId": "CLIENT_ID",
    "name": "Grove Street Dental Mission",
    "countryIso": "US",
    "street": "88 Grove St",
    "city": "San Francisco",
    "stateIso": "CA",
    "postalCode": "94105",
    "phone": "+1 415 555 0142",
    "website": "https://grovestreetdental.example/downtown",
    "categoryName": "Dentist"
  }'
const API = "https://ai.synup.com/api/v1";
const headers = {
  Authorization: `Bearer ${process.env.SYNUP_API_KEY}`,
  "Content-Type": "application/json",
};

async function createLocation(clientId, profile) {
  const res = await fetch(`${API}/locations`, {
    method: "POST",
    headers,
    body: JSON.stringify({ clientId, ...profile }),
  });
  if (!res.ok) throw new Error(`${res.status}: ${(await res.json()).error}`);
  const { data } = await res.json();
  return data.locationId; // store this against your own record
}

await createLocation("CLIENT_ID", {
  name: "Grove Street Dental Mission",
  countryIso: "US",
  street: "88 Grove St",
  city: "San Francisco",
  stateIso: "CA",
  postalCode: "94105",
  phone: "+1 415 555 0142",
  website: "https://grovestreetdental.example/downtown",
  categoryName: "Dentist",
});
import os
import requests

API = "https://ai.synup.com/api/v1"
HEADERS = {"Authorization": f"Bearer {os.environ['SYNUP_API_KEY']}"}

def create_location(client_id, profile):
    r = requests.post(f"{API}/locations", headers=HEADERS, timeout=30,
                      json={"clientId": client_id, **profile})
    r.raise_for_status()
    return r.json()["data"]["locationId"]

create_location("CLIENT_ID", {
    "name": "Grove Street Dental Mission",
    "countryIso": "US",
    "street": "88 Grove St",
    "city": "San Francisco",
    "stateIso": "CA",
    "postalCode": "94105",
    "phone": "+1 415 555 0142",
    "website": "https://grovestreetdental.example/downtown",
    "categoryName": "Dentist",
})

Two field names that are not obvious. The address country is countryIso and the state is stateIso, both ISO codes, not the spelled-out names a read hands back. And categoryName is fuzzy-matched against Synup's catalog. If you want an exact category, resolve its id first with GET /api/v1/locations/categories.

There is no storeCode on the create body. If you keep your own id for the location, patch it on afterwards with PATCH /api/v1/locations/{id}, and store the locationId you got back so you never have to guess the mapping.

Businesses that hide their address

A plumber or a mobile groomer that works at the customer's place usually hides its street on Google and shows a service area instead. Leave the street fields out and send serviceArea with businessType set to CUSTOMER_LOCATION_ONLY:

"serviceArea": {
  "businessType": "CUSTOMER_LOCATION_ONLY",
  "places": [{ "name": "Austin, TX, USA", "placeId": "GOOGLE_PLACE_ID" }]
}

No publisher shows the street of a location set up this way, even one you store for your own records. Google gets an empty address on every update, Bing and the directories that support it get their own hide-address flag, and the rest are sent no street at all. Google and Bing publish the areas. Google only takes areas that carry a Google Place ID, so include placeId for every area you want shown there. Facebook and Apple have no list of service areas, so for them the setting only hides the street. You can set or change serviceArea later with PATCH /api/v1/locations/{id}, and null turns the location back into a regular storefront.

Connecting the location to an existing Google Business Profile later is safe too. If that profile already hides its address, Synup imports its service area and won't push an address onto it.

A different service area per publisher

serviceArea is the Master value, and every publisher uses it unless you give that publisher its own. To set one for Google, Facebook, Apple or Bing, send publisherServiceAreas keyed by publisher, each in the same shape:

"serviceArea": { "businessType": "CUSTOMER_LOCATION_ONLY", "places": [{ "name": "Austin, TX, USA", "placeId": "GOOGLE_PLACE_ID" }] },
"publisherServiceAreas": {
  "facebook": { "businessType": "CUSTOMER_AND_BUSINESS_LOCATION", "places": [] }
}

That works on create and on PATCH /api/v1/locations/{id}. On an update you have three ways to aim it:

  • serviceArea alone changes Master only.
  • serviceArea with applyToPublishers: ["google", "bing"] gives those publishers the same value and leaves Master as it is.
  • publisherServiceAreas gives each publisher its own value, and can ride along with serviceArea to change Master and the publishers in one call. Send null for a publisher to drop its own value so it follows Master again.

Each publisher then publishes its own value, and the app's location editor shows it on that publisher's tab. publisherServiceAreas and applyToPublishers can't be sent together, the call returns 422. Read the result back from GET /api/v1/locations/{id}: Master is serviceArea, and each publisher's own value sits in publisherOverrides.<publisher>.serviceArea.

With or without a client

clientId follows the same rules as the app's Add location flow:

  • Send it to file the location under one of your clients. It has to be a client in your workspace, or the call returns 404.
  • Leave it out and the location is created on its own, the same as picking "Without a client" in the app. You can move it under a client later with Change client in the app.
  • A key limited to specific clients can't do that, because it could never read the new location back. It has to send one of its own clients, or it gets a 400.

Want the client first? Create it with POST /api/v1/clients and use the id it returns. The full loop, from client to a location that is live on every publisher, is in the Business Listings API guide.