# Authentication

> Authenticate to the Nyne.ai Person and Company APIs with an API key and secret sent as X-API-Key / X-API-Secret headers. Covers key management, product access, IP-locking, auth error codes, and key hygiene.

HTML version: https://api.nyne.ai/documentation/authentication

The Person and Company APIs (`/person/*` and `/company/*`) authenticate with an API key and secret. This is a key/secret pair, not a bearer token. Send both values as the `X-API-Key` and `X-API-Secret` headers on every request.

## API key and secret

Send the key and secret as HTTP headers (recommended):

```bash
curl -X POST https://api.nyne.ai/person/enrichment \
  -H "X-API-Key: YOUR_API_KEY" \
  -H "X-API-Secret: YOUR_API_SECRET" \
  -H "Content-Type: application/json" \
  -d '{ "email": "ada@example.com" }'
```

As a fallback, the same credentials also work as the `api_key` and `api_secret` request params (form fields or query string). Headers are preferred - query strings can land in server logs and browser history:

```bash
curl "https://api.nyne.ai/person/enrichment?api_key=YOUR_API_KEY&api_secret=YOUR_API_SECRET&email=ada@example.com"
```

**Always call from your backend.** The secret is a credential - never ship it to a browser, mobile bundle, or any client you do not control. Rate limits apply per key: 100/min, 1,000/hour.

## Get and manage keys

Create and manage keys on the API keys surface in your account. Click **Create key** to mint a key and secret pair. Keys are **workspace-scoped**: a key belongs to the workspace it was created in and inherits that workspace's subscription, credits, and rate limits. Programmatic management lives under the `/v1/api-keys/*` path family.

## Product access

A key is authorized per **product**: `enrichment`, `search`, and `newsfeed`. Each endpoint requires one of them, and authenticating successfully does not by itself grant access to every endpoint.

Calling an endpoint your key is not entitled to returns `403` with `subscription_required` and a message like *"Your subscription does not include access to the Search API."* If your credentials are correct but one endpoint keeps returning that code while others work, the key is missing that product - the fix is to add the product to your plan, not to rotate the key.

## IP-locking (optional)

A key can optionally be locked to an **allow-list of IPs**. Each entry can be an exact IP, a CIDR range (v4 or v6), or an IPv4 wildcard like `203.0.113.*`. Requests from any other address are rejected with `403 ip_not_allowed`. Allow-list the public address that actually originates the API request.

Locking is off until you add entries, and you enable and manage the allow-list from the API keys surface. A key locked with an **empty** allow-list **fails closed**: it blocks every request. Allow-list the address your backend calls from, not your browser's IP.

## Auth error codes

When authentication or access fails, `success` is `false` and the detail moves under `error`. Branch on the stable `error.code`, not the human-readable `message`:

```json
{
  "success": false,
  "error": {
    "code": "invalid_credentials",
    "message": "The API key or secret is invalid."
  },
  "timestamp": "2026-05-28T18:04:45"
}
```

Each `error.code` maps to an HTTP status you can also switch on:

| error.code | HTTP | Meaning |
| --- | --- | --- |
| missing_credentials | 401 | No key or secret was sent. |
| invalid_credentials | 401 | The key or secret is wrong or the key is disabled. |
| api_key_expired | 401 | The key is past its expiry date. |
| subscription_inactive | 403 | The subscription behind the key is not active. |
| subscription_required | 403 | The key or plan does not include this product. |
| ip_not_allowed | 403 | The request IP is not in the key's allow-list. |
| access_denied | 403 | The request does not belong to this key. |
| service_unavailable | 503 | Transient backend outage. Retry - do not rotate keys. |

**Retry vs fix.** `503 service_unavailable` is transient - back off and retry the same request; do not rotate keys. A `401` means the credentials are wrong or expired, so fix or reissue them. A `403` means the credentials are valid but access is not granted - add the product, adjust the IP allow-list, or check the subscription. Retrying a 401 or 403 unchanged will keep failing.

## Key hygiene and rotation

The secret is stored **hashed** and shown in full **only once**, at creation. Copy it immediately into your secret manager or environment - it cannot be retrieved again, only replaced. Treat the secret like a password: never commit it, log it, or embed it in client code.

To rotate a key with no downtime: create a new key, roll your traffic over to it, confirm the old key is idle, then delete the old key. If a secret is ever exposed, delete that key right away and issue a new one.

**MCP authenticates differently.** The Nyne.ai MCP server (`mcp__nyne__*` tools, hosted at `/mcp`) authenticates over OAuth (well-known metadata plus JWKS), separate from this data-API key and secret model. See the [MCP Quick Start](https://api.nyne.ai/documentation/mcp/quickstart.md) for that flow.

## What's next

- [Rate limits](https://api.nyne.ai/documentation/rate-limits.md) - per-key request ceilings and how to handle 429 responses.
- [Credits](https://api.nyne.ai/documentation/credits.md) - how usage is metered and when a request consumes a credit.
- [Your first request](https://api.nyne.ai/documentation/first-request.md) - the submit-and-poll pattern every data endpoint shares.
- [MCP Quick Start](https://api.nyne.ai/documentation/mcp/quickstart.md) - wire Nyne.ai into Claude, ChatGPT, Cursor, and other AI clients over OAuth.
