# Locations

Source: https://ceyo.ai/docs/signal/locations

### Locations

Provision and manage locations in the selected project.

> **Authentication and scope**
>
> Send `Authorization: Bearer ceyo_platform_...` on every request. The API key selects the workspace, so paths never require a workspace identifier.

> **Packages and project modes**
>
> A standard project selects an active project package with `package_id`. Every location selects an active location package. A `locations_only` project is a container for locations, has no project package, and is not itself a tracking scope. Package assignments cannot be changed through resource updates.

> **Pagination, filters, and ordering**
>
> List endpoints use 1-based `page` and `per_page`, return pagination metadata, and return an empty array when the page is beyond the result set. Filters combine with AND. Search is trimmed, case-insensitive, and limited to 200 characters. Sorts are stable and use resource ID as the final ascending tie-breaker.

### List locations

`GET /projects/{project_id}/locations`

Returns locations in a project. Filters combine with AND and the selected sort is deterministic.

#### Path parameters

| Name | Type | Details | Description |
| --- | --- | --- | --- |
| `project_id` | project UUID \| project external ID | Required | Ceyo project UUID or configured partner external ID. |

#### Query parameters

| Name | Type | Details | Description |
| --- | --- | --- | --- |
| `q` | string | Optional | Case-insensitive search across name, external\_id, formatted address, city, state, postal code, country, phone, and email. Maximum: 200 characters. |
| `status` | active \| inactive \| archived | Optional | Return locations in one lifecycle status. |
| `country_code` | ISO 3166-1 alpha-2 string | Optional | Match the location’s explicit country code. |
| `sort` | created\_at \| updated\_at \| name | Optional; Default: created\_at | Field used for ordering. |
| `direction` | asc \| desc | Optional; Default: desc | Sort direction. |
| `page` | integer | Optional; Default: 1 | The 1-based page number. |
| `per_page` | integer | Optional; Default: 25 | Number of records per page, from 1 through 100. Values outside this range return 422. |

#### Response envelope

`locations`:**Location\[\]**`pagination`:**Pagination**

| Name | Type | Details | Description |
| --- | --- | --- | --- |
| `locations` | Location\[\] |  | Matching locations in the requested deterministic sort order. |
| `pagination` | Pagination |  | Pagination metadata. |

#### Location

| Name | Type | Details | Description |
| --- | --- | --- | --- |
| `id` | uuid |  | Location identifier. |
| `project_id` | uuid |  | Identifier of the containing project. |
| `external_id` | string \| null |  | Case-sensitive identifier supplied by the partner. |
| `name` | string |  | Location display name. |
| `description` | string \| null |  | Location description. |
| `website` | string \| null |  | Normalized HTTP or HTTPS location website. |
| `brand_aliases` | string\[\] |  | Normalized alternative names for the location brand. |
| `language` | ISO 639-1 string \| null |  | Location content language, or null when not overridden. |
| `action_language` | ISO 639-1 string \| null |  | Location action language, or null when not overridden. |
| `include_parent_brand` | boolean \| null |  | Whether the parent project brand is included. |
| `competitors` | Competitor\[\] |  | Active tracked competitors. Compatible tracked projection of the dedicated Competitors contract; suggested and dismissed records are excluded. |
| `phone` | string \| null |  | Partner-supplied contact phone number. |
| `email` | string \| null |  | Normalized contact email address. |
| `metadata` | object |  | Partner-owned JSON metadata. Keys and values are returned without interpretation. |
| `local_context` | object |  | Partner-supplied local facts used to contextualize processing. |
| `focus` | global \| country \| region \| city |  | Configured geographic targeting focus. |
| `google_place_id` | string \| null |  | Google place identifier. |
| `google_place_source` | provided \| discovered \| null |  | Whether the Google place was supplied by the customer or matched during onboarding. |
| `google_place_name` | string \| null |  | Business name associated with the Google place. |
| `google_maps_url` | string \| null |  | Google Maps URL for the location. |
| `formatted_address` | string \| null |  | Formatted physical address. |
| `address_line_2` | string \| null |  | Optional second address line. |
| `city` | string \| null |  | Normalized city. |
| `state` | string \| null |  | Normalized region or state. |
| `postal_code` | string \| null |  | Normalized postal code. |
| `country` | string \| null |  | Normalized country name. |
| `country_code` | string \| null |  | Explicit uppercase ISO 3166-1 alpha-2 country code, or null when not configured. Locations do not inherit the project country. |
| `latitude` | number \| null |  | Latitude from -90 through 90. |
| `longitude` | number \| null |  | Longitude from -180 through 180. |
| `status` | active \| inactive \| archived |  | Current location lifecycle status. |
| `package` | PackageReference |  | Assigned location package. |
| `created_at` | datetime |  | Location creation time in ISO 8601 format. |
| `updated_at` | datetime |  | Most recent location update time in ISO 8601 format. |

#### PackageReference

| Name | Type | Details | Description |
| --- | --- | --- | --- |
| `id` | uuid |  | Assigned package identifier. |
| `name` | string |  | Assigned package name. |

#### Competitor

| Name | Type | Details | Description |
| --- | --- | --- | --- |
| `id` | uuid |  | Competitor identifier. |
| `kind` | competitor |  | Entity role. Always competitor in this projection. |
| `name` | string |  | Competitor display name. |
| `domain` | string |  | Normalized hostname without a scheme, path, query, leading www, or trailing dot. Required for tracked competitors. |
| `aliases` | string\[\] |  | Additional names recognized for the competitor. |
| `status` | active |  | Tracked competitors always participate in current processing. |
| `competitor_state` | tracked |  | Management lifecycle state. This projection contains tracked competitors only. |
| `created_at` | datetime |  | Competitor creation time in ISO 8601 format. |
| `updated_at` | datetime |  | Most recent competitor update time in ISO 8601 format. |

#### Metadata and local context object

| Name | Type | Details | Description |
| --- | --- | --- | --- |
| `additional properties` | JSON value |  | Arbitrary partner-owned keys with string, number, boolean, null, object, or array values. |
| `maximum size` | 16 KB |  | Limit measured after JSON serialization. |

#### Pagination

| Name | Type | Details | Description |
| --- | --- | --- | --- |
| `page` | integer |  | Current 1-based page. |
| `per_page` | integer |  | Number of records requested per page. |
| `total` | integer |  | Total records matching the request. |
| `total_pages` | integer |  | Total available pages. |

#### Request and response

```curl
curl --request GET \
  --url 'https://api.signal.ceyo.ai/v1/projects/e6c96c98-d777-40e0-94ec-48931f57782f/locations?q=amsterdam&status=active&sort=name&direction=asc&page=1&per_page=25' \
  --header 'Authorization: Bearer ceyo_platform_...'
```

```json
{
  "locations": [
    {
      "id": "a1308d14-149c-4dd7-a4c5-295ac9090f58",
      "project_id": "e6c96c98-d777-40e0-94ec-48931f57782f",
      "external_id": "partner-location-amsterdam",
      "name": "Acme Amsterdam",
      "description": "Acme flagship store in Amsterdam.",
      "website": "https://acme.example/amsterdam",
      "brand_aliases": [
        "acme amsterdam"
      ],
      "language": "en",
      "action_language": "en",
      "include_parent_brand": true,
      "competitors": [
        {
          "id": "63ec8dad-c12f-43c8-89e4-06eb629d0977",
          "kind": "competitor",
          "name": "Example Rival",
          "domain": "example-rival.com",
          "aliases": [
            "rival"
          ],
          "status": "active",
          "competitor_state": "tracked",
          "created_at": "2026-07-02T11:20:00Z",
          "updated_at": "2026-07-30T09:10:00Z"
        }
      ],
      "phone": "+31 20 555 0100",
      "email": "amsterdam@acme.example",
      "metadata": {
        "partner_region_id": "nl-west"
      },
      "local_context": {
        "neighborhood": "Centrum",
        "service_area": "Amsterdam"
      },
      "focus": "city",
      "google_place_id": "ChIJN1t_tDeuEmsRUsoyG83frY4",
      "google_place_source": "provided",
      "google_place_name": "Acme Amsterdam",
      "google_maps_url": "https://maps.google.com/?cid=123456789",
      "formatted_address": "1 Market Street, 1012 JS Amsterdam, Netherlands",
      "address_line_2": null,
      "city": "Amsterdam",
      "state": "North Holland",
      "postal_code": "1012 JS",
      "country": "Netherlands",
      "country_code": null,
      "latitude": 52.3728,
      "longitude": 4.8936,
      "status": "active",
      "package": {
        "id": "8ae92d3f-18fb-4899-aef0-11f50b8bd0a7",
        "name": "Local Growth Weekly"
      },
      "created_at": "2026-07-31T08:10:00Z",
      "updated_at": "2026-07-31T08:10:00Z"
    }
  ],
  "pagination": {
    "page": 1,
    "per_page": 25,
    "total": 1,
    "total_pages": 1
  }
}
```

