> ## Documentation Index
> Fetch the complete documentation index at: https://developers.ambersearch.de/llms.txt
> Use this file to discover all available pages before exploring further.

# Permissions

> Control who can see indexed documents using the permissions block on each document.

<Info>
  **Indexing API (BETA)** --- [Overview](/indexing-api/introduction) · [Datasource & properties](/indexing-api/setup-datasource) · [Index documents](/indexing-api/index-documents) · [Permissions](/indexing-api/permissions)
</Info>

## Overview

Set `permissions` on each document when you [index it](/indexing-api/index-documents). Search only returns documents the signed-in user is allowed to see.

<Warning>
  **Access is always scoped to the datasource first.** A signed-in user sees a datasource's documents **only if they are registered as a user on that datasource** (`POST /users` --- see [Specific users only](#2-specific-users-only)). This applies to **anonymous documents too**: `allow_anonymous_access: true` (and documents indexed with no `permissions` block) are visible to every registered user of *that* datasource, but **not** to org members who have no user record on it. Register yourself before expecting to see your own indexed content.
</Warning>

<Info>
  **User context uses login email.** The person searching is always an amberSearch user identified by the **email on their account** (the email they have when they log in to amber). Every permission that targets a specific person must use **that same email**: `POST /users` → `user.email`, `allowed_users` → `{ "email": "..." }`, `POST /memberships` → `member_email`, and `POST .../documents/check-access` → `user_email`. Email lookups are **case-insensitive** (stored lowercased).
</Info>

Pick **one** primary rule per document:

| Goal                                               | Set                                                                    |
| -------------------------------------------------- | ---------------------------------------------------------------------- |
| Everyone registered on this datasource can find it | `allow_anonymous_access: true`, **or** omit `permissions` entirely     |
| Only listed people                                 | `allowed_users` (after indexing those users --- see below)             |
| Members of named groups                            | `allowed_groups` (after creating groups and memberships --- see below) |

<Note>
  Documents indexed **without a `permissions` block** are stored with the anonymous token. That token reaches only users who are **registered on this datasource** (created with `POST /users`) --- not everyone in your amberSearch org. A signed-in user with no user record on the datasource sees **none** of its documents, including anonymous ones. To restrict further *within* the datasource, send `allowed_users` and/or `allowed_groups` on the document.
</Note>

## 1. Open to everyone registered on the datasource

```json theme={null}
"permissions": {
  "allow_anonymous_access": true
}
```

<Note>
  "Anonymous" here means **no per-user or per-group restriction inside the datasource** --- it does **not** mean public to your whole org. The document is still only reachable by people registered on the datasource via `POST /users`.
</Note>

## 2. Specific users only

Permissions are always resolved by **email** --- the same address on the user's amberSearch account.

**a)** Register each person once per datasource. **`email` is required** and must equal the address on their amberSearch user profile (same as when they log in). The optional `name` is for display only and does **not** replace email for access checks.

```bash theme={null}
curl -X POST "https://customerDomain.ambersearch.de/api/indexing/users" \
  -H "Authorization: Bearer YOUR_API_KEY" \
  -H "Content-Type: application/json" \
  -d '{
    "datasource": "internal_wiki",
    "user": {
      "email": "alice@company.com",
      "name": "Alice Smith"
    }
  }'
```

**Response codes for `POST /users`**

| Code                | When                                                                       |
| ------------------- | -------------------------------------------------------------------------- |
| **200 OK**          | User created.                                                              |
| **400 Bad Request** | `user.email` is missing or empty after normalization (trim + lower-case).  |
| **404 Not Found**   | Datasource not found.                                                      |
| **409 Conflict**    | A user with the same (normalized) email already exists in this datasource. |

Update a user's display name later with `PUT /users/{datasource}/{email}`:

```bash theme={null}
curl -X PUT "https://customerDomain.ambersearch.de/api/indexing/users/internal_wiki/alice@company.com" \
  -H "Authorization: Bearer YOUR_API_KEY" \
  -H "Content-Type: application/json" \
  -d '{ "name": "Alice S." }'
```

**Response codes for `PUT /users/{datasource}/{email}`**

| Code                | When                                                                |
| ------------------- | ------------------------------------------------------------------- |
| **200 OK**          | User updated.                                                       |
| **400 Bad Request** | The `email` path parameter is empty after normalization.            |
| **404 Not Found**   | Datasource not found, or no user with that email in the datasource. |

**b)** Reference them on the document with **`allowed_users` and the same emails**:

```json theme={null}
"permissions": {
  "allowed_users": [
    { "email": "alice@company.com" },
    { "email": "bob@company.com" }
  ]
}
```

## 3. Groups

Order of operations:

