---
sidebar_label: Manage your API keys
doc_id: bebadb09-1b5f-4f20-a2b4-b27254a9cbe5
description: >-
  Learn how to create and manage API keys for programmatic access, granting
  roles or fine-grained permissions on each resource.
keywords:
  - API keys
  - authentication
  - access control
  - automation
  - permissions
  - fine-grained permissions
title: Manage your API keys
canonical: 'https://docs.nullplatform.com/docs/authorization/api-keys'
---
# Manage your API keys

An API key lets you interact with nullplatform programmatically and is ideal for automation and system tasks.
For machine users like CI workflows, create API keys with specific roles to control access and permissions.

**Keep in mind:** API keys are sensitive, so handle them carefully to prevent unauthorized access.

:::info You need an [Admin or Ops role](/docs/authorization/roles) to create and manage API keys.
:::

You can create and manage API keys through the UI, CLI, or API.

## How API keys fit into authorization

API keys act as **machine users**, and each API key has one or more **grants**.

#### Grants

Grants define the API key's access: **where** it applies and **what the key may do** there.

- **Resource**: The resource where permissions apply, for example *account: main*.  
- **Permissions**: What the key may do at that resource. Usually one or more roles, for example *Agent*. You can also start from roles and adjust single permissions, or hand-pick them one by one as **Custom** permissions.

> ℹ️ **Note:** You can add multiple grants, including several on the same resource.

Whichever way you define the permissions, each grant resolves to one set of actions the key may perform on that resource:

```mermaid
flowchart LR
    K["**API key**"]
    G["**Grant**<br/>on a resource"]
    R["Roles"]
    A["Roles, adjusted<br/>by hand"]
    P["Hand-picked<br/>permissions"]
    E["**What the key may do**<br/>on that resource"]

    K --> G
    G --> R
    G --> A
    G --> P
    R --> E
    A --> E
    P --> E

    classDef np    fill:#e6faf4,stroke:#00b894,color:#0b1e3f,stroke-width:1.5px;
    classDef shape fill:#eef3fb,stroke:#274a86,color:#0b1e3f,stroke-width:1.5px;
    class K,G,E np;
    class R,A,P shape;

    linkStyle 0,1,2,3 stroke:#0b1e3f,stroke-width:2px;
    linkStyle 4,5,6 stroke:#00b894,stroke-width:2px;
```

- **Roles**: the key holds exactly what the roles grant. The Role field lists them, for example *Ops, Developer*.
- **Roles, adjusted by hand**: you start from roles and tick or untick single permissions in Customize permissions.
- **Hand-picked permissions**: you tick the permissions one by one with no role selected. The Role field shows **Custom**.

You can only hand out what you could grant yourself on that resource: the permissions you hold there, plus the ones carried by roles you may assign to an API key.

For more details on grants and roles, see [Roles](/docs/authorization/roles) and [Grants and permissions](/docs/authorization/grants).

## Using the UI

Go to **Platform settings > API keys** and click **Create API key**. Walk through the form below:

<GuidedTour
  alt="The New API key form, recreated as an HTML scene with a step-by-step walkthrough"
  steps={[
    {
      title: '1. Name the key',
      body: 'Use a name that says which system will use it, for example a CI pipeline.',
      anchor: 'api-key-name',
      placement: 'bottom',
    },
    {
      title: '2. Choose where the grant applies',
      body: 'The key gets these permissions on this resource and everything under it.',
      anchor: 'api-key-grant-resource',
      placement: 'bottom',
    },
    {
      title: '3. Start from roles',
      body: 'The roles you pick preselect the permissions. A customized pill marks a grant you adjusted by hand.',
      anchor: 'api-key-grant-role',
      placement: 'bottom',
    },
    {
      title: '4. Customize permissions',
      body: 'Open the full list to tick or untick single permissions on top of the roles. It becomes available once the grant has a resource.',
      anchor: 'api-key-grant-permissions',
      placement: 'bottom',
      align: 'end',
    },
    {
      title: '5. Tick permissions one by one',
      body: 'Each box is one action on one kind of resource. Boxes outside what you can hand out show disabled, and the footer counts what the key may do.',
      anchor: 'api-key-permissions-matrix',
      placement: 'top',
      align: 'end',
    },
    {
      title: '6. Generate the key',
      body: 'Add more grants if the key needs access elsewhere, then generate it.',
      anchor: 'api-key-generate',
      placement: 'top',
    },
    {
      title: '7. Copy the key right away',
      body: "The key is shown only once. Copy it and store it somewhere safe before closing this dialog: it can't be retrieved later.",
      anchor: 'api-key-created',
      placement: 'bottom',
    },
  ]}