#### Errors

#### Error response envelope

| Name | Type | Details | Description |
| --- | --- | --- | --- |
| `error` | Error |  | Structured error payload. |

#### Error

| Name | Type | Details | Description |
| --- | --- | --- | --- |
| `code` | string |  | Stable snake\_case code suitable for programmatic handling. |
| `message` | string |  | Human-readable explanation of the failure. |
| `details` | object \| array \| null |  | Structured validation or request context when available. |
| `request_id` | string |  | Identifier to provide when requesting support. |

```json
{
  "error": {
    "code": "validation_failed",
    "message": "One or more fields are invalid.",
    "details": {
      "name": [
        "must be present"
      ]
    },
    "request_id": "req_01K1JQY1RQQ7N3C5H1K6J0P8AT"
  }
}
```

#### Status codes

| Name | Type | Details | Description |
| --- | --- | --- | --- |
| `400` | invalid\_request |  | A path value, query parameter, or JSON body is malformed. |
| `401` | invalid\_api\_key |  | The Bearer API key is absent or invalid. |
| `403` | forbidden |  | The API key cannot perform this operation. |
| `404` | not\_found |  | The requested project or location was not found. |
| `422` | validation\_failed |  | One or more fields are invalid, or package\_id does not identify an active package of the required type. |
| `429` | rate\_limit\_exceeded |  | Too many requests were made. |

### Create location

`POST /projects/{project_id}/locations`

Synchronously provisions a location under a project and returns it. Optionally starts onboarding after creation.

#### Path parameters

| Name | Type | Details | Description |
| --- | --- | --- | --- |
| `project_id` | project UUID \| project external ID | Required | Ceyo project UUID or configured partner external ID. |

#### Request body

| Name | Type | Details | Description |
| --- | --- | --- | --- |
| `name` | string |  | Required location name. Maximum: 200 characters. |
| `package_id` | uuid |  | Required active location package identifier from the same workspace. |
| `external_id` | string \| null |  | Optional partner identifier, unique among locations in this project. |
| `description` | string \| null |  | Optional description. Maximum: 5,000 characters. |
| `website` | string \| null |  | Valid HTTP or HTTPS URL; maximum 2,048 characters. |
| `brand_aliases` | string\[\] |  | Up to 10 alternative names, each at most 200 characters. Values are normalized and deduplicated. |
| `language` | ISO 639-1 string \| null |  | Optional lowercase content-language override. Null uses the project location default, then en. |
| `action_language` | ISO 639-1 string \| null |  | Optional lowercase action-language override. Null uses the project location default, then the effective content language. |
| `include_parent_brand` | boolean \| null |  | Optional parent-brand override. Null uses the project location default. |
| `phone` | string \| null |  | Contact phone number; maximum 50 characters. |
| `email` | string \| null |  | Valid contact email; maximum 320 characters. |
| `metadata` | object |  | Partner-owned JSON object, maximum serialized size 16 KB. Defaults to {}. |
| `local_context` | object |  | Local context JSON object, maximum serialized size 16 KB. Defaults to {}. |
| `focus` | global \| country \| region \| city |  | Geographic targeting focus. Defaults to city. |
| `google_place_id` | string \| null |  | Google place identifier, unique in the project. When supplied, Ceyo validates it and resolves canonical place details. Maximum: 500 characters. |
| `google_place_name` | string \| null |  | Google place business name. Maximum: 200 characters. |
| `google_maps_url` | string \| null |  | Google Maps URL. Maximum: 2,048 characters. |
| `formatted_address` | string \| null |  | Formatted physical address. Maximum: 500 characters. |
| `address_line_2` | string \| null |  | Optional suite, unit, or floor. Preserved separately from the canonical address. Maximum: 200 characters. |
| `city` | string \| null |  | City. Maximum: 200 characters. |
| `state` | string \| null |  | Region or state. Maximum: 200 characters. |
| `postal_code` | string \| null |  | Postal code. Maximum: 200 characters. |
| `country` | string \| null |  | Country name. Maximum: 200 characters. |
| `country_code` | string \| null |  | ISO 3166-1 alpha-2 code. Null leaves the location country code unset; locations do not inherit the project country. |
| `latitude` | number \| null |  | Latitude from -90 through 90. Latitude and longitude must be supplied together. |
| `longitude` | number \| null |  | Longitude from -180 through 180. Latitude and longitude must be supplied together. |
| `start` | boolean |  | When true, starts onboarding after provisioning. Supply either google\_place\_id or both city and country\_code. Defaults to false. |

> **Location package**
>
> `package_id` must identify an active location package in the same workspace. The location can provide its own `country_code`. If omitted, the location country code remains unset; it does not inherit the project country.

> **Automatic or manual place data**
>
> With `google_place_id`, Ceyo validates the place and fills canonical place details. Without it, provide your own location data. For `start: true`, `city` and `country_code` are enough; onboarding then tries to find a high-confidence Google match in the background.

> **Best-effort listing match**
>
> Customer-supplied fields are kept when a match is found. If no clear match exists, onboarding continues and listing analysis is skipped.

#### Response envelope

`location`:**Location**`onboarding_operation`:**OnboardingOperation | null**

| Name | Type | Details | Description |
| --- | --- | --- | --- |
| `location` | Location |  | The provisioned location. |
| `onboarding_operation` | OnboardingOperation \| null |  | Polling operation when start is true; null when onboarding was not requested. |

#### Location

| Name | Type | Details | Description |
| --- | --- | --- | --- |
| `id` | uuid |  | Location identifier. |
| `project_id` | uuid |  | Identifier of the containing project. |
| `external_id` | string \| null |  | Case-sensitive identifier supplied by the partner. |
| `name` | string |  | Location display name. |
| `description` | string \| null |  | Location description. |
| `website` | string \| null |  | Normalized HTTP or HTTPS location website. |
| `brand_aliases` | string\[\] |  | Normalized alternative names for the location brand. |
| `language` | ISO 639-1 string \| null |  | Location content language, or null when not overridden. |
| `action_language` | ISO 639-1 string \| null |  | Location action language, or null when not overridden. |
| `include_parent_brand` | boolean \| null |  | Whether the parent project brand is included. |
| `competitors` | Competitor\[\] |  | Active tracked competitors. Compatible tracked projection of the dedicated Competitors contract; suggested and dismissed records are excluded. |
| `phone` | string \| null |  | Partner-supplied contact phone number. |
| `email` | string \| null |  | Normalized contact email address. |
| `metadata` | object |  | Partner-owned JSON metadata. Keys and values are returned without interpretation. |
| `local_context` | object |  | Partner-supplied local facts used to contextualize processing. |
| `focus` | global \| country \| region \| city |  | Configured geographic targeting focus. |
| `google_place_id` | string \| null |  | Google place identifier. |
| `google_place_source` | provided \| discovered \| null |  | Whether the Google place was supplied by the customer or matched during onboarding. |
| `google_place_name` | string \| null |  | Business name associated with the Google place. |
| `google_maps_url` | string \| null |  | Google Maps URL for the location. |
| `formatted_address` | string \| null |  | Formatted physical address. |
| `address_line_2` | string \| null |  | Optional second address line. |
| `city` | string \| null |  | Normalized city. |
| `state` | string \| null |  | Normalized region or state. |
| `postal_code` | string \| null |  | Normalized postal code. |
| `country` | string \| null |  | Normalized country name. |
| `country_code` | string \| null |  | Explicit uppercase ISO 3166-1 alpha-2 country code, or null when not configured. Locations do not inherit the project country. |
| `latitude` | number \| null |  | Latitude from -90 through 90. |
| `longitude` | number \| null |  | Longitude from -180 through 180. |
| `status` | active \| inactive \| archived |  | Current location lifecycle status. |
| `package` | PackageReference |  | Assigned location package. |
| `created_at` | datetime |  | Location creation time in ISO 8601 format. |
| `updated_at` | datetime |  | Most recent location update time in ISO 8601 format. |

