# Access grants

Source: https://ceyo.ai/docs/signal/access-grants

### Access grants

Grant or revoke viewer, editor, or hosted admin access to projects and locations for an existing embedded identity.

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

> **Grant identifiers**
>
> `external_user_id` identifies an existing embedded identity and is case-sensitive. Project and location path values accept either Signal UUIDs or your configured external IDs.

> **Granted-location embed scope**
>
> Create a session with `location_scope: "granted"` to expose the identity's active direct location grants in one project. This live scope preserves each location's role; adding, changing, or revoking a direct grant updates access, while a project grant never broadens it. Only Overview and Locations are available at project navigation level.

### Grant project access

`POST /embedded-identities/{external_user_id}/projects/{project_id}/access`

Creates or updates a direct project grant for an active embedded 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. |
| `project_id` | project UUID \| project external ID | Required | Ceyo project UUID or configured partner external ID in the workspace selected by the API key. |

#### Request body

| Name | Type | Details | Description |
| --- | --- | --- | --- |
| `role` | viewer \| editor \| admin |  | Required role. viewer is read-only; editor permits supported operational changes; admin additionally manages hosted project or location settings and users. |

#### Response envelope

`project_access`:**ProjectAccess**

The requested or resulting direct project grant.

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

> **Idempotent grant**
>
> A new grant returns `201 Created`. Repeating the request with the same role returns `200 OK`, preserves `granted_at`, and makes no duplicate. Sending a different role updates the existing direct grant and also preserves `granted_at`.

> **Project scope**
>
> Project access applies to the project and all of its locations. Direct location grants may coexist with a project grant, but they do not reduce the effective project role.

#### Request and response

```curl
curl --request POST \
  --url 'https://api.signal.ceyo.ai/v1/embedded-identities/customer-user-4821/projects/e6c96c98-d777-40e0-94ec-48931f57782f/access' \
  --header 'Authorization: Bearer ceyo_platform_...' \
  --header 'Content-Type: application/json' \
  --data '{
  "role": "editor"
}'
```

```json
HTTP/1.1 201 Created

{
  "project_access": {
    "project_id": "e6c96c98-d777-40e0-94ec-48931f57782f",
    "role": "editor",
    "granted_at": "2026-07-31T09:30: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` | resource\_not\_found |  | The embedded identity, project, or location was not found in this workspace. |
| `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. |

### Revoke project access

`DELETE /embedded-identities/{external_user_id}/projects/{project_id}/access`

Removes the direct project grant for an embedded identity without deleting the identity or its direct 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. |
| `project_id` | project UUID \| project external ID | Required | Ceyo project UUID or configured partner external ID in the workspace selected by the API key. |

> **Idempotent revoke**
>
> The endpoint returns `204 No Content` whether the direct project grant existed or was already absent. The identity and project remain unchanged.

> **Remaining location access**
>
> Revoking project access does not remove direct location grants in the project. The identity can continue to reach those locations with their location roles. Revoke them separately when access to the entire project must end.

#### Request and response

```curl
curl --request DELETE \
  --url 'https://api.signal.ceyo.ai/v1/embedded-identities/customer-user-4821/projects/e6c96c98-d777-40e0-94ec-48931f57782f/access' \
  --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` | resource\_not\_found |  | The embedded identity, project, or location was not found in this workspace. |
| `429` | rate\_limit\_exceeded |  | Too many requests were made. |

### Grant location access

`POST /embedded-identities/{external_user_id}/projects/{project_id}/locations/{location_id}/access`

Creates or updates a direct grant to one location for an active embedded 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. |
| `project_id` | project UUID \| project external ID | Required | Ceyo project UUID or configured partner external ID in the workspace selected by the API key. |
| `location_id` | location UUID \| location external ID | Required | Ceyo location UUID or configured partner external ID belonging to the selected project. |

#### Request body

| Name | Type | Details | Description |
| --- | --- | --- | --- |
| `role` | viewer \| editor \| admin |  | Required role. viewer is read-only; editor permits supported operational changes; admin additionally manages hosted project or location settings and users. |

#### Response envelope

`location_access`:**LocationAccess**

The requested or resulting direct location grant.

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

> **Idempotent grant**
>
> A new grant returns `201 Created`. Repeating the request with the same role returns `200 OK`, preserves `granted_at`, and makes no duplicate. Sending a different role updates the existing direct grant and also preserves `granted_at`.

> **Location scope**
>
> The grant applies only to the selected location. The location must belong to the project in the path. A direct project grant may provide broader or stronger effective access without changing this location grant.

#### Request and response

```curl
curl --request POST \
  --url 'https://api.signal.ceyo.ai/v1/embedded-identities/customer-user-4821/projects/e6c96c98-d777-40e0-94ec-48931f57782f/locations/a1308d14-149c-4dd7-a4c5-295ac9090f58/access' \
  --header 'Authorization: Bearer ceyo_platform_...' \
  --header 'Content-Type: application/json' \
  --data '{
  "role": "viewer"
}'
```

```json
HTTP/1.1 201 Created

{
  "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"
  }
}
```

#### 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` | resource\_not\_found |  | The embedded identity, project, or location was not found in this workspace. |
| `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. |

### Revoke location access

`DELETE /embedded-identities/{external_user_id}/projects/{project_id}/locations/{location_id}/access`

Removes the direct location grant for an embedded identity without changing project access or grants to other locations.

#### 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. |
| `project_id` | project UUID \| project external ID | Required | Ceyo project UUID or configured partner external ID in the workspace selected by the API key. |
| `location_id` | location UUID \| location external ID | Required | Ceyo location UUID or configured partner external ID belonging to the selected project. |

> **Idempotent revoke**
>
> The endpoint returns `204 No Content` whether the direct location grant existed or was already absent. The identity, project, and location remain unchanged.

> **Effective access**
>
> Revoking a location grant does not remove a project grant. If the identity still has project access, it can continue to reach this location through that broader scope.

#### Request and response

```curl
curl --request DELETE \
  --url 'https://api.signal.ceyo.ai/v1/embedded-identities/customer-user-4821/projects/e6c96c98-d777-40e0-94ec-48931f57782f/locations/a1308d14-149c-4dd7-a4c5-295ac9090f58/access' \
  --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` | resource\_not\_found |  | The embedded identity, project, or location was not found in this workspace. |
| `429` | rate\_limit\_exceeded |  | Too many requests were made. |
