> ## Documentation Index
> Fetch the complete documentation index at: https://nova.dweet.com/docs/llms.txt
> Use this file to discover all available pages before exploring further.

# Authentication

> API keys, tenant headers, and security

## API keys

Your API key determines the environment. Use the same base URL for both:

| Key Format         | Environment |
| ------------------ | ----------- |
| `sk_test_<random>` | Sandbox     |
| `sk_live_<random>` | Production  |

Both environments are billed per score at the same rate.

## Required headers

```bash theme={null}
Authorization: Bearer <API_KEY>
X-Tenant-Id: <tenantExternalId>
Content-Type: application/json  # for POST and PATCH
```

<Note>
  `X-Tenant-Id` is required for tenant-scoped requests. Tenant deletion, deletion status, rate-limit status, and analytics endpoints are scoped by your API key and environment instead.
</Note>

### Example request

<CodeGroup>
  ```typescript TypeScript SDK theme={null}
  import { Nova } from '@nova-sdk/api';

  const nova = new Nova({
    apiKey: process.env.NOVA_API_KEY!,
    tenantId: 'acme-corp',
  });

  const result = await nova.jobs.criteria.generate.create({
    jobId: 'job-123',
    body: {
      jobContext: {
        jobTitle: 'Senior Engineer',
        companyName: 'Acme Corp',
        jobDescription: 'We are looking for a Senior Engineer with 5+ years experience...',
      },
    },
  });
  ```

  ```bash cURL theme={null}
  curl -X POST https://embed.nova.dweet.com/v1/jobs/job-123/criteria/generate \
    -H "Authorization: Bearer sk_live_abc123..." \
    -H "X-Tenant-Id: acme-corp" \
    -H "Content-Type: application/json" \
    -d '{
      "jobContext": {
        "jobTitle": "Senior Engineer",
        "companyName": "Acme Corp",
        "jobDescription": "We are looking for a Senior Engineer with 5+ years experience..."
      }
    }'
  ```
</CodeGroup>

## Tenant header

The `X-Tenant-Id` header identifies which tenant the request is for. Tenants are created automatically on first tenant-scoped request.

Use a **stable identifier**: your internal customer/account ID, a UUID, or a unique slug.

## Key rotation

<Steps>
  <Step title="Create new key">
    Generate a new key in the Embed Portal. Your old key stays active.
  </Step>

  <Step title="Deploy">
    Update your systems to use the new key.
  </Step>

  <Step title="Revoke old key">
    Once all systems are updated, revoke the old key.
  </Step>
</Steps>

<Warning>
  Revoking a key returns `401 Unauthorized` for new requests but doesn't affect scoring jobs already accepted.
</Warning>

## Error responses

| Status | Code           | Description                            |
| ------ | -------------- | -------------------------------------- |
| 401    | `UNAUTHORIZED` | Missing or invalid API key             |
| 403    | `FORBIDDEN`    | Valid key but insufficient permissions |

```json Example 401 Response theme={null}
{
  "type": "https://docs.nova.dweet.com/embed-api/errors#unauthorized",
  "code": "UNAUTHORIZED",
  "status": 401,
  "message": "Invalid or missing API key",
  "retryable": false,
  "traceId": "5c2f4f5b2c0a4ce0b6a31a1a18f8e9a1"
}
```

## API versioning

Current version: `v1`. URL-based versioning.

| Breaking (requires new version) | Non-breaking (same version)    |
| ------------------------------- | ------------------------------ |
| Removing or renaming fields     | Adding new optional fields     |
| Changing field types            | Adding new endpoints           |
| Changing error codes            | Adding new optional parameters |
| Changing endpoint behavior      | Adding new enum values         |

No breaking changes to v1. If v2 is ever needed, 12+ months notice before v1 retirement.
