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

# Authenticate requests with Novala API keys

> Issue publishable and secret API keys, attach scopes, pass credentials with every request, and safely rotate keys without downtime.

Every request to the Novala API must be authenticated with an API key. Novala uses a dual-key model: each key pair consists of a **publishable key** (`pk_live_…`) for safe client-side or read-only use, and a **secret key** (`sk_live_…`) for server-side or write operations.

## Creating an API key

<Steps>
  <Step title="Open API Keys settings">
    In the Novala dashboard, navigate to **Settings → API Keys**.
  </Step>

  <Step title="Create a key pair">
    Click **Create Key Pair**, enter a descriptive name (for example, `Acumatica Integration`), and click **Create**.
  </Step>

  <Step title="Copy your keys immediately">
    The secret key is shown only once. Copy both the publishable key and the secret key before closing the dialog.
  </Step>
</Steps>

<Warning>
  The secret key cannot be retrieved after you close the creation dialog. Store it in a secrets manager immediately. If you lose it, rotate the key pair.
</Warning>

## Passing your key

Include the key in the `Authorization` header of every request:

```
Authorization: Bearer YOUR_SECRET_KEY
```

<CodeGroup>
  ```bash cURL theme={null}
  curl -X GET https://app.novala.ai/api/v1/leads \
    -H "Authorization: Bearer sk_live_YOUR_KEY"
  ```

  ```typescript TypeScript theme={null}
  const response = await fetch('https://app.novala.ai/api/v1/leads', {
    headers: {
      'Authorization': `Bearer ${process.env.NOVALA_SECRET_KEY}`,
    },
  });
  const { data } = await response.json();
  ```

  ```python Python theme={null}
  import httpx

  headers = {"Authorization": f"Bearer {NOVALA_SECRET_KEY}"}
  response = httpx.get("https://app.novala.ai/api/v1/leads", headers=headers)
  leads = response.json()["data"]
  ```
</CodeGroup>

## Key types

| Type        | Prefix      | Intended use                                  |
| ----------- | ----------- | --------------------------------------------- |
| Publishable | `pk_live_…` | Client-side apps, read-only public operations |
| Secret      | `sk_live_…` | Server-side integrations, write operations    |

## Scopes

API keys carry one or more permission scopes. A request to an endpoint that requires a scope your key does not have returns `403 Forbidden`.

Common scopes:

| Scope                       | Access granted                                  |
| --------------------------- | ----------------------------------------------- |
| `leads.lead.read`           | List and retrieve leads                         |
| `leads.lead.write`          | Create leads                                    |
| `pipeline`                  | Read and write pipeline deals                   |
| `bookings.read`             | List resources and bookings, check availability |
| `bookings.manage`           | Create and cancel bookings                      |
| `bookings.resources.manage` | Create and update bookable resources            |

<Note>
  Scopes are assigned at key creation time. If you need additional scopes, create a new key pair with the required permissions.
</Note>

## Error responses

A missing or invalid key returns `401 Unauthorized`:

```json theme={null}
{
  "error": "Unauthorized"
}
```

A key that lacks the required scope returns `403 Forbidden`:

```json theme={null}
{
  "error": "Forbidden"
}
```

## Key rotation

Rotate a key pair without downtime by following this sequence:

<Steps>
  <Step title="Create a replacement key">
    Go to **Settings → API Keys** and create a new key pair with the same scopes.
  </Step>

  <Step title="Deploy the new key">
    Update your integration's environment variables or secrets store to use the new key.
  </Step>

  <Step title="Revoke the old key">
    Once the new key is confirmed working, click the rotate icon next to the old key or delete it outright. Revoking is immediate — any request still using the old key will receive `401 Unauthorized`.
  </Step>
</Steps>

You can also use the **rotate** action in the dashboard, which invalidates the old key after a 24-hour grace period so you have time to update your integration.
