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

# Permissions flow

> Grant and revoke access to data, secrets, and live connections with an API key.

The Permissions API lets you drive access control from your own systems instead of the app — provisioning a new analyst from your HR tool, mirroring a group's access onto a new hire, or revoking everything the moment someone leaves.

Access in Nekt has three axes. They are separate endpoints because they scope different kinds of thing, but they share one request shape, so learning one teaches all three.

| Axis                             | Resources                            | Endpoint                                   |
| -------------------------------- | ------------------------------------ | ------------------------------------------ |
| **Catalog**                      | Layers, folders, tables, and volumes | `/api/v1/permissions/`                     |
| **Secrets and live connections** | Secrets, live connections            | `/api/v1/organization/object-permissions/` |
| **Semantic Layer**               | Catalog root, folders, documents     | `/api/v1/semantic-layer/permissions/`      |

All three share the same three levels — `viewer`, `editor`, `manager` — and the same `assignments` payload. What a level *means* differs per resource: see [Permissions](/workspace/permissions) for the Catalog axis and [Assign Object Permissions](/platform-api/permissions/objects/assign) for secrets and live connections.

<Info>
  Data-level permissions apply on **Growth and Custom** plans, and the plan gate is not symmetric across the axes. The Catalog endpoints return `403` outright below Growth. On the other two, grants can be written but do not restrict anything — every member still sees everything until the plan enforces.
</Info>

***

## Before you start

Permissions are granted to **Members**. Owners and Admins bypass data-level access entirely, so there is nothing to grant them — which is why they never appear in the recipients endpoints.

