# Embedded identities

Source: https://ceyo.ai/docs/signal/embedded-identities

### Embedded identities

Provision partner-managed identities for embedded experiences or redirect login, then assign project or location access by your stable `external_user_id`.

> **Role behavior by access method**
>
> `viewer` and `editor` apply to both embedded and hosted access. `admin` enables project or location settings and partner-managed user administration after redirect login. Embed sessions never receive admin privileges; an admin grant is capped to `editor` in the embeddable.

> **Authentication and workspace scope**
>
> Use a workspace-scoped API key with `identities:manage`. Send it as `Authorization: Bearer ceyo_platform_...` on every request. The key selects the workspace, so paths do not include a workspace identifier. Keep the key on your server.

> **Stable external IDs**
>
> Choose an immutable ID from your own system, not an email address. Matching is case-sensitive. URL-encode it in every resource path; for example, `customer/user 42` becomes `customer%2Fuser%2042`.

### List embedded identities

`GET /embedded-identities`

Returns embedded identities in the workspace selected by the API key, including their direct project and location grants.

#### Query parameters

| Name | Type | Details | Description |
| --- | --- | --- | --- |
| `q` | string | Optional | Case-insensitive search across external\_user\_id, email, and name. |
| `status` | active \| disabled | Optional | Return identities in one lifecycle status. |
| `project_id` | project UUID \| project external ID | Optional | Return identities with direct access to this project or one of its locations. |
| `location_id` | location UUID \| location external ID | Optional | Return identities with direct access to this location. When location\_id is an external ID, project\_id is required. |
| `role` | viewer \| editor \| admin | Optional | Return identities with at least one matching direct project or location grant. |
| `page` | integer | Optional; Default: 1 | The 1-based page number. |
| `per_page` | integer | Optional; Default: 25 | Number of identities per page. Minimum: 1; maximum: 100. |

#### Response envelope

`embedded_identities`:**EmbeddedIdentity\[\]**`pagination`:**Pagination**

| Name | Type | Details | Description |
| --- | --- | --- | --- |
| `embedded_identities` | EmbeddedIdentity\[\] |  | Matching identities ordered by creation time newest first. |
| `pagination` | Pagination |  | Pagination metadata. |

#### EmbeddedIdentity

| Name | Type | Details | Description |
| --- | --- | --- | --- |
| `external_user_id` | string |  | Stable, case-sensitive identifier supplied by your application. |
| `email` | string \| null |  | Optional contact or display email. It is profile data and is not used to sign in. |
| `name` | string \| null |  | Optional display name. |
| `avatar_url` | string \| null |  | Optional absolute HTTPS URL for a display avatar. |
| `metadata` | object |  | Partner-defined JSON object. Values are returned as supplied and must not contain credentials or secrets. |
| `status` | active \| disabled |  | Active identities can receive access and use embed sessions or redirect login. Disabling an identity revokes active access sessions and pending login links. |
| `project_access` | ProjectAccess\[\] |  | Direct project grants for the identity. |
| `location_access` | LocationAccess\[\] |  | Direct location grants for the identity. |
| `created_at` | datetime |  | Identity creation time in ISO 8601 format. |
| `updated_at` | datetime |  | Time the identity profile or status was most recently updated, in ISO 8601 format. |

#### ProjectAccess

| Name | Type | Details | Description |
| --- | --- | --- | --- |
| `project_id` | uuid |  | Ceyo UUID of the granted project. |
| `role` | viewer \| editor \| admin |  | Role granted across the project and all of its locations. |
| `granted_at` | datetime |  | Time the direct project grant was first created, in ISO 8601 format. |

#### LocationAccess

| Name | Type | Details | Description |
| --- | --- | --- | --- |
| `project_id` | uuid |  | Ceyo UUID of the project containing the location. |
| `location_id` | uuid |  | Ceyo UUID of the granted location. |
| `role` | viewer \| editor \| admin |  | Role granted for this location. |
| `granted_at` | datetime |  | Time the direct location grant was first created, in ISO 8601 format. |

#### Pagination

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

