# API Authentication

Source: https://docs.havilo.ai/reference/authentication

> **Workspace API Keys**
>
> API keys provide secure programmatic access to your workspace data. Each key is scoped to a single
> workspace and owned by a user for tracking purposes, but can be used by any service or application
> that has access to it.

## Quick Example

Here's how to make an authenticated API request:

```typescript
const response = await fetch('https://havilo.ai/api/endpoint', {
  method: 'GET',
  headers: {
    Authorization: 'Bearer sk_live_abcdefgh.ijklmnopqrstuvwxyz...',
  },
})

const result = await response.json()
```

***

## Creating API Keys

### Requirements

* **Workspace Access**: Must be a member of the workspace with the `MANAGE_API_KEYS` Permission.
* **Feature Enablement**: Requires `API_ACCESS` feature enabled for your workspace plan.

### Steps

1. Navigate to your workspace settings
2. Go to **Advanced** > **API Keys** section
3. Click **Create API Key**
4. Provide a name and optional description
5. Click **Create** and copy your key immediately

> **Save Your Key Immediately**
>
> The full API key is only shown **once** during creation. If you lose it, you'll need to create a
> new key. Store it securely in a password manager or environment variables.

***

## Authentication

### API Key Format

API keys follow this format:

```
sk_{environment}_{prefix}.{secret}
```

* **`sk`**: Prefix indicating "secret key"
* **`environment`**: Either `live` (production) or `test` (development)
* **`prefix`**: 8 random characters (visible in UI for identification)
* **`secret`**: \~35 random characters (never stored on our servers)

### Using API Keys

Include your API key in the `Authorization` header using the `Bearer` scheme:

```
Authorization: Bearer sk_live_abcdefgh.ijklmnopqrstuvwxyz...
```

### Testing Your API Key

Use the `/api/auth/test` endpoint to verify your API key is valid and working correctly:

```bash
curl -X GET "https://havilo.ai/api/auth/test" \
  -H "Authorization: Bearer sk_live_abcdefgh.ijklmnopqrstuvwxyz..."
```

**Success Response (200):**

```json
{
  "authenticated": true,
  "timestamp": "2025-01-16T10:30:00.000Z"
}
```

***

## Error Responses

> **AI Engine API uses a structured error envelope**
>
> Routes under `/api/v1/*` (the [AI Engine API](https://docs.havilo.ai/reference/ai-engine.md)) return errors as
> `{ error: { code, type, message, doc_url, request_id, ... } }` rather than the flat-string format
> shown below. This page documents the legacy authentication endpoints.

### 401 Unauthorized

```json
{
  "error": "Invalid or expired API key"
}
```

**Causes:**

* API key doesn't exist
* Key has been revoked
* Key has expired
* Key format is invalid

### 403 Forbidden

```json
{
  "error": "API access is not enabled for this workspace"
}
```

**Causes:**

* Workspace doesn't have the `API_ACCESS` feature enabled
* Upgrade your workspace plan to enable API access (or contact support)

### 403 Forbidden - Wrong Key Type

```json
{
  "error": "This endpoint requires a workspace-scoped API key"
}
```

**Causes:**

* System-level key used where workspace key is required

### 429 Rate Limited

```json
{
  "error": "Rate limit exceeded"
}
```

**Causes:**

* Too many requests in a short time period
* Wait before retrying or contact support to increase limits

***

## Code Examples

**TypeScript**

```typescript
// Store your API key securely (e.g., environment variables)
const API_KEY = process.env.HAVILO_API_KEY

async function makeAuthenticatedRequest(endpoint: string) {
  const response = await fetch(`https://havilo.ai${endpoint}`, {
    method: 'GET',
    headers: {
      'Authorization': `Bearer ${API_KEY}`,
      'Content-Type': 'application/json',
    },
  })

  if (response.status === 401) {
    throw new Error('Invalid API key')
  }

  if (response.status === 403) {
    const error = await response.json()
    throw new Error(error.error)
  }

  if (response.status === 429) {
    throw new Error('Rate limit exceeded - please retry later')
  }

  if (!response.ok) {
    throw new Error(`API error: ${response.statusText}`)
  }

  return await response.json()
}

// Usage
try {
  const result = await makeAuthenticatedRequest('/api/endpoint')
  console.log('API Status:', result.status)
} catch (error) {
  console.error('Request failed:', error.message)
}
```

**Python**

```python
import os
import requests
from typing import Dict, Any