#### PackageReference

| Name | Type | Details | Description |
| --- | --- | --- | --- |
| `id` | uuid |  | Assigned package identifier. |
| `name` | string |  | Assigned package name. |

#### Competitor

| Name | Type | Details | Description |
| --- | --- | --- | --- |
| `id` | uuid |  | Competitor identifier. |
| `kind` | competitor |  | Entity role. Always competitor in this projection. |
| `name` | string |  | Competitor display name. |
| `domain` | string |  | Normalized hostname without a scheme, path, query, leading www, or trailing dot. Required for tracked competitors. |
| `aliases` | string\[\] |  | Additional names recognized for the competitor. |
| `status` | active |  | Tracked competitors always participate in current processing. |
| `competitor_state` | tracked |  | Management lifecycle state. This projection contains tracked competitors only. |
| `created_at` | datetime |  | Competitor creation time in ISO 8601 format. |
| `updated_at` | datetime |  | Most recent competitor update time in ISO 8601 format. |

#### Metadata and local context object

| Name | Type | Details | Description |
| --- | --- | --- | --- |
| `additional properties` | JSON value |  | Arbitrary partner-owned keys with string, number, boolean, null, object, or array values. |
| `maximum size` | 16 KB |  | Limit measured after JSON serialization. |

#### Request and response

```curl
curl --request POST \
  --url 'https://api.signal.ceyo.ai/v1/projects/e6c96c98-d777-40e0-94ec-48931f57782f/locations' \
  --header 'Authorization: Bearer ceyo_platform_...' \
  --header 'Content-Type: application/json' \
  --data '{
  "name": "Acme Amsterdam",
  "external_id": "partner-location-amsterdam",
  "package_id": "8ae92d3f-18fb-4899-aef0-11f50b8bd0a7",
  "website": "https://acme.example/amsterdam",
  "language": "nl",
  "action_language": "en",
  "include_parent_brand": true,
  "phone": "+31 20 555 0100",
  "email": "amsterdam@acme.example",
  "metadata": {
    "partner_region_id": "nl-west"
  },
  "local_context": {
    "neighborhood": "Centrum",
    "service_area": "Amsterdam"
  },
  "focus": "city",
  "google_place_id": "ChIJN1t_tDeuEmsRUsoyG83frY4",
  "address_line_2": "Suite 4",
  "start": true
}'
```

```json
HTTP/1.1 201 Created

{
  "location": {
    "id": "a1308d14-149c-4dd7-a4c5-295ac9090f58",
    "project_id": "e6c96c98-d777-40e0-94ec-48931f57782f",
    "external_id": "partner-location-amsterdam",
    "name": "Acme Amsterdam",
    "description": "Acme flagship store in Amsterdam.",
    "website": "https://acme.example/amsterdam",
    "brand_aliases": [
      "acme amsterdam"
    ],
    "language": "en",
    "action_language": "en",
    "include_parent_brand": true,
    "competitors": [
      {
        "id": "63ec8dad-c12f-43c8-89e4-06eb629d0977",
        "kind": "competitor",
        "name": "Example Rival",
        "domain": "example-rival.com",
        "aliases": [
          "rival"
        ],
        "status": "active",
        "competitor_state": "tracked",
        "created_at": "2026-07-02T11:20:00Z",
        "updated_at": "2026-07-30T09:10:00Z"
      }
    ],
    "phone": "+31 20 555 0100",
    "email": "amsterdam@acme.example",
    "metadata": {
      "partner_region_id": "nl-west"
    },
    "local_context": {
      "neighborhood": "Centrum",
      "service_area": "Amsterdam"
    },
    "focus": "city",
    "google_place_id": "ChIJN1t_tDeuEmsRUsoyG83frY4",
    "google_place_source": "provided",
    "google_place_name": "Acme Amsterdam",
    "google_maps_url": "https://maps.google.com/?cid=123456789",
    "formatted_address": "1 Market Street, 1012 JS Amsterdam, Netherlands",
    "address_line_2": "Suite 4",
    "city": "Amsterdam",
    "state": "North Holland",
    "postal_code": "1012 JS",
    "country": "Netherlands",
    "country_code": null,
    "latitude": 52.3728,
    "longitude": 4.8936,
    "status": "active",
    "package": {
      "id": "8ae92d3f-18fb-4899-aef0-11f50b8bd0a7",
      "name": "Local Growth Weekly"
    },
    "created_at": "2026-07-31T08:10:00Z",
    "updated_at": "2026-07-31T08:10:00Z"
  },
  "onboarding_operation": {
    "id": "1c07ea43-a8fe-4d07-8741-9c624d67b466",
    "status": "queued",
    "resource_type": "location",
    "resource_id": "a1308d14-149c-4dd7-a4c5-295ac9090f58",
    "progress": {
      "completed": 0,
      "total": 6
    },
    "message": "Onboarding is queued.",
    "steps": [
      {
        "key": "enrichment",
        "status": "pending"
      },
      {
        "key": "topics",
        "status": "pending"
      },
      {
        "key": "prompts",
        "status": "pending"
      },
      {
        "key": "competitors",
        "status": "pending"
      },
      {
        "key": "visibility",
        "status": "pending"
      },
      {
        "key": "diagnosis",
        "status": "pending"
      }
    ],
    "status_url": "/v1/onboarding-operations/1c07ea43-a8fe-4d07-8741-9c624d67b466",
    "created_at": "2026-08-04T15:00:00Z",
    "updated_at": "2026-08-04T15:00:00Z"
  }
}
```

#### Errors

#### Error response envelope

| Name | Type | Details | Description |
| --- | --- | --- | --- |
| `error` | Error |  | Structured error payload. |

#### Error

| Name | Type | Details | Description |
| --- | --- | --- | --- |
| `code` | string |  | Stable snake\_case code suitable for programmatic handling. |
| `message` | string |  | Human-readable explanation of the failure. |
| `details` | object \| array \| null |  | Structured validation or request context when available. |
| `request_id` | string |  | Identifier to provide when requesting support. |

```json
{
  "error": {
    "code": "validation_failed",
    "message": "One or more fields are invalid.",
    "details": {
      "name": [
        "must be present"
      ]
    },
    "request_id": "req_01K1JQY1RQQ7N3C5H1K6J0P8AT"
  }
}
```

#### Status codes

| Name | Type | Details | Description |
| --- | --- | --- | --- |
| `400` | invalid\_request |  | A path value, query parameter, or JSON body is malformed. |
| `401` | invalid\_api\_key |  | The Bearer API key is absent or invalid. |
| `403` | forbidden |  | The API key cannot perform this operation. |
| `404` | not\_found |  | The requested project or location was not found. |
| `409` | conflict |  | The external ID is already in use or the resource cannot accept this operation in its current state. |
| `422` | validation\_failed |  | One or more fields are invalid, or package\_id does not identify an active package of the required type. |
| `429` | rate\_limit\_exceeded |  | Too many requests were made. |
| `503` | place\_details\_unavailable |  | A supplied Google Place ID could not be resolved because place details are temporarily unavailable. |

### Bulk create locations

`POST /projects/{project_id}/locations/bulk`

Accepts up to 100 location create records and provisions them asynchronously under one project.

#### Path parameters

| Name | Type | Details | Description |
| --- | --- | --- | --- |
| `project_id` | project UUID \| project external ID | Required | Ceyo project UUID or configured partner external ID. |

#### Request body

| Name | Type | Details | Description |
| --- | --- | --- | --- |
| `locations` | LocationCreate\[\] |  | Between 1 and 100 records using the same fields and validation as Create location. |

> **Idempotency required**
>
> Send a unique `Idempotency-Key` header. Repeating the same request returns the existing operation. Reusing the key with a different body returns `409`.

> **Independent onboarding**
>
> Set `start` separately on each record. Successful records with `start: true` enqueue location onboarding; one failed record does not roll back the others.

#### Response envelope