> **Access filters**
>
> `project_id` matches direct project grants and direct grants for locations in that project. `location_id` matches direct location grants only. Combine filters to narrow the same result set; an identity must satisfy every supplied filter.

#### Request and response

```curl
curl --request GET \
  --url 'https://api.signal.ceyo.ai/v1/embedded-identities?q=avery&status=active&project_id=e6c96c98-d777-40e0-94ec-48931f57782f&role=viewer&page=1&per_page=25' \
  --header 'Authorization: Bearer ceyo_platform_...'
```

```json
{
  "embedded_identities": [
    {
      "external_user_id": "customer-user-4821",
      "email": "avery.quinn@example.com",
      "name": "Avery Quinn",
      "avatar_url": "https://cdn.example.com/avatars/customer-user-4821.png",
      "metadata": {
        "account_tier": "enterprise",
        "region": "emea"
      },
      "status": "active",
      "project_access": [
        {
          "project_id": "e6c96c98-d777-40e0-94ec-48931f57782f",
          "role": "editor",
          "granted_at": "2026-07-31T09:30:00Z"
        }
      ],
      "location_access": [
        {
          "project_id": "e6c96c98-d777-40e0-94ec-48931f57782f",
          "location_id": "a1308d14-149c-4dd7-a4c5-295ac9090f58",
          "role": "viewer",
          "granted_at": "2026-07-31T09:35:00Z"
        }
      ],
      "created_at": "2026-07-31T09:20:00Z",
      "updated_at": "2026-07-31T09:35: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": "invalid_api_key",
    "message": "The Bearer API key is invalid.",
    "details": null,
    "request_id": "req_01K1F8M7QX4R2V9N6Y3Z0A5BCT"
  }
}
```

#### 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, the role is unsupported, or the location does not belong to the selected project. |
| `429` | rate\_limit\_exceeded |  | Too many requests were made. |

### Upsert embedded identity

`POST /embedded-identities/{external_user_id}`

Creates an embedded identity for external\_user\_id or updates the supplied profile fields on the existing identity.

#### Path parameters

| Name | Type | Details | Description |
| --- | --- | --- | --- |
| `external_user_id` | string | Required | Your stable, case-sensitive identifier for the embedded identity. URL-encode the value before placing it in the path. |

#### Request body

| Name | Type | Details | Description |
| --- | --- | --- | --- |
| `email` | string \| null |  | Optional valid email address. Send null to clear it. The address is profile data only. |
| `name` | string \| null |  | Optional display name, up to 200 characters. Send null to clear it. |
| `avatar_url` | string \| null |  | Optional absolute HTTPS avatar URL, up to 2,048 characters. Send null to clear it. |
| `metadata` | object |  | Optional partner-defined JSON object. When supplied, it replaces the complete metadata object; send {} to clear it. |
| `status` | active \| disabled |  | Optional lifecycle status. New identities default to active. |

> **Idempotent upsert**
>
> Repeating the same request produces the same identity state without creating a duplicate. New identities return `201 Created`; existing identities return `200 OK`. Omitted fields stay unchanged on an existing identity and use their documented defaults on creation. The path value cannot be changed.

> **Status changes**
>
> Setting `status` to `disabled` preserves project and location grants while preventing new embedded sessions and revoking active embedded sessions. Set it back to `active` before minting another session.

#### Response envelope

`embedded_identity`:**EmbeddedIdentity**

The requested or resulting embedded identity.

#### EmbeddedIdentity

| Name | Type | Details | Description |
| --- | --- | --- | --- |
| `external_user_id` | string |  | Stable, case-sensitive identifier supplied by your application. |
| `email` | string \| null |  | Optional contact or display email. It is profile data and is not used to sign in. |
| `name` | string \| null |  | Optional display name. |
| `avatar_url` | string \| null |  | Optional absolute HTTPS URL for a display avatar. |
| `metadata` | object |  | Partner-defined JSON object. Values are returned as supplied and must not contain credentials or secrets. |
| `status` | active \| disabled |  | Active identities can receive access and use embed sessions or redirect login. Disabling an identity revokes active access sessions and pending login links. |
| `project_access` | ProjectAccess\[\] |  | Direct project grants for the identity. |
| `location_access` | LocationAccess\[\] |  | Direct location grants for the identity. |
| `created_at` | datetime |  | Identity creation time in ISO 8601 format. |
| `updated_at` | datetime |  | Time the identity profile or status was most recently updated, in ISO 8601 format. |