# Load API key from environment
API_KEY = os.getenv('HAVILO_API_KEY')

def make_authenticated_request(endpoint: str) -> Dict[str, Any]:
    """
    Make an authenticated API request.

    Args:
        endpoint: API endpoint path (e.g., '/api/v1/runs/<run_id>')

    Returns:
        Response JSON data

    Raises:
        requests.exceptions.HTTPError: For API errors
    """
    headers = {
        'Authorization': f'Bearer {API_KEY}',
        'Content-Type': 'application/json',
    }

    response = requests.get(
        f'https://havilo.ai{endpoint}',
        headers=headers
    )

    # Handle specific error cases
    if response.status_code == 401:
        raise Exception('Invalid API key')
    elif response.status_code == 403:
        error_data = response.json()
        raise Exception(error_data.get('error', 'Forbidden'))
    elif response.status_code == 429:
        raise Exception('Rate limit exceeded - please retry later')

    response.raise_for_status()
    return response.json()

# Usage
try:
    run = make_authenticated_request('/api/v1/runs/<run_id>')
    print('Run Status:', run['status'])
except Exception as error:
    print('Request failed:', str(error))
```

**cURL**

```bash
# Set your API key as an environment variable
export HAVILO_API_KEY="sk_live_abcdefgh.ijklmnopqrstuvwxyz..."

# Make authenticated request
curl -X GET "https://havilo.ai/api/endpoint" \
  -H "Authorization: Bearer $HAVILO_API_KEY" \
  -H "Content-Type: application/json"

# Example with error handling
response=$(curl -s -w "\n%{http_code}" \
  -H "Authorization: Bearer $HAVILO_API_KEY" \
  "https://havilo.ai/api/endpoint")

http_code=$(echo "$response" | tail -n1)
body=$(echo "$response" | head -n-1)

if [ "$http_code" -eq 200 ]; then
  echo "Success: $body"
elif [ "$http_code" -eq 401 ]; then
  echo "Error: Invalid API key"
elif [ "$http_code" -eq 403 ]; then
  echo "Error: Forbidden - $body"
elif [ "$http_code" -eq 429 ]; then
  echo "Error: Rate limited"
else
  echo "Error: HTTP $http_code - $body"
fi
```

***

## Best Practices

> **Never commit API keys to version control**
>
> API keys are passwords. Anyone with your key can access your workspace data until the key is
> rotated.

**Store keys securely.** Use environment variables or a secrets manager. Never hardcode a key in source files, configuration committed to git, or client-side bundles.

```typescript
const apiKey = process.env.HAVILO_API_KEY
// or
const apiKey = await secretsManager.getSecret('havilo-api-key')
```

**Rotate keys regularly** — and immediately on compromise. Create a new key, update consumers, then revoke the old one.

**Name keys by purpose.** Distinguish "Production CI/CD pipeline" from "Local development" so audit logs and the **Last Used** timestamp are actionable.

**Monitor usage.** Check the **Last Used** timestamp to spot dormant keys (safe to revoke) and unexpected activity (potential compromise).

**Use test keys for development.** `sk_test_*` keys are scoped for non-production use; `sk_live_*` keys touch production data.

***

## System-Level API Keys

> **Advanced: System Administrator Access**
>
> System-level API keys can access non-workspace-scoped resources and bypass workspace-specific permissions. These keys can only be issued by system administrators and should be used sparingly for infrastructure monitoring.
>
> To request a system-level key, contact your system administrator.

***

## Key Ownership

API keys are **owned by the user who created them** (for audit trail purposes), but can be **used by anyone** who has access to the key value.

**What this means:**

* The "owner" is just for tracking who created the key
* Anyone with the key value can make API requests
* Revoked keys stop working for everyone
* Audit logs show the key used, not the requester

**Example:**

```
Alice creates an API key → Alice is the owner
Alice shares the key with Bob → Bob can use it
The key is used → Audit log shows "API key: Production CI/CD (owned by Alice)"
Alice revokes the key → Both Alice and Bob lose access
```

***

## Need Help?

If you encounter issues or have questions:

* Verify your workspace has the `API_ACCESS` feature enabled
* Check that your API key hasn't expired or been revoked
* Ensure you're using the correct `Authorization` header format
* Review audit logs to see recent API key activity
