# Projects

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

### Projects

Provision and manage projects in the workspace selected by your API key.

> **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 projects

`GET /projects`

Returns projects visible to the API key. Filters combine with AND and the selected sort is deterministic.

#### Query parameters

| Name | Type | Details | Description |
| --- | --- | --- | --- |
| `q` | string | Optional | Case-insensitive search across project name, website, description, folder, and tag name. Maximum: 200 characters. |
| `folder` | string | Optional | Match one folder, case-insensitively. |
| `status` | active \| inactive \| archived | Optional | Return projects in one lifecycle status. |
| `project_mode` | standard \| locations\_only | Optional | Return projects in one mode. |
| `tag_id` | uuid | Optional | Return projects assigned to this workspace tag. |
| `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

`projects`:**Project\[\]**`pagination`:**Pagination**

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

#### Project

| Name | Type | Details | Description |
| --- | --- | --- | --- |
| `id` | uuid |  | Project identifier. |
| `workspace_id` | uuid |  | Identifier of the workspace that owns the project. |
| `external_id` | string \| null |  | Case-sensitive identifier supplied by the partner. |
| `name` | string |  | Project display name. |
| `description` | string \| null |  | Project description. |
| `website` | string \| null |  | Normalized HTTP or HTTPS website URL. |
| `brand_aliases` | string\[\] |  | Normalized alternative brand names. |
| `competitors` | Competitor\[\] |  | Active tracked competitors. Compatible tracked projection of the dedicated Competitors contract; suggested and dismissed records are excluded. |
| `project_mode` | standard \| locations\_only |  | Standard projects have their own tracking package. locations\_only projects act as containers for independently packaged locations. |
| `status` | active \| inactive \| archived |  | Current project lifecycle status. |
| `package` | PackageReference \| null |  | Assigned project package. Null when project\_mode is locations\_only. |
| `address` | string \| null |  | Normalized street address. |
| `city` | string \| null |  | Normalized city. |
| `state` | string \| null |  | Normalized region or state. |
| `postal_code` | string \| null |  | Normalized postal code. |
| `country` | string |  | Normalized country name. |
| `country_code` | string |  | Uppercase ISO 3166-1 alpha-2 country code. |
| `latitude` | number \| null |  | Latitude from -90 through 90. |
| `longitude` | number \| null |  | Longitude from -180 through 180. |
| `google_place_id` | string \| null |  | Google place identifier when one is configured. |
| `language` | string |  | Lowercase ISO 639-1 content language. |
| `action_language` | string \| null |  | Lowercase ISO 639-1 language used for generated actions. |
| `focus` | global \| country \| region \| city |  | Geographic targeting focus. |
| `local_mode` | boolean |  | Whether local geographic context is emphasized. |
| `local_context` | object |  | Resolved geographic context used by project processing. |
| `location_defaults` | object |  | Default language, action language, website, and parent-brand settings available to locations in this project. |
| `folder` | string \| null |  | Optional workspace organization folder. |
| `tags` | Tag\[\] |  | Up to three workspace tags assigned to the project. |
| `logo_url` | string \| null |  | Absolute project logo URL when configured. |
| `created_at` | datetime |  | Project creation time in ISO 8601 format. |
| `updated_at` | datetime |  | Most recent project 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. |

#### Tag

| Name | Type | Details | Description |
| --- | --- | --- | --- |
| `id` | uuid |  | Tag identifier. |
| `name` | string |  | Tag display name. |
| `color` | string |  | Tag display color. |

#### 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?q=acme&status=active&sort=created_at&direction=desc&page=1&per_page=25' \
  --header 'Authorization: Bearer ceyo_platform_...'
```

```json
{
  "projects": [
    {
      "id": "e6c96c98-d777-40e0-94ec-48931f57782f",
      "workspace_id": "b7dd886f-b144-4ab4-907b-8efde9df881a",
      "external_id": "partner-project-acme",
      "name": "Acme Europe",
      "description": "European visibility program for Acme.",
      "website": "https://acme.example/",
      "brand_aliases": [
        "acme",
        "acme europe"
      ],
      "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"
        }
      ],
      "project_mode": "standard",
      "status": "active",
      "package": {
        "id": "92404fd7-f096-49e9-9ab0-5ed73517d9db",
        "name": "Growth Weekly"
      },
      "address": "1 Market Street",
      "city": "Amsterdam",
      "state": "North Holland",
      "postal_code": "1012 JS",
      "country": "Netherlands",
      "country_code": "NL",
      "latitude": 52.3728,
      "longitude": 4.8936,
      "google_place_id": null,
      "language": "en",
      "action_language": "en",
      "focus": "country",
      "local_mode": false,
      "local_context": {},
      "location_defaults": {
        "language": "en",
        "action_language": "en",
        "website": "https://acme.example/",
        "include_parent_brand": true
      },
      "folder": "Europe",
      "tags": [
        {
          "id": "30761d13-bc7e-45c8-8968-2147a37d6e54",
          "name": "Retail",
          "color": "#2563EB"
        }
      ],
      "logo_url": null,
      "created_at": "2026-07-31T08:00:00Z",
      "updated_at": "2026-07-31T08:00: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. |
| `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 project