>
  <ApiKeyScene />
</GuidedTour>

:::warning Save your API key securely
The API key is **displayed only once**, right after you generate it. Copy it and store it in a secure location, as it cannot be retrieved later.
:::

To edit or delete a key later, open its **⋮** menu in the list and click **View or edit** or **Delete**.

## Using the CLI or API

### Create an API key

Send a [POST request](/docs/api/api-key-create) to create an API key.

Here's an example request:

  **create-api-key-cli**

    ```bash
    np api-key create \
    --body '{
        "name": "my-machine-process-that-will-access-nullplatform",
        "grants": [
          {
            "nrn": "organization=1:account=2:namespace=3:application=4",
            "role_slug": "admin"  // Alternatively, use "role_id". For example: "696188987"
          }
        ],
        "tags": [
          {
            "key": "CI",
            "value": "main"
          }
        ]
      }'
    ```

  **create-api-key-curl**

    ```bash
    curl -L -X POST 'https://api.nullplatform.com/api_key' \
      -H 'Content-Type: application/json' \
      -H 'Accept: application/json' \
      -d '{
        "name": "my-machine-process-that-will-access-nullplatform",
        "grants": [
          {
            "nrn": "organization=1:account=2:namespace=3:application=4",
            "role_slug": "admin"  // Alternatively, use "role_id". For example: "696188987"
          }
        ],
        "tags": [
          {
            "key": "CI",
            "value": "main"
          }
        ]
      }'
    ```

Where:

- `grants` defines the access permissions for the API key.
- `nrn` is where the API key's role is assigned.
- `role_slug` is the slug of the role assigned to the API key for the specified NRN. You can also provide the `role_id` instead of `role_slug`.

#### Example response

You'll receive a `2XX` response like this:

```json
{
  "id": "123",
  "name": "my-machine-process-that-will-access-nullplatform",
  "api_key": "AAAA.1234567890abcdef1234567890abcdefPTs=",  // Your new API key.
  "masked_api_key": "AAAA.xxxxxxxxxxxxxxxxxxxxxPTs=",
  "tags": [
    {
      "key": "CI",
      "value": "main"
    }
  ],
  "grants": [
    {
      "nrn": "organization=1:account=2:namespace=3:application=4",
      "role_slug": "admin",
      "role_id": 696188987
    }
  ],
  "owner_id": 1595,
  "last_used_at": null,
  "created_at": "2024-01-01T00:00:00Z",
  "updated_at": "2024-01-01T00:00:00Z"
}
```

Where:

`api_key` is your newly created API key.

:::warning Save your API key securely.
The API key is **displayed only once**. Make sure to store it in a secure location, as it cannot be retrieved later.
:::

### Grant access to API keys

In the UI, a grant is one row of the form: the resource, the roles, and whatever you changed in **Customize permissions**. Through the API, that same grant is an item of the `grants` field, in one of three shapes:

- Roles only: `role_slug` or `role_id`, one item per role.
- Roles adjusted in Customize permissions: `inherits`, plus `actions.add` and `actions.remove`.
- Custom permissions, ticked with no role selected: `actions`.

To grant a role, include the following in your request to [create](/docs/api/api-key-create) or [update](/docs/api/api-key-update) an API key:
- `nrn` specifies the resource where the API key's role will be assigned.
- `role_slug` is the slug of the role you want to assign to the API key for the specified NRN. You can also provide the `role_id` instead of `role_slug`.

```json
"grants": [
  {
    "nrn": "organization=1:account=2:namespace=3:application=4",
    "role_slug": "admin"  // Alternatively, use "role_id": "696188987"
  }
]
```
In this example, the `admin` role is assigned to the NRN `organization=1:account=2:namespace=3:application=4`. This allows the API key to handle tasks like creating, modifying, and deleting applications and their scopes for the resource.

:::info For the complete list of available role IDs, see [Roles](/docs/authorization/roles).
:::

To grant several roles on the same resource, repeat the item:

