> For the complete documentation index, see [llms.txt](https://docs.akenza.io/llms.txt). Markdown versions of documentation pages are available by appending `.md` to page URLs; this page is available as [Markdown](https://docs.akenza.io/reference/api-documentation/authentication.md).

# Authentication

akenza supports two credential types for machine-to-machine access: **API keys** and **OAuth2 Clients**. Both use the same permission model: credentials are scoped to an organization, optionally restricted to specific workspaces, and carry a fine-grained set of permissions.

**API Keys** are passed via `x-api-key` header to authenticate requests. Choose API Keys when your organization favors simplicity and does not have any requirements regarding short-lived credentials or secret rotation.

**OAuth2 Clients** authenticate with the industry-standard **OAuth2 client credentials grant** (OpenID Connect): your application exchanges a client ID and client secret for a short-lived access token and sends it as a standard bearer token. Choose OAuth2 clients when your organization requires standard OAuth2 tooling, short-lived credentials, or secret rotation.

## Which credential type should I use?

|                  | API keys                                | OAuth2 Clients                                                    |
| ---------------- | --------------------------------------- | ----------------------------------------------------------------- |
| Authentication   | Static secret in the `x-api-key` header | Short-lived bearer token from the OAuth2 client credentials grant |
| Token lifetime   | n/a (key is long-lived)                 | 15 minutes                                                        |
| Secret rotation  | Delete & recreate the key               | Rotate the client secret in place                                 |
| Tooling          | Any HTTP client                         | Any OAuth2/OIDC client library                                    |
| Permission model | identical                               | identical                                                         |

## API Keys

### Creating a API Keys

API-keys can be found under the API-key menu entry. They can only be created and viewed by **Organization Owners** and **Organization Administrators**. Other Members of the Organization have no access to API-keys.

![API-keys with assigned permissions](https://2165942204-files.gitbook.io/~/files/v0/b/gitbook-x-prod.appspot.com/o/spaces%2F-MMKXTFIN5ZlLOjBlfC4%2Fuploads%2FWY3XQER4fLbMIlBnOX9H%2Fscreely-1685093330043.png?alt=media\&token=b28c84dd-80ae-4770-9937-129e9615c49f)

### Calling the API

Send the api key as a static secret in the `x-api-key` header:

```bash
curl -s 'https://api.akenza.io/v3/assets' \
  -H 'x-api-key: 099123122.2ab1000..'
```

Do not combine authentication methods: a request carrying both an `Authorization` header and an `x-api-key` header is rejected.

## OAuth2 Clients

### Creating an OAuth2 Client

OAuth2 clients are managed at the organization level and require organization `manage` permission.

<details>

<summary><strong>Via the Console (TBA)</strong></summary>

</details>

<details>

<summary><strong>Via the REST API (authenticated as a user)</strong></summary>

```
POST https://api.akenza.io/v3/oauth-clients
```

```json
{
  "name": "data-poller",
  "description": "Client for data polling",
  "organizationId": "0123456789abcdef",
  "allWorkspaces": true,
  "permissions": {
    "asset": [
      "read"
    ]
  }
}
```

To restrict the client to specific workspaces, set `"allWorkspaces": false` and list the workspaces:

```json
{
  "allWorkspaces": false,
  "workspaces": [
    {
      "id": "fedcba9876543210"
    }
  ]
}
```

The response contains the credentials:

```json
{
  "id": "1a2b3c4d5e6f7a8b",
  "name": "data-poller",
  "organizationId": "0123456789abcdef",
  "clientId": "3fa1b2c3d4e5f6a7",
  "clientSecret": "xxxxxxxx-xxxx-xxxx-xxxx-xxxxxxxxxxxx",
  "tokenUrl": "https://login.akenza.io/realms/api-keys/protocol/openid-connect/token",
  "permissions": {
    "...": "..."
  }
}
```

Store the `clientSecret` in a secret manager it will not be available later.

</details>

### Requesting an access token

Request tokens from the akenza token endpoint using the client credentials grant:

| Environment | Token endpoint                                                          |
| ----------- | ----------------------------------------------------------------------- |
| Production  | `https://login.akenza.io/realms/api-keys/protocol/openid-connect/token` |

```bash
curl -s -X POST 'https://login.akenza.io/realms/api-keys/protocol/openid-connect/token' \
  -H 'Content-Type: application/x-www-form-urlencoded' \
  -d 'grant_type=client_credentials' \
  -d 'client_id=YOUR_CLIENT_ID' \
  -d 'client_secret=YOUR_CLIENT_SECRET'
```

HTTP Basic authentication (`-u YOUR_CLIENT_ID:YOUR_CLIENT_SECRET`) is supported as well.

```json
{
  "access_token": "eyJhbGciOiJSUzI1NiIs...",
  "expires_in": 900,
  "token_type": "Bearer"
}
```

Tokens are valid for **15 minutes**; there are no refresh tokens. Request a new token when the current one is about to expire. **Cache the token** and reuse it across API calls; do not request a new token per request.

akenza publishes standard OpenID Connect discovery metadata at `https://login.akenza.io/realms/api-keys/.well-known/openid-configuration`, so most OAuth2 client libraries can be configured with the discovery URL alone.

### Calling the API

Send the token as a bearer token in the `Authorization` header:

```bash
curl -s 'https://api.akenza.io/v3/assets' \
  -H 'Authorization: Bearer eyJhbGciOiJSUzI1NiIs...'
```

Do not combine authentication methods: a request carrying both an `Authorization` header and an `x-api-key` header is rejected.

### Managing OAuth2 Clients

| Action            | Request                                        |
| ----------------- | ---------------------------------------------- |
| List clients      | `GET /v3/oauth-clients?organizationId={orgId}` |
| Get a client      | `GET /v3/oauth-clients/{id}`                   |
| Rotate the secret | `POST /v3/oauth-clients/{id}/rotate-secret`    |
| Delete a client   | `DELETE /v3/oauth-clients/{id}`                |

**Rotation:** the old secret stops working immediately; tokens that were already issued remain valid until they expire (at most 15 minutes). Rotate during a maintenance window or update your deployment with the new secret right away.

**Deletion:** the client can no longer obtain tokens, and already-issued tokens are rejected within about one minute.

## Permissions

Both OAuth2 Clients and API keys use the same permission model. Each entry in `permissions` maps a scope to a list of verbs (`create`, `read`, `update`, `delete`, `manage`):

* `organization` (read is always granted)
* `organization.member`
* `workspace`
* `workspace.member`
* `asset`
* `deviceType`
* `dataFlow`
* `integration`
* `rule`
* `customLogic`
* `deviceCredential`
* `dashboard`
* `dashboard.group`
* `billing`
* `blob`
* `dataFusionLayer`

Scopes containing a dot must be quoted as JSON keys, e.g. `"workspace.member": ["read"]`.

## Troubleshooting

| Symptom                                      | Cause & fix                                                                    |
| -------------------------------------------- | ------------------------------------------------------------------------------ |
| `401 invalid token` when calling the API     | The token expired, or the client was deleted. Request a new token.             |
| `401 Two authentication headers received`    | The request carries both `Authorization` and `x-api-key`. Send exactly one.    |
| `401 invalid_client` from the token endpoint | Wrong `client_id`/`client_secret`                                              |
| `403 permission denied` on an API call       | The client lacks the required scope/verb or workspace - check its permissions. |


---

# Agent Instructions
This documentation is published with GitBook. GitBook is the documentation platform designed so that both humans and AI agents can read, navigate, and reason over technical content effectively. Learn more at gitbook.com.

## Querying This Documentation
If you need additional information that is not directly available in this page, you can query the documentation dynamically by asking a question.

Perform an HTTP GET request on the current page URL with the `ask` query parameter, and the optional `goal` query parameter:

```
GET https://docs.akenza.io/reference/api-documentation/authentication.md?ask=<question>&goal=<endgoal>
```

`ask` is the immediate question: it should be specific, self-contained, and written in natural language.
`goal` is optional and describes the broader end goal you are ultimately trying to accomplish on behalf of the user. GitBook uses it to tailor the answer towards what is most useful for that goal.

The response will contain a direct answer to the question and relevant excerpts and sources from the documentation.

Use this mechanism when the answer is not explicitly present in the current page, you need clarification or additional context, or you want to retrieve related documentation sections.