`POST /projects`

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

#### Request body

| Name | Type | Details | Description |
| --- | --- | --- | --- |
| `name` | string |  | Required project name. Maximum: 200 characters. |
| `project_mode` | standard \| locations\_only |  | Optional project mode. Defaults to standard. |
| `package_id` | uuid |  | Required for standard projects and omitted for locations\_only projects. Must identify an active project package. |
| `external_id` | string \| null |  | Optional partner identifier, unique across projects. Maximum: 200 characters. |
| `website` | string |  | Required for standard projects. Valid HTTP or HTTPS URL; maximum 2,048 characters. |
| `description` | string \| null |  | Optional description. Maximum: 5,000 characters. |
| `brand_aliases` | string\[\] |  | Up to 10 alternative names, each at most 200 characters. Values are trimmed, lowercased, and deduplicated. |
| `country_code` | string |  | Uppercase ISO 3166-1 alpha-2 country code. Defaults to US. |
| `country` | string |  | Country name. Defaults to country\_code. |
| `city` | string \| null |  | City; required when focus is city. Maximum: 200 characters. |
| `state` | string \| null |  | Region or state. Maximum: 200 characters. |
| `address` | string \| null |  | Street address. Maximum: 500 characters. |
| `postal_code` | string \| null |  | Postal code. Maximum: 200 characters. |
| `latitude` | number \| null |  | Latitude from -90 through 90. |
| `longitude` | number \| null |  | Longitude from -180 through 180. |
| `google_place_id` | string \| null |  | Google place identifier. Maximum: 500 characters. |
| `language` | ISO 639-1 string |  | Content language. Defaults to en. |
| `action_language` | ISO 639-1 string \| null |  | Optional language for generated actions. |
| `focus` | global \| country \| region \| city |  | Geographic targeting focus. Defaults to country. |
| `local_mode` | boolean |  | Enable local geographic context. Defaults to false. |
| `folder` | string \| null |  | Optional organization folder. Maximum: 30 characters. |
| `tag_ids` | uuid\[\] |  | Up to three tag identifiers from this workspace. |
| `tag_names` | string\[\] |  | Up to three tag names. Names are trimmed and matched case-insensitively; missing names are created. Each name is 1–25 characters and the workspace may contain at most 50 tags. Takes precedence over tag\_ids. |
| `start` | boolean |  | When true, starts onboarding immediately after synchronous provisioning succeeds. Must be false for locations\_only container projects. Defaults to false. |

> **Conditional requirements**
>
> For `project_mode: "standard"`, `package_id` and `website` are required. For `project_mode: "locations_only"`, omit `package_id`; locations created under the project select their own location packages.

#### Response envelope

`project`:**Project**`onboarding_operation`:**OnboardingOperation | null**

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

#### Project

| Name | Type | Details | Description |
| --- | --- | --- | --- |
| `id` | uuid |  | Project identifier. |
| `workspace_id` | uuid |  | Identifier of the workspace that owns the project. |
| `external_id` | string \| null |  | Case-sensitive identifier supplied by the partner. |
| `name` | string |  | Project display name. |
| `description` | string \| null |  | Project description. |
| `website` | string \| null |  | Normalized HTTP or HTTPS website URL. |
| `brand_aliases` | string\[\] |  | Normalized alternative brand names. |
| `competitors` | Competitor\[\] |  | Active tracked competitors. Compatible tracked projection of the dedicated Competitors contract; suggested and dismissed records are excluded. |
| `project_mode` | standard \| locations\_only |  | Standard projects have their own tracking package. locations\_only projects act as containers for independently packaged locations. |
| `status` | active \| inactive \| archived |  | Current project lifecycle status. |
| `package` | PackageReference \| null |  | Assigned project package. Null when project\_mode is locations\_only. |
| `address` | string \| null |  | Normalized street address. |
| `city` | string \| null |  | Normalized city. |
| `state` | string \| null |  | Normalized region or state. |
| `postal_code` | string \| null |  | Normalized postal code. |
| `country` | string |  | Normalized country name. |
| `country_code` | string |  | Uppercase ISO 3166-1 alpha-2 country code. |
| `latitude` | number \| null |  | Latitude from -90 through 90. |
| `longitude` | number \| null |  | Longitude from -180 through 180. |
| `google_place_id` | string \| null |  | Google place identifier when one is configured. |
| `language` | string |  | Lowercase ISO 639-1 content language. |
| `action_language` | string \| null |  | Lowercase ISO 639-1 language used for generated actions. |
| `focus` | global \| country \| region \| city |  | Geographic targeting focus. |
| `local_mode` | boolean |  | Whether local geographic context is emphasized. |
| `local_context` | object |  | Resolved geographic context used by project processing. |
| `location_defaults` | object |  | Default language, action language, website, and parent-brand settings available to locations in this project. |
| `folder` | string \| null |  | Optional workspace organization folder. |
| `tags` | Tag\[\] |  | Up to three workspace tags assigned to the project. |
| `logo_url` | string \| null |  | Absolute project logo URL when configured. |
| `created_at` | datetime |  | Project creation time in ISO 8601 format. |
| `updated_at` | datetime |  | Most recent project 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. |