#### ProjectAccess

| Name | Type | Details | Description |
| --- | --- | --- | --- |
| `project_id` | uuid |  | Ceyo UUID of the granted project. |
| `role` | viewer \| editor \| admin |  | Role granted across the project and all of its locations. |
| `granted_at` | datetime |  | Time the direct project grant was first created, in ISO 8601 format. |

#### LocationAccess

| Name | Type | Details | Description |
| --- | --- | --- | --- |
| `project_id` | uuid |  | Ceyo UUID of the project containing the location. |
| `location_id` | uuid |  | Ceyo UUID of the granted location. |
| `role` | viewer \| editor \| admin |  | Role granted for this location. |
| `granted_at` | datetime |  | Time the direct location grant was first created, in ISO 8601 format. |

#### Request and response

```curl
curl --request POST \
  --url 'https://api.signal.ceyo.ai/v1/embedded-identities/customer-user-4821' \
  --header 'Authorization: Bearer ceyo_platform_...' \
  --header 'Content-Type: application/json' \
  --data '{
  "email": "avery.quinn@example.com",
  "name": "Avery Quinn",
  "avatar_url": "https://cdn.example.com/avatars/customer-user-4821.png",
  "metadata": {
    "account_tier": "enterprise",
    "region": "emea"
  },
  "status": "active"
}'
```

```json
HTTP/1.1 201 Created

{
  "embedded_identity": {
    "external_user_id": "customer-user-4821",
    "email": "avery.quinn@example.com",
    "name": "Avery Quinn",
    "avatar_url": "https://cdn.example.com/avatars/customer-user-4821.png",
    "metadata": {
      "account_tier": "enterprise",
      "region": "emea"
    },
    "status": "active",
    "project_access": [],
    "location_access": [],
    "created_at": "2026-07-31T09:20:00Z",
    "updated_at": "2026-07-31T09:20: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": "invalid_api_key",
    "message": "The Bearer API key is invalid.",
    "details": null,
    "request_id": "req_01K1F8M7QX4R2V9N6Y3Z0A5BCT"
  }
}
```

#### 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 requested change conflicts with the current identity or resource state. |
| `422` | validation\_failed |  | One or more fields are invalid, the role is unsupported, or the location does not belong to the selected project. |
| `429` | rate\_limit\_exceeded |  | Too many requests were made. |

### Get embedded identity

`GET /embedded-identities/{external_user_id}`

Returns one embedded identity and all of its direct project and location grants.

#### Path parameters

| Name | Type | Details | Description |
| --- | --- | --- | --- |
| `external_user_id` | string | Required | Your stable, case-sensitive identifier for the embedded identity. URL-encode the value before placing it in the path. |

#### Response envelope

`embedded_identity`:**EmbeddedIdentity**

The requested or resulting embedded identity.

#### EmbeddedIdentity

| Name | Type | Details | Description |
| --- | --- | --- | --- |
| `external_user_id` | string |  | Stable, case-sensitive identifier supplied by your application. |
| `email` | string \| null |  | Optional contact or display email. It is profile data and is not used to sign in. |
| `name` | string \| null |  | Optional display name. |
| `avatar_url` | string \| null |  | Optional absolute HTTPS URL for a display avatar. |
| `metadata` | object |  | Partner-defined JSON object. Values are returned as supplied and must not contain credentials or secrets. |
| `status` | active \| disabled |  | Active identities can receive access and use embed sessions or redirect login. Disabling an identity revokes active access sessions and pending login links. |
| `project_access` | ProjectAccess\[\] |  | Direct project grants for the identity. |
| `location_access` | LocationAccess\[\] |  | Direct location grants for the identity. |
| `created_at` | datetime |  | Identity creation time in ISO 8601 format. |
| `updated_at` | datetime |  | Time the identity profile or status was most recently updated, in ISO 8601 format. |

