# Authentication

Learn how to securely authenticate with the Freddy API using API keys and manage your authentication workflow.

## API Key Authentication

Freddy uses API key authentication for all requests. Every organization API key acts either as one person in your organization — a **Classic** key — or as the organization itself — a **Scoped** key. A Classic key can do what that person can do, within the limits set on the key. A Scoped key reaches only the areas — and, if you choose, the individual stores, connectors, assistants or spaces — it was granted and cannot use operations that need a person. The `acts_as` field on every key says which: `user` or `organization`. To pick one, see [Choosing a key type](/docs/documentation/guides/choosing-a-key-type).

### Getting Your API Key

1. Sign in to [Freddy](https://chat.aitronos.com) and open **API** in the sidebar.
2. Under **API keys**, click **New key**. The button shows only if you may create API keys; if it is missing, ask an admin of your organization.
3. **Type** — enter a **Name**, then choose **Classic** (*acts as a person*) or **Scoped** (*acts as the organization*). To decide, see [Choosing a key type](/docs/documentation/guides/choosing-a-key-type).
4. **Access** — for a Scoped key, pick a **Preset**: **Website chat**, **Knowledge sync**, **Read-only reporting**, **Full content access**, or **Custom** to choose each area and its level yourself. For a Classic key, choose who it acts as, **Me (*your name*)** or **Another member…** (they must approve before the key works; shown only if you may manage API keys), then **Everything this person can do (except admin)** or **Only selected areas**.
5. **Limits** — choose when the key **Expires**: a Scoped key starts at **No expiry**, a Classic key at **In 90 days**, and a Classic key expires after at most one year. Optionally set a **Monthly spending limit**, **Allowed IP ranges** and a **Rate limit**; each starts off.
6. **Review** — check the summary, then click **Create key**.
7. Copy the secret from **Your new API key** before you close it: it is shown only once and cannot be shown again.


The keys that act as you, and requests from colleagues for a key to act as you, are under **Settings** → **API keys**.

### API Key Format

All Freddy API keys follow this format:


```
ak_1234567890abcdef1234567890abcdef12345678
```

- Prefix: `ak_` (identifies it as an API key)
- Length: 42 characters total
- Characters: Alphanumeric (a-z, A-Z, 0-9)


## What an API key acts as

Every key returned by the organization API key endpoints (`/v1/organizations/{org_id}/api-keys`) and by `GET /v1/users/me/api-keys` reports `acts_as`. The service-account key endpoints (`/v1/organizations/{organization_id}/service-accounts/{service_account_id}/api-keys`) do not return it.

| Kind | `acts_as` | `acting_user` | What it does |
|  --- | --- | --- | --- |
| Classic | `user` | The person: `id`, `name`, `email` | Acts as that person, with their live permissions, within the key's scopes, in each granted organization where they are an active member. Actions show in the audit log as the person, with the key named. |
| Scoped | `organization` | `null` | Acts as the organization; reaches what its scopes allow. |
| Service account | `organization` | `null` | Acts as the organization through the service account's capabilities. |


Existing keys are Scoped. Nothing about what they reach has changed.

Scoped keys cannot run governance operations, such as listing the organization's API keys or reading its audit log. See [Refusals by operation class](/docs/api-reference/errors/api-key-scope-forbidden#refusals-by-operation-class).

A Classic key that waits for its person to accept reports `paused_reason: "consent_pending"` and a `pending_consent`; until then it reads `acts_as: "organization"` and `acting_user: null` if it was created for that person.

Each of these keys also reports `grant_preset`. Its value is `legacy_full_content`: full content access, as every key had before key types were introduced. See [Retrieve an API key](/docs/api-reference/api-keys/retrieve) for the full key object.

### Operations that need a person

Some operations belong to a person rather than to the organization — for example pinning an assistant (`POST /v1/assistants/{assistant_id}/pin?organization_id=…`) or creating an organization (`POST /v1/organizations`). A key that acts as the organization receives `403 API_KEY_PERSON_REQUIRED` for these: the key is valid, but the operation needs a person. Use a Classic key, or a signed-in user's access token (`Authorization: Bearer …`, see [Complete Login Flow](/docs/documentation/auth/login-flow)). To turn an existing Scoped key into a Classic key that acts as you, keeping its secret, use [Act as me](/docs/api-reference/api-keys/act-as-me).

A key whose scopes do not cover the operation is refused by its scopes first, with `403 API_KEY_SCOPE_FORBIDDEN`.


```json
{
  "success": false,
  "error": {
    "code": "API_KEY_PERSON_REQUIRED",
    "message": "This API key acts as the organization, not as a person, and this operation needs a person.",
    "system_message": "This API key acts as the organization, not as a person, and this operation needs a person.",
    "type": "authorization_error",
    "status": 403,
    "details": {
      "reason": "api_key_person_required",
      "acts_as": "organization",
      "hint": "Use a key that acts as a person, or a signed-in user's access token."
    },
    "trace_id": "eefa481f-2c3d-42eb-ab2d-66168c40d244",
    "timestamp": "2026-09-30T14:44:45.368903Z"
  }
}
```

See [API_KEY_PERSON_REQUIRED](/docs/api-reference/errors/api-key-person-required) for the causes and the fix.

Before keys reported `acts_as`, these operations answered an organization API key with `401 AUTHENTICATION_REQUIRED` — on some operations with `details.reason: "auth_context_missing_user_id"`. If your integration handles that 401, handle the 403 too.

Service-account keys are unchanged: on these operations they still answer `401 AUTHENTICATION_REQUIRED`.

### When a Classic key stops working

If its person leaves the organization or withdraws the key, it pauses and answers [`403 API_KEY_ACTING_USER_INACTIVE`](/docs/api-reference/errors/api-key-acting-user-inactive). An admin can put another member behind it (that member must accept), convert it to a Scoped key, or delete it. A key that waits for its person to accept answers [`403 API_KEY_CONSENT_PENDING`](/docs/api-reference/errors/api-key-consent-pending).

Unpausing does not reopen either one. See [Convert an API key to Scoped](/docs/api-reference/api-keys/convert-to-scoped) and [Ask a member to stand behind an API key](/docs/api-reference/api-keys/create-consent-request).

## Using Your API Key

Pass your API key in the `X-API-Key` header on every request.

### Python SDK (Recommended)


```python
from aitronos import Aitronos

client = Aitronos(api_key="ak_your_api_key_here")

result = client.responses.create_response(
    organization_id="org_your_org_id",
    model="gpt-4o",
    inputs=[{"role": "user", "content": "Hello!"}],
)
print(result.response[0]["text"])
```

Install: `pip install aitronos-sdk`

### cURL


```bash
curl -X POST "https://api.aitronos.com/v1/model/response" \
  -H "X-API-Key: ak_your_api_key_here" \
  -H "Content-Type: application/json" \
  -d '{
    "organization_id": "org_your_org_id",
    "model": "gpt-4o",
    "inputs": [{"role": "user", "content": "Hello!"}]
  }'
```

### Python


```python
import os
import requests

api_key = os.environ["FREDDY_API_KEY"]
headers = {
    "X-API-Key": api_key,
    "Content-Type": "application/json",
}

response = requests.post(
    "https://api.aitronos.com/v1/model/response",
    headers=headers,
    json={
        "organization_id": "org_your_org_id",
        "model": "gpt-4o",
        "inputs": [{"role": "user", "content": "Hello!"}],
    },
)
data = response.json()
print(data["response"][0]["text"])
```

### JavaScript


```javascript
const apiKey = process.env.FREDDY_API_KEY;

const response = await fetch("https://api.aitronos.com/v1/model/response", {
  method: "POST",
  headers: {
    "X-API-Key": apiKey,
    "Content-Type": "application/json",
  },
  body: JSON.stringify({
    organization_id: "org_your_org_id",
    model: "gpt-4o",
    inputs: [{ role: "user", content: "Hello!" }],
  }),
});
const data = await response.json();
console.log(data.response[0].text);
```

## Security Best Practices

### 1. Keep Your API Keys Secret

**DO:**

- Store API keys in environment variables
- Use secure key management systems
- Rotate keys regularly
- Use different keys for different environments
- Restrict server keys to your servers' IP ranges (`allowed_ip_cidrs`)


**DON'T:**

- Commit API keys to version control
- Share keys in chat or email
- Use production keys in development
- Hardcode keys in your application


### 2. Environment Variables

Store your API key securely:


```bash
# .env file
FREDDY_API_KEY=ak_your_api_key_here
```


```python
# Python
import os

api_key = os.getenv("FREDDY_API_KEY")
```


```javascript
// Node.js
const apiKey = process.env.FREDDY_API_KEY;
```

### 3. Key Rotation

Regularly rotate your API keys:

1. **Create a new key** in Freddy
2. **Update your applications** to use the new key
3. **Test thoroughly** in staging environment
4. **Deploy to production**
5. **Delete the old key** after confirming everything works


You can also [rotate a key](/docs/api-reference/api-keys/rotate) in one call, which issues a new secret and retires the old one. Rotating a key keeps every organization it was granted, its service account and its assistant binding.

## Error Handling

### Common Authentication Errors

#### 401 Unauthorized


```json
{
  "success": false,
  "error": {
    "code": "INVALID_API_KEY",
    "message": "The API key provided is invalid or has expired.",
    "system_message": "API key validation failed",
    "type": "client_error",
    "status": 401,
    "details": {},
    "trace_id": "abc-123-def",
    "timestamp": "2025-12-22T15:30:00Z"
  }
}
```

**Causes:**

- Missing `X-API-Key` header
- Invalid or expired API key
- Malformed API key


#### 403 Forbidden


```json
{
  "success": false,
  "error": {
    "code": "INSUFFICIENT_PERMISSIONS",
    "message": "You don't have permission to access this resource.",
    "system_message": "Insufficient permissions for this operation",
    "type": "client_error",
    "status": 403,
    "details": {},
    "trace_id": "abc-123-def",
    "timestamp": "2025-12-22T15:30:00Z"
  }
}
```

**Causes:**

- API key doesn't have required permissions
- Account limitations or restrictions
- Resource access denied


#### 403 API_KEY_PERSON_REQUIRED


```json
{
  "success": false,
  "error": {
    "code": "API_KEY_PERSON_REQUIRED",
    "message": "This API key acts as the organization, not as a person, and this operation needs a person.",
    "system_message": "This API key acts as the organization, not as a person, and this operation needs a person.",
    "type": "authorization_error",
    "status": 403,
    "details": {
      "reason": "api_key_person_required",
      "acts_as": "organization",
      "hint": "Use a key that acts as a person, or a signed-in user's access token."
    },
    "trace_id": "eefa481f-2c3d-42eb-ab2d-66168c40d244",
    "timestamp": "2026-09-30T14:44:45.368903Z"
  }
}
```

The key is valid; the operation needs a person. Use a Classic key or a signed-in user's access token. See [What an API key acts as](#what-an-api-key-acts-as).

#### 403 API_KEY_ACTING_USER_INACTIVE


```json
{
  "success": false,
  "error": {
    "code": "API_KEY_ACTING_USER_INACTIVE",
    "message": "The person this API key acts as can no longer use it here. An admin can put another member behind it, convert it, or delete it.",
    "system_message": "The person this API key acts as can no longer use it",
    "type": "authorization_error",
    "status": 403,
    "details": {
      "reason": "person_withdrew",
      "acts_as": "user",
      "key_id": "ak_c2d6d0f5f26d4b60812889bd1ab2b13e"
    },
    "trace_id": "c3399616-6084-4568-80f4-22c1afd2a741",
    "timestamp": "2026-10-01T20:00:08.963697Z"
  }
}
```

The Classic key's person left the organization or withdrew the key (`details.reason` says which). See [API_KEY_ACTING_USER_INACTIVE](/docs/api-reference/errors/api-key-acting-user-inactive).

#### 403 API_KEY_CONSENT_PENDING


```json
{
  "success": false,
  "error": {
    "code": "API_KEY_CONSENT_PENDING",
    "message": "This API key is waiting for the person it acts as to accept. It works once they accept.",
    "system_message": "This API key is waiting for its person to accept it",
    "type": "authorization_error",
    "status": 403,
    "details": {
      "reason": "consent_pending",
      "key_id": "ak_c2d6d0f5f26d4b60812889bd1ab2b13e"
    },
    "trace_id": "f6536a19-43a9-4662-b4b0-680812b895e7",
    "timestamp": "2026-10-01T20:00:07.533385Z"
  }
}
```

The key waits for its person to accept. See [API_KEY_CONSENT_PENDING](/docs/api-reference/errors/api-key-consent-pending).

### Error Handling Code


```python
import os
import requests


def make_authenticated_request(endpoint):
    api_key = os.environ["FREDDY_API_KEY"]
    headers = {"X-API-Key": api_key}

    response = requests.get(
        f"https://api.aitronos.com/v1/{endpoint}",
        headers=headers,
    )

    if response.status_code == 401:
        print("Authentication failed. Check your API key.")
        return None
    elif response.status_code == 403:
        try:
            code = response.json().get("error", {}).get("code")
        except (ValueError, AttributeError):
            code = None  # not a Freddy error body, e.g. from a proxy
        if code == "API_KEY_PERSON_REQUIRED":
            print("This operation needs a person. Use a Classic key or an access token.")
        elif code in ("API_KEY_ACTING_USER_INACTIVE", "API_KEY_CONSENT_PENDING"):
            print("This Classic key cannot act for its person. Ask an admin.")
        else:
            print("Permission denied. Check the key's scopes.")
        return None
    elif response.status_code == 200:
        return response.json()
    else:
        print(f"Unexpected error: {response.status_code}")
        return None
```

## Rate Limiting

API keys are subject to rate limits based on your plan:

- **Standard**: 100 requests/minute
- **Premium**: 1,000 requests/minute
- **Enterprise**: Custom limits


Rate limit headers are included in responses:


```
X-RateLimit-Limit: 100
X-RateLimit-Remaining: 95
X-RateLimit-Reset: 1640995200
```

An organization API key can also carry its own per-minute cap, `rate_limit_per_minute`. See [Per-key limits](/docs/documentation/rate-limiting#per-key-limits).

## Monitoring Usage

Track your API usage in [Freddy](https://chat.aitronos.com): open **API** in the sidebar.

- **Total API Usage Cost** shows this month's cost of all keys in your organization against its monthly API limit. If you may manage billing, **Adjust Limit** changes that limit.
- **API Usage** charts this month's requests per day. Until an API key has usage this month, it draws one line: your organization's total. After that it draws a line for each of up to three keys, the ones that cost the most this month. A key's line is not that key's own daily count: it is your organization's daily total, split among those keys by each key's share of their requests this month.
- Both cards need access to your organization's billing.


Each key can also carry its own limits. See [Per-key limits](/docs/documentation/rate-limiting#per-key-limits).

## Troubleshooting

### API Key Not Working?

1. **Check the format** — Should start with `ak_`
2. **Verify in Hub** — Ensure key exists and is active
3. **Check the header** — Use `X-API-Key` as the header name
4. **Test with cURL** — Isolate the issue
5. **403 API_KEY_PERSON_REQUIRED?** The operation needs a person — see [What an API key acts as](#what-an-api-key-acts-as).
6. **403 API_KEY_ACTING_USER_INACTIVE or API_KEY_CONSENT_PENDING?** The Classic key's person is gone or has not accepted yet — see [When a Classic key stops working](#when-a-classic-key-stops-working).
7. **403 API_KEY_IP_NOT_ALLOWED or 429 API_KEY_RATE_LIMITED?** The key's own limits refused the request — see [API_KEY_IP_NOT_ALLOWED](/docs/api-reference/errors/api-key-ip-not-allowed) and [API_KEY_RATE_LIMITED](/docs/api-reference/errors/api-key-rate-limited).


### Still Having Issues?

- **[Quick Start](/docs/documentation/getting-started/quick-start)** — Basic setup guide
- **[API Reference](/docs/api-reference/introduction)** — Detailed endpoint docs
- **[Hub](https://chat.aitronos.com)** — Manage your keys
- **Support** — Contact our team


- **[Quick Start](/docs/documentation/getting-started/quick-start)** — Make your first request
- **[Code Examples](/docs/documentation/examples)** — Common patterns and use cases
- **[Error Handling](/docs/documentation/error-handling)** — Handle errors gracefully