#### Tag

| Name | Type | Details | Description |
| --- | --- | --- | --- |
| `id` | uuid |  | Tag identifier. |
| `name` | string |  | Tag display name. |
| `color` | string |  | Tag display color. |

#### Request and response

```curl
curl --request POST \
  --url 'https://api.signal.ceyo.ai/v1/projects' \
  --header 'Authorization: Bearer ceyo_platform_...' \
  --header 'Content-Type: application/json' \
  --data '{
  "name": "Acme Europe",
  "external_id": "partner-project-acme",
  "website": "https://acme.example",
  "description": "European visibility program for Acme.",
  "package_id": "92404fd7-f096-49e9-9ab0-5ed73517d9db",
  "country": "Netherlands",
  "country_code": "NL",
  "language": "en",
  "focus": "country",
  "start": false
}'
```

```json
HTTP/1.1 201 Created

{
  "project": {
    "id": "e6c96c98-d777-40e0-94ec-48931f57782f",
    "workspace_id": "b7dd886f-b144-4ab4-907b-8efde9df881a",
    "external_id": "partner-project-acme",
    "name": "Acme Europe",
    "description": "European visibility program for Acme.",
    "website": "https://acme.example/",
    "brand_aliases": [
      "acme",
      "acme europe"
    ],
    "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"
      }
    ],
    "project_mode": "standard",
    "status": "active",
    "package": {
      "id": "92404fd7-f096-49e9-9ab0-5ed73517d9db",
      "name": "Growth Weekly"
    },
    "address": "1 Market Street",
    "city": "Amsterdam",
    "state": "North Holland",
    "postal_code": "1012 JS",
    "country": "Netherlands",
    "country_code": "NL",
    "latitude": 52.3728,
    "longitude": 4.8936,
    "google_place_id": null,
    "language": "en",
    "action_language": "en",
    "focus": "country",
    "local_mode": false,
    "local_context": {},
    "location_defaults": {
      "language": "en",
      "action_language": "en",
      "website": "https://acme.example/",
      "include_parent_brand": true
    },
    "folder": "Europe",
    "tags": [
      {
        "id": "30761d13-bc7e-45c8-8968-2147a37d6e54",
        "name": "Retail",
        "color": "#2563EB"
      }
    ],
    "logo_url": null,
    "created_at": "2026-07-31T08:00:00Z",
    "updated_at": "2026-07-31T08:00:00Z"
  },
  "onboarding_operation": null
}
```

#### 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. |
| `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. |

### Bulk create projects

`POST /projects/bulk`

Accepts up to 100 project create records and provisions them asynchronously with controlled workspace concurrency.

#### Request body

| Name | Type | Details | Description |
| --- | --- | --- | --- |
| `projects` | ProjectCreate\[\] |  | Between 1 and 100 records using the same fields and validation as Create project. |

> **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`.

> **Onboarding**
>
> Each record controls onboarding independently with `start`. `start: true` is rejected for a `locations_only` container; onboard its locations instead.

#### 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/bulk' \
  --header 'Authorization: Bearer ceyo_platform_...' \
  --header 'Idempotency-Key: provision-2026-08-04-001' \
  --header 'Content-Type: application/json' \
  --data '{
  "projects": [
    {
      "name": "Acme Europe",
      "external_id": "partner-project-acme",
      "website": "https://acme.example",
      "description": "European visibility program for Acme.",
      "package_id": "92404fd7-f096-49e9-9ab0-5ed73517d9db",
      "country": "Netherlands",
      "country_code": "NL",
      "language": "en",
      "focus": "country",
      "start": false
    },
    {
      "name": "Acme locations",
      "external_id": "partner-project-acme-locations",
      "project_mode": "locations_only",
      "start": false
    }
  ]
}'
```

```json
HTTP/1.1 202 Accepted

