---
title: "Authentication"
description: "API keys, scopes and key rotation for Nexora AI applications."
url: "https://nexora.apim.eu/guides/authentication"
image: "https://nexora.apim.eu/_og/d/c_Ocean.takumi,title_Authentication,description_~QVBJIGtleXMsIHNjb3BlcyBhbmQga2V5IHJvdGF0aW9uIGZvciBOZXhvcmEgQUkgYXBwbGljYXRpb25zLg,props_eyJ0aGVtZSI6eyJtb2RlIjoiZGFyayIsImNvbG9ycyI6eyJwcmltYXJ5IjoiIzM5RkYxNCJ9fX0,p_Ii9ndWlkZXMvYXV0aGVudGljYXRpb24i,s_9sFscxotJhzYeQog.png"
---

## Authentication

How applications identify themselves, what scopes exist, and how to rotate keys without interrupting operations.

## [One Header, Nothing Else](#one-header-nothing-else)

All Nexora AI APIs authenticate via an **API key**. It goes along with every call in the `apikey` header:

```bash
curl https://api.nexora.example/v1/agents \
  -H "apikey: $NEXORA_API_KEY"
```

There's no token endpoint, no expiry, and no refresh. That keeps integration simple - but it requires treating the key like a password.

## [Applications and Keys](#applications-and-keys)

Every integration is its own **application**. Each application has one or more keys attached to it.

Create at least two applications: one for the sandbox, one for production. Keeping both under one application means you can't selectively lock one down if there's a leak.

The key is shown **exactly once** at creation. After that it can't be read again, only replaced. Keep it in a secret store - not in version control, not in a ticket, not in a config file baked into an image.

## [Scopes](#scopes)

What a key is allowed to do is attached to it. Grant only what the given process needs: a nightly import doesn't need read access to chargebacks.

| Scope            | Allows                                                      |
| :--------------- | :---------------------------------------------------------- |
| agents:read      | Query agent deployments and artifacts                       |
| agents:write     | Create, update, retire deployments, upload artifacts        |
| catalog:read     | Query catalog entries and their status, download the record |
| catalog:write    | Generate and publish catalog entries                        |
| requests:read    | Query access requests, capability profiles and their scores |
| requests:write   | Maintain access requests, score requests                    |
| sessions:write   | Create, reschedule, cancel evaluation sessions              |
| estimates:read   | Query cost estimates and comparable deployments             |
| chargebacks:read | View settlements and internal invoices                      |

If a scope is missing, the API responds with `403`:

```json
{
  "code": "scope_missing",
  "message": "The API key is missing a required scope.",
  "details": [
    { "field": "catalog:write", "reason": "not_granted" }
  ]
}
```

## [Rotating a Key](#rotating-a-key)

A rotation without downtime works by overlapping - that's why an application allows several active keys:

1.  Generate a second key for the existing application. Both are valid immediately.
2.  Switch the deployment over to the new key.
3.  Confirm no more calls arrive with the old one - the application overview shows `last_used_at` per key.
4.  Revoke the old key.

Rotate on a schedule every 90 days, and immediately if a key ever ends up in a log, a ticket, or a repository. A revoked key stops working within seconds.

## [What Not to Do](#what-not-to-do)

-   **Use the key in the browser.** Anything running on an end-user device exposes it. Call the APIs server-side.
-   **One key for everything.** A key per application and environment is the difference between "lock down one access" and "everything's exposed."
-   **Put the key in the URL.** It belongs in the header; query parameters end up in server logs and browser history.