1. **Users** --- `POST /users` for each person (same as above).
2. **Group** --- `POST /groups` with a `name`. **Group name rules**: must be non-empty, must **not contain whitespace**, and must **not start with `amber`** (case-insensitive).
3. **Membership** --- `POST /memberships` with `group_name` and **either `member_email` (the member's amber login email) or `member_group_name` (to nest another group)**. Exactly one of the two must be set.
4. **Document** --- `allowed_groups: ["engineering"]`.

Create a group:

```bash theme={null}
curl -X POST "https://customerDomain.ambersearch.de/api/indexing/groups" \
  -H "Authorization: Bearer YOUR_API_KEY" \
  -H "Content-Type: application/json" \
  -d '{
    "datasource": "internal_wiki",
    "group": { "name": "engineering" }
  }'
```

**Response codes for `POST /groups`**

| Code                | When                                                                        |
| ------------------- | --------------------------------------------------------------------------- |
| **200 OK**          | Group created.                                                              |
| **400 Bad Request** | `group.name` contains whitespace or starts with `amber` (case-insensitive). |
| **404 Not Found**   | Datasource not found.                                                       |
| **409 Conflict**    | A group with that name already exists in this datasource.                   |

Rename it later with `PUT /groups/{datasource}/{group_name}`:

```bash theme={null}
curl -X PUT "https://customerDomain.ambersearch.de/api/indexing/groups/internal_wiki/engineering" \
  -H "Authorization: Bearer YOUR_API_KEY" \
  -H "Content-Type: application/json" \
  -d '{ "group_name": "eng-team" }'
```

**Response codes for `PUT /groups/{datasource}/{group_name}`**

| Code                | When                                                                |
| ------------------- | ------------------------------------------------------------------- |
| **200 OK**          | Group renamed.                                                      |
| **400 Bad Request** | The new `group_name` contains whitespace or starts with `amber`.    |
| **404 Not Found**   | Datasource not found, or no group with that name in the datasource. |

Add a user as a member (`member_email` = amber login email):

```bash theme={null}
curl -X POST "https://customerDomain.ambersearch.de/api/indexing/memberships" \
  -H "Authorization: Bearer YOUR_API_KEY" \
  -H "Content-Type: application/json" \
  -d '{
    "datasource": "internal_wiki",
    "membership": {
      "group_name": "engineering",
      "member_email": "alice@company.com"
    }
  }'
```

Or nest one group inside another (`member_group_name`):

```bash theme={null}
curl -X POST "https://customerDomain.ambersearch.de/api/indexing/memberships" \
  -H "Authorization: Bearer YOUR_API_KEY" \
  -H "Content-Type: application/json" \
  -d '{
    "datasource": "internal_wiki",
    "membership": {
      "group_name": "all-staff",
      "member_group_name": "engineering"
    }
  }'
```

**Response codes for `POST /memberships`**

| Code              | When                                                                                                                                    |
| ----------------- | --------------------------------------------------------------------------------------------------------------------------------------- |
| **200 OK**        | Membership created.                                                                                                                     |
| **404 Not Found** | Datasource not found, parent group not found, or the referenced user / `member_group_name` does not exist.                              |
| **409 Conflict**  | The membership already exists, **or** the new `group → group` edge would close a cycle — the response message lists the offending path. |

Document snippet:

```json theme={null}
"permissions": {
  "allowed_groups": ["engineering"]
}
```

### Remove a membership

`DELETE /memberships/{datasource}/{group_name}/{member_type}/{member_id}` --- `member_type` is `user` or `group`. For `user`, `member_id` is the email; for `group`, it is the nested group's name.

```bash theme={null}
curl -X DELETE "https://customerDomain.ambersearch.de/api/indexing/memberships/internal_wiki/engineering/user/alice@company.com" \
  -H "Authorization: Bearer YOUR_API_KEY"

curl -X DELETE "https://customerDomain.ambersearch.de/api/indexing/memberships/internal_wiki/all-staff/group/engineering" \
  -H "Authorization: Bearer YOUR_API_KEY"
```

**Response codes for `DELETE /memberships/{datasource}/{group_name}/{member_type}/{member_id}`**

| Code                | When                                                                                                  |
| ------------------- | ----------------------------------------------------------------------------------------------------- |
| **200 OK**          | Membership removed.                                                                                   |
| **400 Bad Request** | `member_type` is neither `user` nor `group`.                                                          |
| **404 Not Found**   | Datasource, parent group, referenced user / member-group, or the membership row itself was not found. |

<Note>
  Group membership is **transitive**. When `engineering` is a member of `all-staff`, anyone who is a member of `engineering` (directly or via further nesting) is also treated as a member of `all-staff` for access checks.
</Note>

## Verify access

`user_email` must be the same **amber login email** you use in `/users` and permissions. The check resolves the user's transitive group memberships and compares them to the document's stored access tokens.

```bash theme={null}
curl -X POST "https://customerDomain.ambersearch.de/api/indexing/documents/check-access" \
  -H "Authorization: Bearer YOUR_API_KEY" \
  -H "Content-Type: application/json" \
  -d '{
    "datasource": "internal_wiki",
    "document_id": "architecture-doc",
    "user_email": "alice@company.com"
  }'
```

<ResponseExample>
  ```json 200 theme={null}
  {
      "has_access": true,
      "datasource": "internal_wiki",
      "document_id": "architecture-doc",
      "user_email": "alice@company.com"
  }
  ```
</ResponseExample>

**Response codes for `POST /documents/check-access`**

| Code                         | When                                                              |
| ---------------------------- | ----------------------------------------------------------------- |
| **200 OK**                   | Access decision returned in `has_access`.                         |
| **404 Not Found**            | Datasource not found, or Solr has no document with that `id`.     |
| **422 Unprocessable Entity** | `user_email` is missing or empty (rejected by the request model). |

Remember that newly indexed documents may take **up to 2 hours** to be committed and become discoverable; `check-access` queries the search index directly so it will not find a document that is still being processed.

## Behaviour notes

* Permission updates can take a short time to apply everywhere in search.
* Tighten rules after testing: start with `allow_anonymous_access: true` (or no `permissions`) if you need to confirm indexing before locking down. Even then, register your own login email on the datasource with `POST /users` first --- without a user record you will not see the datasource's documents, anonymous or otherwise.
* Renaming a group **after** documents reference it will leave existing Solr documents pointing at the old name; re-index those documents to apply the new name.
