API Reference

Bulk CSV

The 16-column bulk CSV format for PlaceOptimizer location edits — column reference, validation rules, and the diff-and-apply flow.

The bulk CSV diff-and-apply workflow is PlaceOptimizer's flagship way to edit many locations at once. You export every location to a CSV, edit it in a spreadsheet, import it back, review a field-level diff, and apply only the changes you select. It lives at Locations → Bulk edit (/locations/bulk) in the operator console.

The workflow

  1. Export. Download every managed location as a CSV — one row per location, a fixed 16-column header.
  2. Edit. Open the CSV in a spreadsheet, change the values you want to correct. Each column maps to one field on the location; leave a cell empty to clear that field (or mark a day closed).
  3. Import and preview. Upload the edited CSV. The engine parses it, validates every row, and computes a field-level diff between the current locations and your proposed values — it never applies anything automatically.
  4. Review. The preview shows, per location, exactly which fields would change (before → after), plus any rows that could not be matched and any rows with validation errors.
  5. Apply. Select the locations whose changes you want (or all of them) and apply. Each location is applied independently — one failing location never aborts the rest of the batch.

The diff engine is pure and runs before anything touches your listings. The preview is honest: it reports matched, changed, unchanged, unknown, and erroring rows in one summary.

The 16-column format

The header row is fixed and human-readable for spreadsheet editing. Columns appear in this exact order:

#ColumnFieldRequiredNotes
1Store CodestoreCodeYesThe join key that matches a row to an existing location (case-insensitive). Cannot be empty.
2TitletitleYesThe location name. Cannot be empty.
3PhonephonePrimaryNoPrimary phone number.
4Address LineaddressLineNoFull street address (lines joined with , ).
5LocalitylocalityNoCity or locality.
6Admin AreaadminAreaNoState or admin area.
7Postal CodepostalCodeNoPIN / postal code.
8WebsitewebsiteNoWebsite URI.
9Primary CategoryprimaryCategoryNoThe location's primary GBP category.
10MondaymondayNoWeekly hours: HH:MM-HH:MM, closed, or empty (see below).
11TuesdaytuesdayNoSame format as Monday.
12WednesdaywednesdayNoSame format as Monday.
13ThursdaythursdayNoSame format as Monday.
14FridayfridayNoSame format as Monday.
15SaturdaysaturdayNoSame format as Monday.
16SundaysundayNoSame format as Monday.

That is 9 profile columns + 7 day columns = 16 columns.

Weekly hours format

Each day column accepts one of:

  • HH:MM-HH:MM — e.g. 09:00-21:00 (a single open period; hours 0–24).
  • closed — any case; the location is closed that day.
  • (empty) — treated as closed for a day with no trading hours.

The value is parsed with the same hours helpers the wire mapper uses, so what validates in the CSV is exactly what is applied.

Column → data model mapping

The diff engine maps each CSV column to a dotted path on the managed location model:

CSV columnData path
Titletitle
Phonephones.primary
Address Lineaddress.lines
Localityaddress.locality
Admin Areaaddress.adminArea
Postal Codeaddress.postalCode
WebsitewebsiteUri
Primary Categorycategories.primary
Monday…SundayregularHours.monday…regularHours.sunday

The preview reports changes as these dotted paths with before and after values, so what you approve is unambiguous.

Matching and validation rules

  • Store Code is the join key. A row matches a location when its Store Code matches an existing location's store code (trimmed, case-insensitive). Rows whose Store Code matches nothing are reported as unknown and are never silently skipped.
  • Store Code and Title are required — a row missing either is a validation error.
  • Column count is enforced. A row with the wrong number of columns is reported with its row number.
  • Hours are validated per cell. Anything that is not HH:MM-HH:MM or closed is an error on that row.
  • Parsing is RFC 4180. The exporter writes CRLF rows and quotes fields containing commas, quotes, or newlines; the importer tolerates BOM, CRLF or LF, quoted fields with embedded commas/newlines, and doubled quotes (Excel output shapes).

Errors and unknown rows

The preview never fails silently. It reports, per row number:

  • Validation errors — a required field missing, a malformed hours cell, or a wrong column count.
  • Unknown rows — valid rows whose Store Code does not match any existing location.

You see all of them before applying anything, and you can choose which matched locations to apply changes to.

Behind the scenes: the preview and apply endpoints

The console calls two REST endpoints (console-internal, session-guarded):

  • POST /api/v1/gbp/locations/import/preview with { "csv": "<csv text>" } returns the parsed preview: per-location diffs, unknown rows, issues, and a summary of rows, matched, changed, unchanged, unknown, and errors.
  • POST /api/v1/gbp/locations/import/apply with { "changes": [{ "locationRef": "...", "changes": [...] }] } applies the confirmed change sets (up to 1000), isolating per-location failures.

These endpoints back the console UI and are not part of the public supported API surface — use the bulk workflow through the console, or the authenticated MCP tools for programmatic access.

Copyright © 2026