`bulk_operation`:**BulkOperation**

Accepted operation summary with an ID and status\_url for polling.

#### Request and response

```curl
curl --request POST \
  --url 'https://api.signal.ceyo.ai/v1/projects/e6c96c98-d777-40e0-94ec-48931f57782f/locations/bulk' \
  --header 'Authorization: Bearer ceyo_platform_...' \
  --header 'Idempotency-Key: provision-2026-08-04-001' \
  --header 'Content-Type: application/json' \
  --data '{
  "locations": [
    {
      "name": "Acme Amsterdam",
      "external_id": "partner-location-amsterdam",
      "package_id": "8ae92d3f-18fb-4899-aef0-11f50b8bd0a7",
      "website": "https://acme.example/amsterdam",
      "language": "nl",
      "action_language": "en",
      "include_parent_brand": true,
      "phone": "+31 20 555 0100",
      "email": "amsterdam@acme.example",
      "metadata": {
        "partner_region_id": "nl-west"
      },
      "local_context": {
        "neighborhood": "Centrum",
        "service_area": "Amsterdam"
      },
      "focus": "city",
      "google_place_id": "ChIJN1t_tDeuEmsRUsoyG83frY4",
      "address_line_2": "Suite 4",
      "start": true
    },
    {
      "name": "Acme Rotterdam",
      "external_id": "partner-location-rotterdam",
      "package_id": "8ae92d3f-18fb-4899-aef0-11f50b8bd0a7",
      "website": "https://acme.example/amsterdam",
      "language": "nl",
      "action_language": "en",
      "include_parent_brand": true,
      "phone": "+31 20 555 0100",
      "email": "amsterdam@acme.example",
      "metadata": {
        "partner_region_id": "nl-west"
      },
      "local_context": {
        "neighborhood": "Centrum",
        "service_area": "Amsterdam"
      },
      "focus": "city",
      "google_place_id": "ChIJN1t_tDeuEmsRUsoyG83frY4",
      "address_line_2": "Suite 4",
      "start": true,
      "city": "Rotterdam"
    }
  ]
}'
```

```json
HTTP/1.1 202 Accepted

{
  "bulk_operation": {
    "id": "f9bc15cc-e9c9-4e93-a93e-b713c92c7315",
    "type": "locations",
    "status": "pending",
    "parent_project_id": "e6c96c98-d777-40e0-94ec-48931f57782f",
    "total": 2,
    "pending": 2,
    "succeeded": 0,
    "failed": 0,
    "created_at": "2026-08-04T15:00:00Z",
    "started_at": null,
    "completed_at": null,
    "status_url": "/v1/bulk-operations/f9bc15cc-e9c9-4e93-a93e-b713c92c7315"
  }
}
```