You will need an [API key](https://app.nekt.ai/settings/api-keys), and it matters who created it. Read the ceiling rule below before you build anything.

***

## The ceiling rule

An API key is not an all-powerful credential. **It acts as the person who created it**, carrying their id and their role.

<Warning>
  A key created by a **Member** can only grant `viewer` and `editor`, and only on resources where that Member holds `manager`. It cannot create or remove `manager` grants at all. A key created by an **Owner** or **Admin** has no such limit.
</Warning>

This is the same rule the app applies to that person, with no separate concept for machines — so the fastest way to reason about what your key can do is to ask what its creator can do in the UI. It also means a key does not outlive its creator's authority: demote the person and the key narrows with them.

If you are automating workspace-wide provisioning, create the key as an Owner or Admin. If you are letting a team self-serve within their own domain, a Member's key is the right ceiling.

***

## The flow

<Steps>
  <Step title="Find the recipient">
    Grants target a user or a permission group by UUID, and the recipients endpoint is the only public source of those ids — there is no separate user or group listing.

    ```bash theme={null}
    curl --request GET \
      --url "https://api.nekt.ai/api/v1/permission-recipients/?expand=users&search=analyst@example.com" \
      --header "x-api-key: YOUR_API_KEY"
    ```

    An empty `users` list usually means the person is an Owner or Admin, or is not an active member — not that the search failed.
  </Step>

  <Step title="Find the resource">
    Catalog resources come from [List Layers](/platform-api/catalog/list-layers) and the folders, tables, and volumes endpoints. Granting on a **layer** covers everything inside it, so prefer the coarsest resource that expresses your intent.
  </Step>

  <Step title="Assign">
    One call can create, change, and revoke at once. Each assignment block pairs every recipient with every resource and applies one level.

    ```bash theme={null}
    curl --request POST \
      --url https://api.nekt.ai/api/v1/permissions/ \
      --header "x-api-key: YOUR_API_KEY" \
      --header "Content-Type: application/json" \
      --data '{
        "assignments": [
          {
            "permission_level": "viewer",
            "users": ["3f7c1e88-9a41-4b2d-8e5f-6c0a2d4b9e11"],
            "layers": ["e4d5c6b7-a8b9-40c1-d2e3-f4a5b6c7d8e9"]
          }
        ]
      }'
    ```

    The response tells you exactly what changed:

    ```json theme={null}
    { "created": [ … ], "updated": [], "revoked": [] }
    ```
  </Step>

  <Step title="Verify">
    List the grants back, filtered by recipient. `indirect_user` includes grants the person receives through a group, which is what you usually want when auditing what someone can actually reach.

    ```bash theme={null}
    curl --request GET \
      --url "https://api.nekt.ai/api/v1/permissions/?indirect_user=3f7c1e88-9a41-4b2d-8e5f-6c0a2d4b9e11&expand=table,layer" \
      --header "x-api-key: YOUR_API_KEY"
    ```
  </Step>

  <Step title="Revoke">
    Two ways, and they differ in what you need to know beforehand. Send `permission_level: null` through the assign endpoint to revoke by recipient and resource, or `DELETE` the grant if you already hold its id.

    ```bash theme={null}
    curl --request POST \
      --url https://api.nekt.ai/api/v1/permissions/ \
      --header "x-api-key: YOUR_API_KEY" \
      --header "Content-Type: application/json" \
      --data '{
        "assignments": [
          {
            "permission_level": null,
            "users": ["3f7c1e88-9a41-4b2d-8e5f-6c0a2d4b9e11"],
            "layers": ["e4d5c6b7-a8b9-40c1-d2e3-f4a5b6c7d8e9"]
          }
        ]
      }'
    ```

    Revoking is a soft delete: the grant stops applying immediately and leaves the listing, and the row is retained with the actor and timestamp for the audit trail.
  </Step>
</Steps>

***

## How an assignment resolves

Three behaviors surprise people, and all three are deliberate.

**It is an upsert, not a create.** You describe the access you want; Nekt computes the difference against what exists. Sending the same request twice is safe — the second call reports nothing created, updated, or revoked.

**Each block is a cross product.** Two users and three tables in one assignment produce six grants. To apply different levels, use separate blocks.

**`null` is a verb.** `permission_level: null` revokes the pair rather than being a no-op or an error.

<Note>
  One more asymmetry worth knowing before you automate: `notify_members` sends email on the **Semantic Layer** axis and is silently ignored on the Catalog axis. If you are provisioning in bulk, leave it `false`.
</Note>

<Warning>
  Two blocks that name the same recipient and the same resource with **different** levels are rejected with `400`, naming the conflicting positions. Repeating the same pair at the same level is fine.
</Warning>

***

## Group grants versus direct grants

A recipient can be a user or a permission group, and a person's effective access is the **union** of every grant that reaches them. A group grant is not a snapshot: adding someone to the group later gives them that access, and removing them takes it away, with no permission call at all.

Managing a stable team through a group and reserving direct grants for exceptions keeps the number of API calls proportional to the number of teams rather than the number of people.

***

## Errors

| Status | Meaning                                             | What to check                                                                                                                                     |
| ------ | --------------------------------------------------- | ------------------------------------------------------------------------------------------------------------------------------------------------- |
| `400`  | The request describes something impossible.         | Conflicting blocks, an id your key cannot see, a deactivated member, or a `tools` rule on the other axis.                                         |
| `403`  | The plan or the ceiling refuses.                    | Your workspace is on Free or Starter, or the key's creator lacks `manager` on the resource — or is trying to touch a `manager` grant as a Member. |
| `404`  | The grant id does not exist, or is already revoked. | Revoked grants are excluded from reads, so a second `DELETE` returns `404`.                                                                       |

The `403` messages name the rule that refused, for example `Member users cannot assign manager permissions.` Read the body before assuming the key is broken.

***

## Reference

<CardGroup cols={2}>
  <Card title="Assign Permissions" icon="key" href="/platform-api/permissions/catalog/assign">
    Grant, change, and revoke Catalog access in bulk.
  </Card>

  <Card title="List Recipients" icon="users" href="/platform-api/permissions/catalog/recipients">
    The users and groups that can receive a grant.
  </Card>

  <Card title="Assign Object Permissions" icon="lock" href="/platform-api/permissions/objects/assign">
    The same flow for secrets and live connections.
  </Card>

  <Card title="Assign Semantic Layer Permissions" icon="brain" href="/platform-api/permissions/semantic-layer/assign">
    The same flow for catalogs, folders, and documents.
  </Card>

  <Card title="Permissions in the app" icon="sliders" href="/workspace/permissions">
    What each level allows, module by module.
  </Card>
</CardGroup>