{
  "bulk_operation": {
    "id": "f9bc15cc-e9c9-4e93-a93e-b713c92c7315",
    "type": "projects",
    "status": "pending",
    "parent_project_id": null,
    "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. |
| `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 project

`GET /projects/{project_id}`

Returns one project by its Ceyo UUID.

#### Path parameters

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

#### Response envelope

`project`:**Project**

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

#### Project

| Name | Type | Details | Description |
| --- | --- | --- | --- |
| `id` | uuid |  | Project identifier. |
| `workspace_id` | uuid |  | Identifier of the workspace that owns the project. |
| `external_id` | string \| null |  | Case-sensitive identifier supplied by the partner. |
| `name` | string |  | Project display name. |
| `description` | string \| null |  | Project description. |
| `website` | string \| null |  | Normalized HTTP or HTTPS website URL. |
| `brand_aliases` | string\[\] |  | Normalized alternative brand names. |
| `competitors` | Competitor\[\] |  | Active tracked competitors. Compatible tracked projection of the dedicated Competitors contract; suggested and dismissed records are excluded. |
| `project_mode` | standard \| locations\_only |  | Standard projects have their own tracking package. locations\_only projects act as containers for independently packaged locations. |
| `status` | active \| inactive \| archived |  | Current project lifecycle status. |
| `package` | PackageReference \| null |  | Assigned project package. Null when project\_mode is locations\_only. |
| `address` | string \| null |  | Normalized street address. |
| `city` | string \| null |  | Normalized city. |
| `state` | string \| null |  | Normalized region or state. |
| `postal_code` | string \| null |  | Normalized postal code. |
| `country` | string |  | Normalized country name. |
| `country_code` | string |  | Uppercase ISO 3166-1 alpha-2 country code. |
| `latitude` | number \| null |  | Latitude from -90 through 90. |
| `longitude` | number \| null |  | Longitude from -180 through 180. |
| `google_place_id` | string \| null |  | Google place identifier when one is configured. |
| `language` | string |  | Lowercase ISO 639-1 content language. |
| `action_language` | string \| null |  | Lowercase ISO 639-1 language used for generated actions. |
| `focus` | global \| country \| region \| city |  | Geographic targeting focus. |
| `local_mode` | boolean |  | Whether local geographic context is emphasized. |
| `local_context` | object |  | Resolved geographic context used by project processing. |
| `location_defaults` | object |  | Default language, action language, website, and parent-brand settings available to locations in this project. |
| `folder` | string \| null |  | Optional workspace organization folder. |
| `tags` | Tag\[\] |  | Up to three workspace tags assigned to the project. |
| `logo_url` | string \| null |  | Absolute project logo URL when configured. |
| `created_at` | datetime |  | Project creation time in ISO 8601 format. |
| `updated_at` | datetime |  | Most recent project 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. |

#### Tag

| Name | Type | Details | Description |
| --- | --- | --- | --- |
| `id` | uuid |  | Tag identifier. |
| `name` | string |  | Tag display name. |
| `color` | string |  | Tag display color. |

#### Request and response

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

```json
{
  "project": {
    "id": "e6c96c98-d777-40e0-94ec-48931f57782f",
    "workspace_id": "b7dd886f-b144-4ab4-907b-8efde9df881a",
    "external_id": "partner-project-acme",
    "name": "Acme Europe",
    "description": "European visibility program for Acme.",
    "website": "https://acme.example/",
    "brand_aliases": [
      "acme",
      "acme europe"
    ],
    "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"
      }
    ],
    "project_mode": "standard",
    "status": "active",
    "package": {
      "id": "92404fd7-f096-49e9-9ab0-5ed73517d9db",
      "name": "Growth Weekly"
    },
    "address": "1 Market Street",
    "city": "Amsterdam",
    "state": "North Holland",
    "postal_code": "1012 JS",
    "country": "Netherlands",
    "country_code": "NL",
    "latitude": 52.3728,
    "longitude": 4.8936,
    "google_place_id": null,
    "language": "en",
    "action_language": "en",
    "focus": "country",
    "local_mode": false,
    "local_context": {},
    "location_defaults": {
      "language": "en",
      "action_language": "en",
      "website": "https://acme.example/",
      "include_parent_brand": true
    },
    "folder": "Europe",
    "tags": [
      {
        "id": "30761d13-bc7e-45c8-8968-2147a37d6e54",
        "name": "Retail",
        "color": "#2563EB"
      }
    ],
    "logo_url": null,
    "created_at": "2026-07-31T08:00:00Z",
    "updated_at": "2026-07-31T08: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. |
| `429` | rate\_limit\_exceeded |  | Too many requests were made. |

### Get project overview

`GET /projects/{project_id}/overview`

Returns the project, a 30-day visibility summary, and location map markers.

#### Path parameters

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

#### Response envelope

`project`:**Project**`visibility_summary`:**VisibilitySummary**`map`:**LocationMap**

| Name | Type | Details | Description |
| --- | --- | --- | --- |
| `project` | Project |  | Current project resource. |
| `visibility_summary` | VisibilitySummary |  | Latest completed 30-day visibility summary across active locations. |
| `map` | LocationMap |  | Active location markers with valid coordinates. |

#### 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. |

#### 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. |

> **Aggregation and empty data**
>
> Visibility uses the latest completed 30-day window across active locations. A project without eligible data returns null rates and positions.

#### Request and response

```curl
curl --request GET \
  --url 'https://api.signal.ceyo.ai/v1/projects/e6c96c98-d777-40e0-94ec-48931f57782f/overview' \
  --header 'Authorization: Bearer ceyo_platform_...'
```

```json
{
  "project": {
    "id": "e6c96c98-d777-40e0-94ec-48931f57782f",
    "workspace_id": "b7dd886f-b144-4ab4-907b-8efde9df881a",
    "external_id": "partner-project-acme",
    "name": "Acme Europe",
    "description": "European visibility program for Acme.",
    "website": "https://acme.example/",
    "brand_aliases": [
      "acme",
      "acme europe"
    ],
    "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"
      }
    ],
    "project_mode": "standard",
    "status": "active",
    "package": {
      "id": "92404fd7-f096-49e9-9ab0-5ed73517d9db",
      "name": "Growth Weekly"
    },
    "address": "1 Market Street",
    "city": "Amsterdam",
    "state": "North Holland",
    "postal_code": "1012 JS",
    "country": "Netherlands",
    "country_code": "NL",
    "latitude": 52.3728,
    "longitude": 4.8936,
    "google_place_id": null,
    "language": "en",
    "action_language": "en",
    "focus": "country",
    "local_mode": false,
    "local_context": {},
    "location_defaults": {
      "language": "en",
      "action_language": "en",
      "website": "https://acme.example/",
      "include_parent_brand": true
    },
    "folder": "Europe",
    "tags": [
      {
        "id": "30761d13-bc7e-45c8-8968-2147a37d6e54",
        "name": "Retail",
        "color": "#2563EB"
      }
    ],
    "logo_url": null,
    "created_at": "2026-07-31T08:00:00Z",
    "updated_at": "2026-07-31T08:00:00Z"
  },
  "visibility_summary": {
    "visibility_rate": 68.4,
    "avg_position": 2.7
  },
  "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. |
| `429` | rate\_limit\_exceeded |  | Too many requests were made. |

### Find project by external ID

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

Returns the project whose external\_id exactly matches the URL-encoded path value.

#### Path parameters

| Name | Type | Details | Description |
| --- | --- | --- | --- |
| `external_id` | string | Required | URL-encoded, case-sensitive external ID previously assigned to the resource. |

> **Exact lookup**
>
> Project external IDs are unique for the API key's workspace. Matching is case-sensitive. An empty or malformed path value returns `400`; an unknown value returns `404`.

#### Response envelope

`project`:**Project**

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

#### Project

| Name | Type | Details | Description |
| --- | --- | --- | --- |
| `id` | uuid |  | Project identifier. |
| `workspace_id` | uuid |  | Identifier of the workspace that owns the project. |
| `external_id` | string \| null |  | Case-sensitive identifier supplied by the partner. |
| `name` | string |  | Project display name. |
| `description` | string \| null |  | Project description. |
| `website` | string \| null |  | Normalized HTTP or HTTPS website URL. |
| `brand_aliases` | string\[\] |  | Normalized alternative brand names. |
| `competitors` | Competitor\[\] |  | Active tracked competitors. Compatible tracked projection of the dedicated Competitors contract; suggested and dismissed records are excluded. |
| `project_mode` | standard \| locations\_only |  | Standard projects have their own tracking package. locations\_only projects act as containers for independently packaged locations. |
| `status` | active \| inactive \| archived |  | Current project lifecycle status. |
| `package` | PackageReference \| null |  | Assigned project package. Null when project\_mode is locations\_only. |
| `address` | string \| null |  | Normalized street address. |
| `city` | string \| null |  | Normalized city. |
| `state` | string \| null |  | Normalized region or state. |
| `postal_code` | string \| null |  | Normalized postal code. |
| `country` | string |  | Normalized country name. |
| `country_code` | string |  | Uppercase ISO 3166-1 alpha-2 country code. |
| `latitude` | number \| null |  | Latitude from -90 through 90. |
| `longitude` | number \| null |  | Longitude from -180 through 180. |
| `google_place_id` | string \| null |  | Google place identifier when one is configured. |
| `language` | string |  | Lowercase ISO 639-1 content language. |
| `action_language` | string \| null |  | Lowercase ISO 639-1 language used for generated actions. |
| `focus` | global \| country \| region \| city |  | Geographic targeting focus. |
| `local_mode` | boolean |  | Whether local geographic context is emphasized. |
| `local_context` | object |  | Resolved geographic context used by project processing. |
| `location_defaults` | object |  | Default language, action language, website, and parent-brand settings available to locations in this project. |
| `folder` | string \| null |  | Optional workspace organization folder. |
| `tags` | Tag\[\] |  | Up to three workspace tags assigned to the project. |
| `logo_url` | string \| null |  | Absolute project logo URL when configured. |
| `created_at` | datetime |  | Project creation time in ISO 8601 format. |
| `updated_at` | datetime |  | Most recent project 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. |

#### Tag

| Name | Type | Details | Description |
| --- | --- | --- | --- |
| `id` | uuid |  | Tag identifier. |
| `name` | string |  | Tag display name. |
| `color` | string |  | Tag display color. |

#### Request and response

```curl
curl --request GET \
  --url 'https://api.signal.ceyo.ai/v1/projects/by-external-id/partner-project-acme' \
  --header 'Authorization: Bearer ceyo_platform_...'
```

```json
{
  "project": {
    "id": "e6c96c98-d777-40e0-94ec-48931f57782f",
    "workspace_id": "b7dd886f-b144-4ab4-907b-8efde9df881a",
    "external_id": "partner-project-acme",
    "name": "Acme Europe",
    "description": "European visibility program for Acme.",
    "website": "https://acme.example/",
    "brand_aliases": [
      "acme",
      "acme europe"
    ],
    "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"
      }
    ],
    "project_mode": "standard",
    "status": "active",
    "package": {
      "id": "92404fd7-f096-49e9-9ab0-5ed73517d9db",
      "name": "Growth Weekly"
    },
    "address": "1 Market Street",
    "city": "Amsterdam",
    "state": "North Holland",
    "postal_code": "1012 JS",
    "country": "Netherlands",
    "country_code": "NL",
    "latitude": 52.3728,
    "longitude": 4.8936,
    "google_place_id": null,
    "language": "en",
    "action_language": "en",
    "focus": "country",
    "local_mode": false,
    "local_context": {},
    "location_defaults": {
      "language": "en",
      "action_language": "en",
      "website": "https://acme.example/",
      "include_parent_brand": true
    },
    "folder": "Europe",
    "tags": [
      {
        "id": "30761d13-bc7e-45c8-8968-2147a37d6e54",
        "name": "Retail",
        "color": "#2563EB"
      }
    ],
    "logo_url": null,
    "created_at": "2026-07-31T08:00:00Z",
    "updated_at": "2026-07-31T08: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. |
| `429` | rate\_limit\_exceeded |  | Too many requests were made. |

### Update project

`PATCH /projects/{project_id}`

Updates only supplied project 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. |

#### JSON request body

| Name | Type | Details | Description |
| --- | --- | --- | --- |
| `name` | string |  | New project name. Maximum: 200 characters. |
| `external_id` | string \| null |  | partner identifier, unique across projects. Maximum: 200 characters. |
| `description` | string \| null |  | description. Maximum: 5,000 characters. |
| `brand_aliases` | string\[\] |  | Up to 10 alternative names, each at most 200 characters. Values are trimmed, lowercased, and deduplicated. |
| `country_code` | string |  | Uppercase ISO 3166-1 alpha-2 country code. |
| `country` | string |  | Country name. |
| `city` | string \| null |  | City; required when focus is city. Maximum: 200 characters. |
| `state` | string \| null |  | Region or state. Maximum: 200 characters. |
| `address` | string \| null |  | Street address. Maximum: 500 characters. |
| `postal_code` | string \| null |  | Postal code. Maximum: 200 characters. |
| `latitude` | number \| null |  | Latitude from -90 through 90. |
| `longitude` | number \| null |  | Longitude from -180 through 180. |
| `google_place_id` | string \| null |  | Google place identifier. Maximum: 500 characters. |
| `language` | ISO 639-1 string |  | Content language. |
| `action_language` | ISO 639-1 string \| null |  | language for generated actions. |
| `focus` | global \| country \| region \| city |  | Geographic targeting focus. |
| `local_mode` | boolean |  | Enable local geographic context. |
| `folder` | string \| null |  | organization folder. Maximum: 30 characters. |
| `tag_ids` | uuid\[\] |  | Up to three tag identifiers from this workspace. |
| `tag_names` | string\[\] |  | Up to three tag names. Names are trimmed and matched case-insensitively; missing names are created. Each name is 1–25 characters and the workspace may contain at most 50 tags. Takes precedence over tag\_ids. |
| `location_defaults` | object |  | Replacement defaults for location language, action\_language, website, and include\_parent\_brand. These affect effective location behavior without changing explicit location values. |
| `website` | string \| null |  | Replacement normalized HTTP or HTTPS URL, maximum 2,048 characters. Standard projects cannot clear this field; locations\_only projects may send null. |

#### Multipart request body

| Name | Type | Details | Description |
| --- | --- | --- | --- |
| `name` | string |  | New project name. Maximum: 200 characters. |
| `external_id` | string \| null |  | partner identifier, unique across projects. Maximum: 200 characters. |
| `description` | string \| null |  | description. Maximum: 5,000 characters. |
| `brand_aliases` | string\[\] |  | Up to 10 alternative names, each at most 200 characters. Values are trimmed, lowercased, and deduplicated. |
| `country_code` | string |  | Uppercase ISO 3166-1 alpha-2 country code. |
| `country` | string |  | Country name. |
| `city` | string \| null |  | City; required when focus is city. Maximum: 200 characters. |
| `state` | string \| null |  | Region or state. Maximum: 200 characters. |
| `address` | string \| null |  | Street address. Maximum: 500 characters. |
| `postal_code` | string \| null |  | Postal code. Maximum: 200 characters. |
| `latitude` | number \| null |  | Latitude from -90 through 90. |
| `longitude` | number \| null |  | Longitude from -180 through 180. |
| `google_place_id` | string \| null |  | Google place identifier. Maximum: 500 characters. |
| `language` | ISO 639-1 string |  | Content language. |
| `action_language` | ISO 639-1 string \| null |  | language for generated actions. |
| `focus` | global \| country \| region \| city |  | Geographic targeting focus. |
| `local_mode` | boolean |  | Enable local geographic context. |
| `folder` | string \| null |  | organization folder. Maximum: 30 characters. |
| `tag_ids` | uuid\[\] |  | Up to three tag identifiers from this workspace. |
| `tag_names` | string\[\] |  | Up to three tag names. Names are trimmed and matched case-insensitively; missing names are created. Each name is 1–25 characters and the workspace may contain at most 50 tags. Takes precedence over tag\_ids. |
| `location_defaults` | object |  | Replacement defaults for location language, action\_language, website, and include\_parent\_brand. These affect effective location behavior without changing explicit location values. |
| `website` | string \| null |  | Replacement normalized HTTP or HTTPS URL, maximum 2,048 characters. Standard projects cannot clear this field; locations\_only projects may send null. |
| `logo` | binary |  | JPEG, PNG, or WebP image, maximum 5 MB. Replaces the current logo after validation. |
| `remove_logo` | boolean |  | Set true to remove the current logo. If logo is also supplied, the uploaded logo takes precedence and remove\_logo is ignored. |

> **Replacement fields and precedence**
>
> `project_mode` and package assignment are immutable. Set nullable JSON fields to `null` to clear them. Empty alias and tag arrays clear their assignments. If both `tag_names` and `tag_ids` are supplied, `tag_names` wins. Missing tag names are created atomically; if the three-assignment or 50-tag workspace limit would be exceeded, no tags are changed.

> **Logo upload and removal**
>
> Use `application/json` when no file is involved. Use `multipart/form-data` to upload `logo` or set `remove_logo=true`; other fields retain the same validation and replacement semantics. Repeated `tag_ids[]`, `tag_names[]`, and `brand_aliases[]` parts represent arrays. A supplied logo takes precedence over `remove_logo`. Successful removal returns `logo_url: null`.

#### Response envelope

`project`:**Project**

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

#### Project

| Name | Type | Details | Description |
| --- | --- | --- | --- |
| `id` | uuid |  | Project identifier. |
| `workspace_id` | uuid |  | Identifier of the workspace that owns the project. |
| `external_id` | string \| null |  | Case-sensitive identifier supplied by the partner. |
| `name` | string |  | Project display name. |
| `description` | string \| null |  | Project description. |
| `website` | string \| null |  | Normalized HTTP or HTTPS website URL. |
| `brand_aliases` | string\[\] |  | Normalized alternative brand names. |
| `competitors` | Competitor\[\] |  | Active tracked competitors. Compatible tracked projection of the dedicated Competitors contract; suggested and dismissed records are excluded. |
| `project_mode` | standard \| locations\_only |  | Standard projects have their own tracking package. locations\_only projects act as containers for independently packaged locations. |
| `status` | active \| inactive \| archived |  | Current project lifecycle status. |
| `package` | PackageReference \| null |  | Assigned project package. Null when project\_mode is locations\_only. |
| `address` | string \| null |  | Normalized street address. |
| `city` | string \| null |  | Normalized city. |
| `state` | string \| null |  | Normalized region or state. |
| `postal_code` | string \| null |  | Normalized postal code. |
| `country` | string |  | Normalized country name. |
| `country_code` | string |  | Uppercase ISO 3166-1 alpha-2 country code. |
| `latitude` | number \| null |  | Latitude from -90 through 90. |
| `longitude` | number \| null |  | Longitude from -180 through 180. |
| `google_place_id` | string \| null |  | Google place identifier when one is configured. |
| `language` | string |  | Lowercase ISO 639-1 content language. |
| `action_language` | string \| null |  | Lowercase ISO 639-1 language used for generated actions. |
| `focus` | global \| country \| region \| city |  | Geographic targeting focus. |
| `local_mode` | boolean |  | Whether local geographic context is emphasized. |
| `local_context` | object |  | Resolved geographic context used by project processing. |
| `location_defaults` | object |  | Default language, action language, website, and parent-brand settings available to locations in this project. |
| `folder` | string \| null |  | Optional workspace organization folder. |
| `tags` | Tag\[\] |  | Up to three workspace tags assigned to the project. |
| `logo_url` | string \| null |  | Absolute project logo URL when configured. |
| `created_at` | datetime |  | Project creation time in ISO 8601 format. |
| `updated_at` | datetime |  | Most recent project 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. |

#### Tag

| Name | Type | Details | Description |
| --- | --- | --- | --- |
| `id` | uuid |  | Tag identifier. |
| `name` | string |  | Tag display name. |
| `color` | string |  | Tag display color. |

#### Request and response

```curl
curl --request PATCH \
  --url 'https://api.signal.ceyo.ai/v1/projects/e6c96c98-d777-40e0-94ec-48931f57782f' \
  --header 'Authorization: Bearer ceyo_platform_...' \
  --header 'Content-Type: application/json' \
  --data '{
  "name": "Acme Europe & UK",
  "external_id": "partner-project-acme-eu",
  "brand_aliases": [
    "acme",
    "acme europe",
    "acme uk"
  ],
  "tag_names": [
    "Retail",
    "Europe"
  ]
}'
```

```json
{
  "project": {
    "id": "e6c96c98-d777-40e0-94ec-48931f57782f",
    "workspace_id": "b7dd886f-b144-4ab4-907b-8efde9df881a",
    "external_id": "partner-project-acme-eu",
    "name": "Acme Europe & UK",
    "description": "European visibility program for Acme.",
    "website": "https://acme.example/",
    "brand_aliases": [
      "acme",
      "acme europe",
      "acme uk"
    ],
    "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"
      }
    ],
    "project_mode": "standard",
    "status": "active",
    "package": {
      "id": "92404fd7-f096-49e9-9ab0-5ed73517d9db",
      "name": "Growth Weekly"
    },
    "address": "1 Market Street",
    "city": "Amsterdam",
    "state": "North Holland",
    "postal_code": "1012 JS",
    "country": "Netherlands",
    "country_code": "NL",
    "latitude": 52.3728,
    "longitude": 4.8936,
    "google_place_id": null,
    "language": "en",
    "action_language": "en",
    "focus": "country",
    "local_mode": false,
    "local_context": {},
    "location_defaults": {
      "language": "en",
      "action_language": "en",
      "website": "https://acme.example/",
      "include_parent_brand": true
    },
    "folder": "Europe",
    "tags": [
      {
        "id": "30761d13-bc7e-45c8-8968-2147a37d6e54",
        "name": "Retail",
        "color": "#2563EB"
      },
      {
        "id": "b4e8bc5c-8282-4969-9d50-d8cf7723705a",
        "name": "Europe",
        "color": "#7C3AED"
      }
    ],
    "logo_url": null,
    "created_at": "2026-07-31T08:00:00Z",
    "updated_at": "2026-07-31T12:00:00Z"
  }
}
```

#### Request and response

```curl
curl --request PATCH \
  --url 'https://api.signal.ceyo.ai/v1/projects/e6c96c98-d777-40e0-94ec-48931f57782f' \
  --header 'Authorization: Bearer ceyo_platform_...' \
  --form 'tag_names[]=Retail' \
  --form 'tag_names[]=Europe' \
  --form 'logo=@./acme-logo.webp;type=image/webp' \
  --form 'remove_logo=true'
```

```json
{
  "project": {
    "id": "e6c96c98-d777-40e0-94ec-48931f57782f",
    "workspace_id": "b7dd886f-b144-4ab4-907b-8efde9df881a",
    "external_id": "partner-project-acme",
    "name": "Acme Europe",
    "description": "European visibility program for Acme.",
    "website": "https://acme.example/",
    "brand_aliases": [
      "acme",
      "acme europe"
    ],
    "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"
      }
    ],
    "project_mode": "standard",
    "status": "active",
    "package": {
      "id": "92404fd7-f096-49e9-9ab0-5ed73517d9db",
      "name": "Growth Weekly"
    },
    "address": "1 Market Street",
    "city": "Amsterdam",
    "state": "North Holland",
    "postal_code": "1012 JS",
    "country": "Netherlands",
    "country_code": "NL",
    "latitude": 52.3728,
    "longitude": 4.8936,
    "google_place_id": null,
    "language": "en",
    "action_language": "en",
    "focus": "country",
    "local_mode": false,
    "local_context": {},
    "location_defaults": {
      "language": "en",
      "action_language": "en",
      "website": "https://acme.example/",
      "include_parent_brand": true
    },
    "folder": "Europe",
    "tags": [
      {
        "id": "30761d13-bc7e-45c8-8968-2147a37d6e54",
        "name": "Retail",
        "color": "#2563EB"
      },
      {
        "id": "b4e8bc5c-8282-4969-9d50-d8cf7723705a",
        "name": "Europe",
        "color": "#7C3AED"
      }
    ],
    "logo_url": "https://api.ceyo.ai/media/logos/opaque-logo-token",
    "created_at": "2026-07-31T08:00:00Z",
    "updated_at": "2026-07-31T12:05: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 project

`DELETE /projects/{project_id}`

Schedules asynchronous deletion of a project and all resources contained by it.

#### Path parameters

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

> **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' \
  --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. |