```json
"grants": [
  { "nrn": "organization=1:account=2", "role_slug": "ops" },
  { "nrn": "organization=1:account=2", "role_slug": "developer" }
]
```

:::warning Name roles one way
Use `role_id` in every grant of a key, or `role_slug` in every grant, never one in some and the other in others. Every grant of a key is resolved the same way, so a mixed request fails.
:::

### Grant custom permissions

When no existing role is the right size, a grant can name actions directly, which is what the UI shows as **Custom** in the Role field. Instead of `role_slug`, list them in `actions`:

```json
"grants": [
  {
    "nrn": "organization=1:account=2:namespace=3",
    "actions": ["application:read", "deployment:create"]
  }
]
```

This key can read applications and create deployments in that namespace, and nothing else. Nullplatform creates a role private to the key to hold those actions. You don't manage it, it never appears among your roles, and it's deleted with the key.

:::info You can only grant what you could hand out yourself
At a given resource, that's the actions you hold there plus the ones carried by roles you may assign to an API key there. To see that ceiling for an NRN, [list your roles](/docs/api/user-role-list) with `include=actions`.
:::

### Inherit existing roles and adjust them

A grant can also start from roles that already exist and change the result. Use `inherits` for the roles, then `actions.add` and `actions.remove`:

```json
"grants": [
  {
    "nrn": "organization=1:account=2",
    "inherits": ["ops", "developer"],
    "actions": {
      "add": ["application:delete"],
      "remove": ["deployment:create"]
    }
  }
]
```

This key gets everything `ops` and `developer` grant, plus `application:delete`, minus `deployment:create`.

Listing two roles in one `inherits` is not the same as granting them as two items. Two items give the key two independent roles, and there's nothing to subtract from. One `inherits` produces a single role, and that role is what `actions.remove` acts on.

An inherited grant follows its roles. If one of them later gains an action, the key gains it too, unless it's in `actions.remove`. That's the difference from a literal `actions` list, which never changes on its own.

A few rules apply. You need to be allowed to assign each inherited role to an API key at that resource, the same check a grant by `role_slug` passes. What you add by hand is measured against what you can hand out there, while removing never is. `actions.remove` can only name actions the inherited roles carry, an action can't be in both lists, and the grant can't end up empty. You can inherit by role ID instead of slug, but a role private to another API key can't be inherited.

### What the API returns for these grants

A grant you wrote with `actions` or `inherits` doesn't come back in the shape you sent. The response reports what the grant resolved to. This example is an inherited grant, which carries the most fields:

```json
"grants": [
  {
    "nrn": "organization=1:account=2",
    "role_id": 841203556,
    "role_slug": "apikey:123:0",
    "actions": ["application:read", "application:delete"],
    "inherits": [
      { "id": 708509758, "slug": "ops", "organization_id": null },
      { "id": 700317756, "slug": "developer", "organization_id": null }
    ],
    "added": ["application:delete"],
    "removed": ["deployment:create"]
  }
]
```

Where:

- `actions` is the effective set: everything the grant authorizes, after inheritance and removals.
- `inherits` names the roles that were merged. `organization_id` is `null` for a platform role, and your organization's ID for a role of your own or your override of a platform one. `added` and `removed` report the adjustment you sent.
- `role_id` and `role_slug` name the private role holding all this. It's an implementation detail: nullplatform recreates it whenever you replace the key's grants, so don't store it or reference it anywhere.

A grant you wrote with `actions` returns `nrn`, `actions`, `role_id` and `role_slug` only: there's nothing inherited to report, so `inherits`, `added` and `removed` are absent. A grant naming an existing role returns just `nrn`, `role_id` and `role_slug`.

:::note Aliased actions come back under the alias
Some actions have an alias, a second name they're exposed under. Such an action is listed by its alias, not by the name it's stored as.
:::

### Generate an access token

Use your new API key to create an access token, which allows you to authenticate and make API calls with nullplatform.

:::note
  These instructions are for API keys and machine users. If you're a human user, check the [Authorization](/docs/authorization/) article.
:::

Send a [POST request](/docs/api/access-token-create) to get your token. You can use either:

- your API key, or
- your username and password

Here are examples:

  **create-token-curl**