#### ProjectAccess

| Name | Type | Details | Description |
| --- | --- | --- | --- |
| `project_id` | uuid |  | Ceyo UUID of the granted project. |
| `role` | viewer \| editor \| admin |  | Role granted across the project and all of its locations. |
| `granted_at` | datetime |  | Time the direct project grant was first created, in ISO 8601 format. |

#### LocationAccess

| Name | Type | Details | Description |
| --- | --- | --- | --- |
| `project_id` | uuid |  | Ceyo UUID of the project containing the location. |
| `location_id` | uuid |  | Ceyo UUID of the granted location. |
| `role` | viewer \| editor \| admin |  | Role granted for this location. |
| `granted_at` | datetime |  | Time the direct location grant was first created, in ISO 8601 format. |

#### Request and response

```curl
curl --request GET \
  --url 'https://api.signal.ceyo.ai/v1/embedded-identities/customer-user-4821' \
  --header 'Authorization: Bearer ceyo_platform_...'
```

```json
{
  "embedded_identity": {
    "external_user_id": "customer-user-4821",
    "email": "avery.quinn@example.com",
    "name": "Avery Quinn",
    "avatar_url": "https://cdn.example.com/avatars/customer-user-4821.png",
    "metadata": {
      "account_tier": "enterprise",
      "region": "emea"
    },
    "status": "active",
    "project_access": [
      {
        "project_id": "e6c96c98-d777-40e0-94ec-48931f57782f",
        "role": "editor",
        "granted_at": "2026-07-31T09:30:00Z"
      }
    ],
    "location_access": [
      {
        "project_id": "e6c96c98-d777-40e0-94ec-48931f57782f",
        "location_id": "a1308d14-149c-4dd7-a4c5-295ac9090f58",
        "role": "viewer",
        "granted_at": "2026-07-31T09:35:00Z"
      }
    ],
    "created_at": "2026-07-31T09:20:00Z",
    "updated_at": "2026-07-31T09:35: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": "invalid_api_key",
    "message": "The Bearer API key is invalid.",
    "details": null,
    "request_id": "req_01K1F8M7QX4R2V9N6Y3Z0A5BCT"
  }
}
```

#### 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` | embedded\_identity\_not\_found |  | No embedded identity has the supplied external\_user\_id in this workspace. |
| `429` | rate\_limit\_exceeded |  | Too many requests were made. |

### Delete embedded identity

`DELETE /embedded-identities/{external_user_id}`

Permanently deletes an embedded identity and removes every direct project and location grant assigned to it.

#### Path parameters

| Name | Type | Details | Description |
| --- | --- | --- | --- |
| `external_user_id` | string | Required | Your stable, case-sensitive identifier for the embedded identity. URL-encode the value before placing it in the path. |

> **Deletion effects**
>
> Deletion is synchronous and returns `204 No Content`. Project and location resources are unchanged, but every access grant for this identity is removed. Active embedded sessions are revoked, and subsequent session creation or identity lookup fails until you upsert the external\_user\_id again.

> **Re-creating the identity**
>
> Upserting the same `external_user_id` after deletion creates a new identity with no project or location access. Grant each required scope again before minting a session.

#### Request and response

```curl
curl --request DELETE \
  --url 'https://api.signal.ceyo.ai/v1/embedded-identities/customer-user-4821' \
  --header 'Authorization: Bearer ceyo_platform_...'
```

```json
HTTP/1.1 204 No Content
```

#### 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": "invalid_api_key",
    "message": "The Bearer API key is invalid.",
    "details": null,
    "request_id": "req_01K1F8M7QX4R2V9N6Y3Z0A5BCT"
  }
}
```

#### 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` | embedded\_identity\_not\_found |  | No embedded identity has the supplied external\_user\_id in this workspace. |
| `409` | conflict |  | The requested change conflicts with the current identity or resource state. |
| `429` | rate\_limit\_exceeded |  | Too many requests were made. |