Poll the shared [Get bulk operation](/docs/signal/bulk-operations#get-bulk-operation) endpoint for per-record results.

#### Errors

#### Error response envelope

| Name | Type | Details | Description |
| --- | --- | --- | --- |
| `error` | Error |  | Structured error payload. |

#### Error

| Name | Type | Details | Description |
| --- | --- | --- | --- |
| `code` | string |  | Stable snake\_case code suitable for programmatic handling. |
| `message` | string |  | Human-readable explanation of the failure. |
| `details` | object \| array \| null |  | Structured validation or request context when available. |
| `request_id` | string |  | Identifier to provide when requesting support. |

```json
{
  "error": {
    "code": "validation_failed",
    "message": "One or more fields are invalid.",
    "details": {
      "name": [
        "must be present"
      ]
    },
    "request_id": "req_01K1JQY1RQQ7N3C5H1K6J0P8AT"
  }
}
```

#### Status codes

| Name | Type | Details | Description |
| --- | --- | --- | --- |
| `400` | invalid\_request |  | A path value, query parameter, or JSON body is malformed. |
| `401` | invalid\_api\_key |  | The Bearer API key is absent or invalid. |
| `403` | forbidden |  | The API key cannot perform this operation. |
| `404` | not\_found |  | The requested project or location was not found. |
| `409` | conflict |  | The external ID is already in use or the resource cannot accept this operation in its current state. |
| `422` | validation\_failed |  | One or more fields are invalid, or package\_id does not identify an active package of the required type. |
| `429` | rate\_limit\_exceeded |  | Too many requests were made. |

### Get location

`GET /projects/{project_id}/locations/{location_id}`

Returns one location by its Ceyo UUID within the selected project.

#### Path parameters

| Name | Type | Details | Description |
| --- | --- | --- | --- |
| `project_id` | project UUID \| project external ID | Required | Ceyo project UUID or configured partner external ID. |
| `location_id` | location UUID \| location external ID | Required | Ceyo location UUID or configured partner external ID belonging to the project. |

#### Response envelope

`location`:**Location**

The requested, created, or updated location. The envelope is identical for UUID and external-ID lookup.

#### Location

| Name | Type | Details | Description |
| --- | --- | --- | --- |
| `id` | uuid |  | Location identifier. |
| `project_id` | uuid |  | Identifier of the containing project. |
| `external_id` | string \| null |  | Case-sensitive identifier supplied by the partner. |
| `name` | string |  | Location display name. |
| `description` | string \| null |  | Location description. |
| `website` | string \| null |  | Normalized HTTP or HTTPS location website. |
| `brand_aliases` | string\[\] |  | Normalized alternative names for the location brand. |
| `language` | ISO 639-1 string \| null |  | Location content language, or null when not overridden. |
| `action_language` | ISO 639-1 string \| null |  | Location action language, or null when not overridden. |
| `include_parent_brand` | boolean \| null |  | Whether the parent project brand is included. |
| `competitors` | Competitor\[\] |  | Active tracked competitors. Compatible tracked projection of the dedicated Competitors contract; suggested and dismissed records are excluded. |
| `phone` | string \| null |  | Partner-supplied contact phone number. |
| `email` | string \| null |  | Normalized contact email address. |
| `metadata` | object |  | Partner-owned JSON metadata. Keys and values are returned without interpretation. |
| `local_context` | object |  | Partner-supplied local facts used to contextualize processing. |
| `focus` | global \| country \| region \| city |  | Configured geographic targeting focus. |
| `google_place_id` | string \| null |  | Google place identifier. |
| `google_place_source` | provided \| discovered \| null |  | Whether the Google place was supplied by the customer or matched during onboarding. |
| `google_place_name` | string \| null |  | Business name associated with the Google place. |
| `google_maps_url` | string \| null |  | Google Maps URL for the location. |
| `formatted_address` | string \| null |  | Formatted physical address. |
| `address_line_2` | string \| null |  | Optional second address line. |
| `city` | string \| null |  | Normalized city. |
| `state` | string \| null |  | Normalized region or state. |
| `postal_code` | string \| null |  | Normalized postal code. |
| `country` | string \| null |  | Normalized country name. |
| `country_code` | string \| null |  | Explicit uppercase ISO 3166-1 alpha-2 country code, or null when not configured. Locations do not inherit the project country. |
| `latitude` | number \| null |  | Latitude from -90 through 90. |
| `longitude` | number \| null |  | Longitude from -180 through 180. |
| `status` | active \| inactive \| archived |  | Current location lifecycle status. |
| `package` | PackageReference |  | Assigned location package. |
| `created_at` | datetime |  | Location creation time in ISO 8601 format. |
| `updated_at` | datetime |  | Most recent location update time in ISO 8601 format. |

#### PackageReference

| Name | Type | Details | Description |
| --- | --- | --- | --- |
| `id` | uuid |  | Assigned package identifier. |
| `name` | string |  | Assigned package name. |

#### Competitor

| Name | Type | Details | Description |
| --- | --- | --- | --- |
| `id` | uuid |  | Competitor identifier. |
| `kind` | competitor |  | Entity role. Always competitor in this projection. |
| `name` | string |  | Competitor display name. |
| `domain` | string |  | Normalized hostname without a scheme, path, query, leading www, or trailing dot. Required for tracked competitors. |
| `aliases` | string\[\] |  | Additional names recognized for the competitor. |
| `status` | active |  | Tracked competitors always participate in current processing. |
| `competitor_state` | tracked |  | Management lifecycle state. This projection contains tracked competitors only. |
| `created_at` | datetime |  | Competitor creation time in ISO 8601 format. |
| `updated_at` | datetime |  | Most recent competitor update time in ISO 8601 format. |

#### Metadata and local context object

| Name | Type | Details | Description |
| --- | --- | --- | --- |
| `additional properties` | JSON value |  | Arbitrary partner-owned keys with string, number, boolean, null, object, or array values. |
| `maximum size` | 16 KB |  | Limit measured after JSON serialization. |

#### Request and response

```curl
curl --request GET \
  --url 'https://api.signal.ceyo.ai/v1/projects/e6c96c98-d777-40e0-94ec-48931f57782f/locations/a1308d14-149c-4dd7-a4c5-295ac9090f58' \
  --header 'Authorization: Bearer ceyo_platform_...'
```

```json
{
  "location": {
    "id": "a1308d14-149c-4dd7-a4c5-295ac9090f58",
    "project_id": "e6c96c98-d777-40e0-94ec-48931f57782f",
    "external_id": "partner-location-amsterdam",
    "name": "Acme Amsterdam",
    "description": "Acme flagship store in Amsterdam.",
    "website": "https://acme.example/amsterdam",
    "brand_aliases": [
      "acme amsterdam"
    ],
    "language": "en",
    "action_language": "en",
    "include_parent_brand": true,
    "competitors": [
      {
        "id": "63ec8dad-c12f-43c8-89e4-06eb629d0977",
        "kind": "competitor",
        "name": "Example Rival",
        "domain": "example-rival.com",
        "aliases": [
          "rival"
        ],
        "status": "active",
        "competitor_state": "tracked",
        "created_at": "2026-07-02T11:20:00Z",
        "updated_at": "2026-07-30T09:10:00Z"
      }
    ],
    "phone": "+31 20 555 0100",
    "email": "amsterdam@acme.example",
    "metadata": {
      "partner_region_id": "nl-west"
    },
    "local_context": {
      "neighborhood": "Centrum",
      "service_area": "Amsterdam"
    },
    "focus": "city",
    "google_place_id": "ChIJN1t_tDeuEmsRUsoyG83frY4",
    "google_place_source": "provided",
    "google_place_name": "Acme Amsterdam",
    "google_maps_url": "https://maps.google.com/?cid=123456789",
    "formatted_address": "1 Market Street, 1012 JS Amsterdam, Netherlands",
    "address_line_2": null,
    "city": "Amsterdam",
    "state": "North Holland",
    "postal_code": "1012 JS",
    "country": "Netherlands",
    "country_code": null,
    "latitude": 52.3728,
    "longitude": 4.8936,
    "status": "active",
    "package": {
      "id": "8ae92d3f-18fb-4899-aef0-11f50b8bd0a7",
      "name": "Local Growth Weekly"
    },
    "created_at": "2026-07-31T08:10:00Z",
    "updated_at": "2026-07-31T08:10:00Z"
  }
}
```

#### Errors

#### Error response envelope

| Name | Type | Details | Description |
| --- | --- | --- | --- |
| `error` | Error |  | Structured error payload. |

#### Error

| Name | Type | Details | Description |
| --- | --- | --- | --- |
| `code` | string |  | Stable snake\_case code suitable for programmatic handling. |
| `message` | string |  | Human-readable explanation of the failure. |
| `details` | object \| array \| null |  | Structured validation or request context when available. |
| `request_id` | string |  | Identifier to provide when requesting support. |

```json
{
  "error": {
    "code": "validation_failed",
    "message": "One or more fields are invalid.",
    "details": {
      "name": [
        "must be present"
      ]
    },
    "request_id": "req_01K1JQY1RQQ7N3C5H1K6J0P8AT"
  }
}
```

#### Status codes

| Name | Type | Details | Description |
| --- | --- | --- | --- |
| `400` | invalid\_request |  | A path value, query parameter, or JSON body is malformed. |
| `401` | invalid\_api\_key |  | The Bearer API key is absent or invalid. |
| `403` | forbidden |  | The API key cannot perform this operation. |
| `404` | not\_found |  | The requested project or location was not found. |
| `429` | rate\_limit\_exceeded |  | Too many requests were made. |

### Get locations overview

`GET /projects/{project_id}/locations/overview`

Returns a paginated location comparison view and map markers for the filtered result set.

#### Path parameters

| Name | Type | Details | Description |
| --- | --- | --- | --- |
| `project_id` | project UUID \| project external ID | Required | Ceyo project UUID or configured partner external ID. |

#### Query parameters

| Name | Type | Details | Description |
| --- | --- | --- | --- |
| `q` | string | Optional | Case-insensitive search across location name, external\_id, formatted address, city, state, postal code, and country. Maximum: 200 characters. |
| `status` | active \| inactive | Optional | Return one non-archived lifecycle status. Archived locations are excluded. |
| `country_code` | ISO 3166-1 alpha-2 string | Optional | Match the location’s explicit country code. |
| `sort` | name \| visibility\_rate \| avg\_position | Optional; Default: name | Field used for ordering. Null metrics sort after non-null values in either direction. |
| `direction` | asc \| desc | Optional; Default: asc | Sort direction. When sort is not name and direction is omitted, the default is desc. |
| `page` | integer | Optional; Default: 1 | The 1-based page number. |
| `per_page` | integer | Optional; Default: 25 | Number of records per page, from 1 through 100. Values outside this range return 422. |

#### Response envelope

`locations`:**LocationOverview\[\]**`pagination`:**Pagination**`map`:**LocationMap**

| Name | Type | Details | Description |
| --- | --- | --- | --- |
| `locations` | LocationOverview\[\] |  | The requested page of matching locations. |
| `pagination` | Pagination |  | Pagination over the filtered and sorted location set. |
| `map` | LocationMap |  | Markers for the complete filtered set. |

#### LocationOverview

| Name | Type | Details | Description |
| --- | --- | --- | --- |
| `id` | uuid |  | Location identifier. |
| `external_id` | string \| null |  | Partner-supplied location identifier. |
| `name` | string |  | Location display name. |
| `status` | active \| inactive |  | Current non-archived lifecycle status. |
| `formatted_address` | string \| null |  | Formatted physical address. |
| `city` | string \| null |  | Normalized city. |
| `state` | string \| null |  | Normalized region or state. |
| `country_code` | string |  | Effective uppercase ISO 3166-1 alpha-2 country code. |
| `latitude` | number \| null |  | Latitude, or null when unavailable. |
| `longitude` | number \| null |  | Longitude, or null when unavailable. |
| `visibility_summary` | VisibilitySummary |  | Latest completed 30-day location visibility summary. |

#### VisibilitySummary

| Name | Type | Details | Description |
| --- | --- | --- | --- |
| `visibility_rate` | number \| null |  | Percentage of included responses that mentioned the brand; null when unavailable. |
| `avg_position` | number \| null |  | Average 1-based brand position when present; null when no ranked mention is available. |

#### Pagination

| Name | Type | Details | Description |
| --- | --- | --- | --- |
| `page` | integer |  | Current 1-based page. |
| `per_page` | integer |  | Number of records requested per page. |
| `total` | integer |  | Total records matching the request. |
| `total_pages` | integer |  | Total available pages. |

#### LocationMap

| Name | Type | Details | Description |
| --- | --- | --- | --- |
| `markers` | LocationMarker\[\] |  | Markers ordered by location name, then location ID. Only active locations with both coordinates are eligible. |

#### LocationMarker

| Name | Type | Details | Description |
| --- | --- | --- | --- |
| `id` | uuid |  | Location identifier. |
| `external_id` | string \| null |  | Partner-supplied location identifier. |
| `name` | string |  | Location display name. |
| `formatted_address` | string \| null |  | Formatted address used in map labels. |
| `latitude` | number |  | Marker latitude. |
| `longitude` | number |  | Marker longitude. |
| `visibility_rate` | number \| null |  | Latest 30-day brand visibility percentage. |
| `avg_position` | number \| null |  | Latest 30-day average brand position. |

> **Metrics, map, and sorting**
>
> Visibility fields use the latest completed 30-day window for each location. The page and marker set use the same search and filters. Pagination affects `locations` only. Every sort uses name and then location ID as ascending tie-breakers.

#### Request and response

```curl
curl --request GET \
  --url 'https://api.signal.ceyo.ai/v1/projects/e6c96c98-d777-40e0-94ec-48931f57782f/locations/overview?q=amsterdam&status=active&sort=visibility_rate&direction=desc&page=1&per_page=25' \
  --header 'Authorization: Bearer ceyo_platform_...'
```

```json
{
  "locations": [
    {
      "id": "a1308d14-149c-4dd7-a4c5-295ac9090f58",
      "external_id": "partner-location-amsterdam",
      "name": "Acme Amsterdam",
      "status": "active",
      "formatted_address": "1 Market Street, 1012 JS Amsterdam, Netherlands",
      "city": "Amsterdam",
      "state": "North Holland",
      "country_code": "NL",
      "latitude": 52.3728,
      "longitude": 4.8936,
      "visibility_summary": {
        "visibility_rate": 68.4,
        "avg_position": 2.7
      }
    }
  ],
  "pagination": {
    "page": 1,
    "per_page": 25,
    "total": 1,
    "total_pages": 1
  },
  "map": {
    "markers": [
      {
        "id": "a1308d14-149c-4dd7-a4c5-295ac9090f58",
        "external_id": "partner-location-amsterdam",
        "name": "Acme Amsterdam",
        "formatted_address": "1 Market Street, 1012 JS Amsterdam, Netherlands",
        "latitude": 52.3728,
        "longitude": 4.8936,
        "visibility_rate": 68.4,
        "avg_position": 2.7
      }
    ]
  }
}
```

#### Errors

#### Error response envelope

| Name | Type | Details | Description |
| --- | --- | --- | --- |
| `error` | Error |  | Structured error payload. |

#### Error

| Name | Type | Details | Description |
| --- | --- | --- | --- |
| `code` | string |  | Stable snake\_case code suitable for programmatic handling. |
| `message` | string |  | Human-readable explanation of the failure. |
| `details` | object \| array \| null |  | Structured validation or request context when available. |
| `request_id` | string |  | Identifier to provide when requesting support. |

```json
{
  "error": {
    "code": "validation_failed",
    "message": "One or more fields are invalid.",
    "details": {
      "name": [
        "must be present"
      ]
    },
    "request_id": "req_01K1JQY1RQQ7N3C5H1K6J0P8AT"
  }
}
```

#### Status codes

| Name | Type | Details | Description |
| --- | --- | --- | --- |
| `400` | invalid\_request |  | A path value, query parameter, or JSON body is malformed. |
| `401` | invalid\_api\_key |  | The Bearer API key is absent or invalid. |
| `403` | forbidden |  | The API key cannot perform this operation. |
| `404` | not\_found |  | The requested project or location was not found. |
| `422` | validation\_failed |  | One or more fields are invalid, or package\_id does not identify an active package of the required type. |
| `429` | rate\_limit\_exceeded |  | Too many requests were made. |

### Find location by external ID

`GET /projects/{project_id}/locations/by-external-id/{external_id}`

Returns the location whose external\_id exactly matches the URL-encoded value within the selected project.

#### Path parameters

| Name | Type | Details | Description |
| --- | --- | --- | --- |
| `project_id` | project UUID \| project external ID | Required | Ceyo project UUID or configured partner external ID. |
| `external_id` | string | Required | URL-encoded, case-sensitive external ID previously assigned to the resource. |

> **Project-scoped lookup**
>
> Location external IDs are unique within a project and matching is case-sensitive. An empty or malformed path value returns `400`; an unknown value or a value assigned in another project returns `404`.

#### Response envelope

`location`:**Location**

The requested, created, or updated location. The envelope is identical for UUID and external-ID lookup.

#### Location

| Name | Type | Details | Description |
| --- | --- | --- | --- |
| `id` | uuid |  | Location identifier. |
| `project_id` | uuid |  | Identifier of the containing project. |
| `external_id` | string \| null |  | Case-sensitive identifier supplied by the partner. |
| `name` | string |  | Location display name. |
| `description` | string \| null |  | Location description. |
| `website` | string \| null |  | Normalized HTTP or HTTPS location website. |
| `brand_aliases` | string\[\] |  | Normalized alternative names for the location brand. |
| `language` | ISO 639-1 string \| null |  | Location content language, or null when not overridden. |
| `action_language` | ISO 639-1 string \| null |  | Location action language, or null when not overridden. |
| `include_parent_brand` | boolean \| null |  | Whether the parent project brand is included. |
| `competitors` | Competitor\[\] |  | Active tracked competitors. Compatible tracked projection of the dedicated Competitors contract; suggested and dismissed records are excluded. |
| `phone` | string \| null |  | Partner-supplied contact phone number. |
| `email` | string \| null |  | Normalized contact email address. |
| `metadata` | object |  | Partner-owned JSON metadata. Keys and values are returned without interpretation. |
| `local_context` | object |  | Partner-supplied local facts used to contextualize processing. |
| `focus` | global \| country \| region \| city |  | Configured geographic targeting focus. |
| `google_place_id` | string \| null |  | Google place identifier. |
| `google_place_source` | provided \| discovered \| null |  | Whether the Google place was supplied by the customer or matched during onboarding. |
| `google_place_name` | string \| null |  | Business name associated with the Google place. |
| `google_maps_url` | string \| null |  | Google Maps URL for the location. |
| `formatted_address` | string \| null |  | Formatted physical address. |
| `address_line_2` | string \| null |  | Optional second address line. |
| `city` | string \| null |  | Normalized city. |
| `state` | string \| null |  | Normalized region or state. |
| `postal_code` | string \| null |  | Normalized postal code. |
| `country` | string \| null |  | Normalized country name. |
| `country_code` | string \| null |  | Explicit uppercase ISO 3166-1 alpha-2 country code, or null when not configured. Locations do not inherit the project country. |
| `latitude` | number \| null |  | Latitude from -90 through 90. |
| `longitude` | number \| null |  | Longitude from -180 through 180. |
| `status` | active \| inactive \| archived |  | Current location lifecycle status. |
| `package` | PackageReference |  | Assigned location package. |
| `created_at` | datetime |  | Location creation time in ISO 8601 format. |
| `updated_at` | datetime |  | Most recent location update time in ISO 8601 format. |

#### PackageReference

| Name | Type | Details | Description |
| --- | --- | --- | --- |
| `id` | uuid |  | Assigned package identifier. |
| `name` | string |  | Assigned package name. |

#### Competitor

| Name | Type | Details | Description |
| --- | --- | --- | --- |
| `id` | uuid |  | Competitor identifier. |
| `kind` | competitor |  | Entity role. Always competitor in this projection. |
| `name` | string |  | Competitor display name. |
| `domain` | string |  | Normalized hostname without a scheme, path, query, leading www, or trailing dot. Required for tracked competitors. |
| `aliases` | string\[\] |  | Additional names recognized for the competitor. |
| `status` | active |  | Tracked competitors always participate in current processing. |
| `competitor_state` | tracked |  | Management lifecycle state. This projection contains tracked competitors only. |
| `created_at` | datetime |  | Competitor creation time in ISO 8601 format. |
| `updated_at` | datetime |  | Most recent competitor update time in ISO 8601 format. |

#### Metadata and local context object

| Name | Type | Details | Description |
| --- | --- | --- | --- |
| `additional properties` | JSON value |  | Arbitrary partner-owned keys with string, number, boolean, null, object, or array values. |
| `maximum size` | 16 KB |  | Limit measured after JSON serialization. |

#### Request and response

```curl
curl --request GET \
  --url 'https://api.signal.ceyo.ai/v1/projects/e6c96c98-d777-40e0-94ec-48931f57782f/locations/by-external-id/partner-location-amsterdam' \
  --header 'Authorization: Bearer ceyo_platform_...'
```

```json
{
  "location": {
    "id": "a1308d14-149c-4dd7-a4c5-295ac9090f58",
    "project_id": "e6c96c98-d777-40e0-94ec-48931f57782f",
    "external_id": "partner-location-amsterdam",
    "name": "Acme Amsterdam",
    "description": "Acme flagship store in Amsterdam.",
    "website": "https://acme.example/amsterdam",
    "brand_aliases": [
      "acme amsterdam"
    ],
    "language": "en",
    "action_language": "en",
    "include_parent_brand": true,
    "competitors": [
      {
        "id": "63ec8dad-c12f-43c8-89e4-06eb629d0977",
        "kind": "competitor",
        "name": "Example Rival",
        "domain": "example-rival.com",
        "aliases": [
          "rival"
        ],
        "status": "active",
        "competitor_state": "tracked",
        "created_at": "2026-07-02T11:20:00Z",
        "updated_at": "2026-07-30T09:10:00Z"
      }
    ],
    "phone": "+31 20 555 0100",
    "email": "amsterdam@acme.example",
    "metadata": {
      "partner_region_id": "nl-west"
    },
    "local_context": {
      "neighborhood": "Centrum",
      "service_area": "Amsterdam"
    },
    "focus": "city",
    "google_place_id": "ChIJN1t_tDeuEmsRUsoyG83frY4",
    "google_place_source": "provided",
    "google_place_name": "Acme Amsterdam",
    "google_maps_url": "https://maps.google.com/?cid=123456789",
    "formatted_address": "1 Market Street, 1012 JS Amsterdam, Netherlands",
    "address_line_2": null,
    "city": "Amsterdam",
    "state": "North Holland",
    "postal_code": "1012 JS",
    "country": "Netherlands",
    "country_code": null,
    "latitude": 52.3728,
    "longitude": 4.8936,
    "status": "active",
    "package": {
      "id": "8ae92d3f-18fb-4899-aef0-11f50b8bd0a7",
      "name": "Local Growth Weekly"
    },
    "created_at": "2026-07-31T08:10:00Z",
    "updated_at": "2026-07-31T08:10:00Z"
  }
}
```

#### Errors

#### Error response envelope

| Name | Type | Details | Description |
| --- | --- | --- | --- |
| `error` | Error |  | Structured error payload. |

#### Error

| Name | Type | Details | Description |
| --- | --- | --- | --- |
| `code` | string |  | Stable snake\_case code suitable for programmatic handling. |
| `message` | string |  | Human-readable explanation of the failure. |
| `details` | object \| array \| null |  | Structured validation or request context when available. |
| `request_id` | string |  | Identifier to provide when requesting support. |

```json
{
  "error": {
    "code": "validation_failed",
    "message": "One or more fields are invalid.",
    "details": {
      "name": [
        "must be present"
      ]
    },
    "request_id": "req_01K1JQY1RQQ7N3C5H1K6J0P8AT"
  }
}
```

#### Status codes

| Name | Type | Details | Description |
| --- | --- | --- | --- |
| `400` | invalid\_request |  | A path value, query parameter, or JSON body is malformed. |
| `401` | invalid\_api\_key |  | The Bearer API key is absent or invalid. |
| `403` | forbidden |  | The API key cannot perform this operation. |
| `404` | not\_found |  | The requested project or location was not found. |
| `429` | rate\_limit\_exceeded |  | Too many requests were made. |

### Update location

`PATCH /projects/{project_id}/locations/{location_id}`

Updates only supplied location settings. Omitted fields remain unchanged.

#### Path parameters

| Name | Type | Details | Description |
| --- | --- | --- | --- |
| `project_id` | project UUID \| project external ID | Required | Ceyo project UUID or configured partner external ID. |
| `location_id` | location UUID \| location external ID | Required | Ceyo location UUID or configured partner external ID belonging to the project. |

#### Request body

| Name | Type | Details | Description |
| --- | --- | --- | --- |
| `name` | string |  | New location name. Maximum: 200 characters. |
| `external_id` | string \| null |  | partner identifier, unique among locations in this project. |
| `description` | string \| null |  | description. Maximum: 5,000 characters. |
| `website` | string \| null |  | Valid HTTP or HTTPS URL; maximum 2,048 characters. |
| `brand_aliases` | string\[\] |  | Up to 10 alternative names, each at most 200 characters. Values are normalized and deduplicated. |
| `language` | ISO 639-1 string \| null |  | lowercase content-language override. Null uses the project location default, then en. |
| `action_language` | ISO 639-1 string \| null |  | lowercase action-language override. Null uses the project location default, then the effective content language. |
| `include_parent_brand` | boolean \| null |  | parent-brand override. Null uses the project location default. |
| `phone` | string \| null |  | Contact phone number; maximum 50 characters. |
| `email` | string \| null |  | Valid contact email; maximum 320 characters. |
| `metadata` | object |  | Partner-owned JSON object, maximum serialized size 16 KB. |
| `local_context` | object |  | Local context JSON object, maximum serialized size 16 KB. |
| `focus` | global \| country \| region \| city |  | Geographic targeting focus. |
| `google_place_id` | string \| null |  | Google place identifier, unique in the project. When supplied, Ceyo validates it and resolves canonical place details. Maximum: 500 characters. |
| `google_place_name` | string \| null |  | Google place business name. Maximum: 200 characters. |
| `google_maps_url` | string \| null |  | Google Maps URL. Maximum: 2,048 characters. |
| `formatted_address` | string \| null |  | Formatted physical address. Maximum: 500 characters. |
| `address_line_2` | string \| null |  | suite, unit, or floor. Preserved separately from the canonical address. Maximum: 200 characters. |
| `city` | string \| null |  | City. Maximum: 200 characters. |
| `state` | string \| null |  | Region or state. Maximum: 200 characters. |
| `postal_code` | string \| null |  | Postal code. Maximum: 200 characters. |
| `country` | string \| null |  | Country name. Maximum: 200 characters. |
| `country_code` | string \| null |  | ISO 3166-1 alpha-2 code. Null leaves the location country code unset; locations do not inherit the project country. |
| `latitude` | number \| null |  | Latitude from -90 through 90. Latitude and longitude must be supplied together. |
| `longitude` | number \| null |  | Longitude from -180 through 180. Latitude and longitude must be supplied together. |
| `address_line_2` | string \| null |  | Replacement optional second address line. |

> **Replacement and clearing behavior**
>
> Project membership and package assignment are immutable. Set nullable fields to `null` to clear them. `metadata` and `local_context` replace their complete objects; send `{}` to clear their keys. An empty `brand_aliases` array removes all aliases.

#### Response envelope

`location`:**Location**

The requested, created, or updated location. The envelope is identical for UUID and external-ID lookup.

#### Location

| Name | Type | Details | Description |
| --- | --- | --- | --- |
| `id` | uuid |  | Location identifier. |
| `project_id` | uuid |  | Identifier of the containing project. |
| `external_id` | string \| null |  | Case-sensitive identifier supplied by the partner. |
| `name` | string |  | Location display name. |
| `description` | string \| null |  | Location description. |
| `website` | string \| null |  | Normalized HTTP or HTTPS location website. |
| `brand_aliases` | string\[\] |  | Normalized alternative names for the location brand. |
| `language` | ISO 639-1 string \| null |  | Location content language, or null when not overridden. |
| `action_language` | ISO 639-1 string \| null |  | Location action language, or null when not overridden. |
| `include_parent_brand` | boolean \| null |  | Whether the parent project brand is included. |
| `competitors` | Competitor\[\] |  | Active tracked competitors. Compatible tracked projection of the dedicated Competitors contract; suggested and dismissed records are excluded. |
| `phone` | string \| null |  | Partner-supplied contact phone number. |
| `email` | string \| null |  | Normalized contact email address. |
| `metadata` | object |  | Partner-owned JSON metadata. Keys and values are returned without interpretation. |
| `local_context` | object |  | Partner-supplied local facts used to contextualize processing. |
| `focus` | global \| country \| region \| city |  | Configured geographic targeting focus. |
| `google_place_id` | string \| null |  | Google place identifier. |
| `google_place_source` | provided \| discovered \| null |  | Whether the Google place was supplied by the customer or matched during onboarding. |
| `google_place_name` | string \| null |  | Business name associated with the Google place. |
| `google_maps_url` | string \| null |  | Google Maps URL for the location. |
| `formatted_address` | string \| null |  | Formatted physical address. |
| `address_line_2` | string \| null |  | Optional second address line. |
| `city` | string \| null |  | Normalized city. |
| `state` | string \| null |  | Normalized region or state. |
| `postal_code` | string \| null |  | Normalized postal code. |
| `country` | string \| null |  | Normalized country name. |
| `country_code` | string \| null |  | Explicit uppercase ISO 3166-1 alpha-2 country code, or null when not configured. Locations do not inherit the project country. |
| `latitude` | number \| null |  | Latitude from -90 through 90. |
| `longitude` | number \| null |  | Longitude from -180 through 180. |
| `status` | active \| inactive \| archived |  | Current location lifecycle status. |
| `package` | PackageReference |  | Assigned location package. |
| `created_at` | datetime |  | Location creation time in ISO 8601 format. |
| `updated_at` | datetime |  | Most recent location update time in ISO 8601 format. |

#### PackageReference

| Name | Type | Details | Description |
| --- | --- | --- | --- |
| `id` | uuid |  | Assigned package identifier. |
| `name` | string |  | Assigned package name. |

#### Competitor

| Name | Type | Details | Description |
| --- | --- | --- | --- |
| `id` | uuid |  | Competitor identifier. |
| `kind` | competitor |  | Entity role. Always competitor in this projection. |
| `name` | string |  | Competitor display name. |
| `domain` | string |  | Normalized hostname without a scheme, path, query, leading www, or trailing dot. Required for tracked competitors. |
| `aliases` | string\[\] |  | Additional names recognized for the competitor. |
| `status` | active |  | Tracked competitors always participate in current processing. |
| `competitor_state` | tracked |  | Management lifecycle state. This projection contains tracked competitors only. |
| `created_at` | datetime |  | Competitor creation time in ISO 8601 format. |
| `updated_at` | datetime |  | Most recent competitor update time in ISO 8601 format. |

#### Metadata and local context object

| Name | Type | Details | Description |
| --- | --- | --- | --- |
| `additional properties` | JSON value |  | Arbitrary partner-owned keys with string, number, boolean, null, object, or array values. |
| `maximum size` | 16 KB |  | Limit measured after JSON serialization. |

#### Request and response

```curl
curl --request PATCH \
  --url 'https://api.signal.ceyo.ai/v1/projects/e6c96c98-d777-40e0-94ec-48931f57782f/locations/a1308d14-149c-4dd7-a4c5-295ac9090f58' \
  --header 'Authorization: Bearer ceyo_platform_...' \
  --header 'Content-Type: application/json' \
  --data '{
  "name": "Acme Amsterdam Centrum",
  "description": "Acme flagship store in central Amsterdam.",
  "brand_aliases": [
    "acme amsterdam",
    "acme centrum"
  ],
  "phone": "+31 20 555 0199",
  "email": "centrum@acme.example",
  "metadata": {
    "partner_region_id": "nl-central"
  },
  "local_context": {
    "neighborhood": "Centrum",
    "service_area": "Amsterdam city center"
  },
  "focus": "city"
}'
```

```json
{
  "location": {
    "id": "a1308d14-149c-4dd7-a4c5-295ac9090f58",
    "project_id": "e6c96c98-d777-40e0-94ec-48931f57782f",
    "external_id": "partner-location-amsterdam",
    "name": "Acme Amsterdam Centrum",
    "description": "Acme flagship store in central Amsterdam.",
    "website": "https://acme.example/amsterdam",
    "brand_aliases": [
      "acme amsterdam",
      "acme centrum"
    ],
    "language": "en",
    "action_language": "en",
    "include_parent_brand": true,
    "competitors": [
      {
        "id": "63ec8dad-c12f-43c8-89e4-06eb629d0977",
        "kind": "competitor",
        "name": "Example Rival",
        "domain": "example-rival.com",
        "aliases": [
          "rival"
        ],
        "status": "active",
        "competitor_state": "tracked",
        "created_at": "2026-07-02T11:20:00Z",
        "updated_at": "2026-07-30T09:10:00Z"
      }
    ],
    "phone": "+31 20 555 0199",
    "email": "centrum@acme.example",
    "metadata": {
      "partner_region_id": "nl-central"
    },
    "local_context": {
      "neighborhood": "Centrum",
      "service_area": "Amsterdam city center"
    },
    "focus": "city",
    "google_place_id": "ChIJN1t_tDeuEmsRUsoyG83frY4",
    "google_place_source": "provided",
    "google_place_name": "Acme Amsterdam",
    "google_maps_url": "https://maps.google.com/?cid=123456789",
    "formatted_address": "1 Market Street, 1012 JS Amsterdam, Netherlands",
    "address_line_2": null,
    "city": "Amsterdam",
    "state": "North Holland",
    "postal_code": "1012 JS",
    "country": "Netherlands",
    "country_code": null,
    "latitude": 52.3728,
    "longitude": 4.8936,
    "status": "active",
    "package": {
      "id": "8ae92d3f-18fb-4899-aef0-11f50b8bd0a7",
      "name": "Local Growth Weekly"
    },
    "created_at": "2026-07-31T08:10:00Z",
    "updated_at": "2026-07-31T12:10:00Z"
  }
}
```

#### Errors

#### Error response envelope

| Name | Type | Details | Description |
| --- | --- | --- | --- |
| `error` | Error |  | Structured error payload. |

#### Error

| Name | Type | Details | Description |
| --- | --- | --- | --- |
| `code` | string |  | Stable snake\_case code suitable for programmatic handling. |
| `message` | string |  | Human-readable explanation of the failure. |
| `details` | object \| array \| null |  | Structured validation or request context when available. |
| `request_id` | string |  | Identifier to provide when requesting support. |

```json
{
  "error": {
    "code": "validation_failed",
    "message": "One or more fields are invalid.",
    "details": {
      "name": [
        "must be present"
      ]
    },
    "request_id": "req_01K1JQY1RQQ7N3C5H1K6J0P8AT"
  }
}
```

#### Status codes

| Name | Type | Details | Description |
| --- | --- | --- | --- |
| `400` | invalid\_request |  | A path value, query parameter, or JSON body is malformed. |
| `401` | invalid\_api\_key |  | The Bearer API key is absent or invalid. |
| `403` | forbidden |  | The API key cannot perform this operation. |
| `404` | not\_found |  | The requested project or location was not found. |
| `409` | conflict |  | The external ID is already in use or the resource cannot accept this operation in its current state. |
| `422` | validation\_failed |  | One or more fields are invalid, or package\_id does not identify an active package of the required type. |
| `429` | rate\_limit\_exceeded |  | Too many requests were made. |

### Delete location

`DELETE /projects/{project_id}/locations/{location_id}`

Schedules asynchronous deletion of a location and its location-scoped resources.

#### Path parameters

| Name | Type | Details | Description |
| --- | --- | --- | --- |
| `project_id` | project UUID \| project external ID | Required | Ceyo project UUID or configured partner external ID. |
| `location_id` | location UUID \| location external ID | Required | Ceyo location UUID or configured partner external ID belonging to the project. |

> **202 Accepted**
>
> Deletion runs asynchronously and the response has no body.

#### Request and response

```curl
curl --request DELETE \
  --url 'https://api.signal.ceyo.ai/v1/projects/e6c96c98-d777-40e0-94ec-48931f57782f/locations/a1308d14-149c-4dd7-a4c5-295ac9090f58' \
  --header 'Authorization: Bearer ceyo_platform_...'
```

```json
HTTP/1.1 202 Accepted
```

#### Errors

#### Error response envelope

| Name | Type | Details | Description |
| --- | --- | --- | --- |
| `error` | Error |  | Structured error payload. |

#### Error

| Name | Type | Details | Description |
| --- | --- | --- | --- |
| `code` | string |  | Stable snake\_case code suitable for programmatic handling. |
| `message` | string |  | Human-readable explanation of the failure. |
| `details` | object \| array \| null |  | Structured validation or request context when available. |
| `request_id` | string |  | Identifier to provide when requesting support. |

```json
{
  "error": {
    "code": "validation_failed",
    "message": "One or more fields are invalid.",
    "details": {
      "name": [
        "must be present"
      ]
    },
    "request_id": "req_01K1JQY1RQQ7N3C5H1K6J0P8AT"
  }
}
```

#### Status codes

| Name | Type | Details | Description |
| --- | --- | --- | --- |
| `400` | invalid\_request |  | A path value, query parameter, or JSON body is malformed. |
| `401` | invalid\_api\_key |  | The Bearer API key is absent or invalid. |
| `403` | forbidden |  | The API key cannot perform this operation. |
| `404` | not\_found |  | The requested project or location was not found. |
| `429` | rate\_limit\_exceeded |  | Too many requests were made. |