```bash
curl -L -X POST 'https://api.nullplatform.com/token' \
-H 'Content-Type: application/json' \
-H 'Accept: application/json' \
-d '{
    "api_key": "AAAA.1234567890abcdef1234567890abcdefPTs="
  }'
```

  **create-token-cli**

  ```bash  
  np token create \
    --body '{
      "api_key": "AAAA.1234567890abcdef1234567890abcdefPTs="
    }'
  ```

Instead of an API key, you can send the username, password, and organization ID. Like this:

```json
{
  "username": "alex.doe@email.com",
  "password": "your_password",
  "organization_id": "1234"
}
```

**Response**

You'll receive a `2XX` response like this:

```json
{
  "refresh_token": "epJjdHky...",   // A long-lived token used to obtain new access tokens.
  "access_token": "epJraWQy...",    // A short-lived bearer token for making API calls.
  "organization_id": 123245,     // The unique ID of the organization.
  "token_expires_at": 1700000000000
}
```

### Renew an access token

When your access token expires, you can make a [POST request](/docs/api/access-token-create) to create a new one. You'll need your `refresh_token` and `organization_id` for the request.

Example request:

  **renew-token-cli**

  ```bash  
  np token create \
    --body '{
      "refresh_token": "epJjdHky...",
      "organization_id": "123245"
    }'
  ```    

  **renew-token-curl**

  ```bash
  curl -L -X POST 'https://api.nullplatform.com/token' \
  -H 'Content-Type: application/json' \
  -H 'Accept: application/json' \
  -d '{
      "refresh_token": "epJjdHky...",
      "organization_id": "123245"
  }'
  ```

Example response:

```json
{
  "access_token": "eyJra3JQb...",    // Your renewed access_token for making API calls.
  "organization_id": 123245
}
```

### Update your API key

To update an existing API key, send a [PATCH request](/docs/api/api-key-update) with the details you want to change. If you include `grants`, the list you send replaces the key's grants. Only what changes is measured against what you can hand out, so a grant you send back unchanged is kept even if it holds permissions you don't.

:::note You'll need the API key `id` for this request. 
To get the API key ID, send a [GET request](/docs/api/api-key-list) to list all the API keys.
:::

Here's an example request to update the API key name and tags:

  **patch-api-key-cli**

    ```bash
    np api-key patch \
      --id 123 \
      --body '{
        "name": "updated-machine-process",
        "tags": [
          {
            "key": "CI",
            "value": "updated"
          }
        ]
      }'
    ```

  **patch-api-key-curl**

    ```bash
      curl -L -X PATCH 'https://api.nullplatform.com/api_key/123' \
      -H 'Content-Type: application/json' \
      -H 'Accept: application/json' \
      -d '{
        "name": "updated-machine-process",
        "tags": [
          {
            "key": "CI",
            "value": "updated"
          }
        ]
      }'
    ```

Where:

- `id`: The unique ID of the API key you want to update (e.g., `123`).
- `name`: The **updated** name for the API key.
- `tags`: The **updated** tags for the API key.

Example response:

```json
{
  "id": "123",
  "name": "updated-machine-process",
  "masked_api_key": "AAAA.xxxxxxxxxxxxxxxxxxxxxPTs=",
  "tags": [
    {
      "key": "CI",
      "value": "updated"
    }
  ],
  "grants": [
    {
      "nrn": "organization=1:account=2:namespace=3:application=4",
      "role_id": 696188987,
      "role_slug": "admin"
    }
  ],
  "owner_id": 1595,
  "last_used_at": "2025-01-10T00:00:00Z",
  "created_at": "2024-01-01T00:00:00Z",
  "updated_at": "2025-01-10T00:00:00Z"
}
```
Where:

- `name`: The **updated** name for the API key.
- `tags`: The **updated** tags for the API key.
- `updated_at`: The time when the API key was last updated.

### Delete your API key

To permanently remove an API key, send a [DELETE request](/docs/api/api-key-delete).

:::note You'll need the API key `id` for this request. 
To get the API key ID, send a [GET request](/docs/api/api-key-list) to list all the API keys.
:::

Here's an example request:

  **delete-api-key-cli**

    ```bash
    np api-key delete \
      --id 123 \
      --auth "Bearer {{access_token}}"
    ```

  **delete-api-key-curl**

  ```bash
  curl -L -X DELETE 'https://api.nullplatform.com/api_key/:id' \
  -H 'Authorization: Bearer <token>'
  ```

You'll receive a `204` response, confirming your API key has been removed successfully.
