---
title: "Authentication"
description: "Authenticate server requests with an Affinity API key."
---

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

# Authentication

An API key identifies a backend service. It does not identify the provider who uses your platform.

## Practice and platform access

A practice key with `catalog:read` can list its available pharmacies, medication catalog and
item-specific shipping options. The key selects the practice. If you also supply `practiceId`, it
must identify that practice. Catalog prices use the practice's published price book and customer
segment.

A platform key can select an authorized practice with `practiceId` when it requests catalog
prices. The practice price book takes precedence over the platform's price book. Access and
pricing remain separate from the clinician review and signing required to send an order.

## Send the API key

Use the Bearer authentication scheme for new integrations.

```http
Authorization: Bearer <api-key>
```

The API also accepts the service key header.

```http
x-affinity-api-key: <api-key>
```

Send one authentication header. Sending both returns `400 invalid_request`.

## Select an organization

An API key uses its own organization by default.
Use `acct_...` for a platform, `prac_...` for a practice, or `pharm_...` for a pharmacy.
These are the same public IDs used in dashboard URLs. Private database IDs and retired `org_...` aliases are rejected with `400`.
For delegated webhook management, a platform sends `X-Affinity-Organization-Id` with the owner's public organization ID.
The practice or pharmacy must first grant that platform webhook access in the same mode.
See [Delegate endpoint management](/guides/webhooks/#delegate-endpoint-management).

This header does not grant permissions or change the API key's mode.
Ordinary service keys cannot use it to switch organizations on other API resources.
Practice-specific routes continue to identify the practice in their URL.

Personal credentials can select an explicitly granted organization with the same header.
Without the header, they use their anchor organization if it remains granted.

## Attribute platform requests

Affinity attributes requests to the authenticated service account as a system actor by default.
You do not need to send actor headers for autonomous work.

If a person performs a protected patient or order action, send the person's stable user ID:

```http
Affinity-Actor-Id: prescriber-user-id
Affinity-Actor-Type: user
```

The actor identifies the user in your application. The actor does not identify the API client.

If several workers share one service account and you need to distinguish them in audit records,
send both system actor headers. Do not use `system` when a person initiated the action.

```http
Affinity-Actor-Id: patient-sync-worker
Affinity-Actor-Type: system
```

Signing and submission require `orders:sign`. Select the clinician with `prescriber: { npi }`,
`prescriber: { id }`, or `prescriber: { externalId }`, or inherit the draft's prescriber.
Actor headers are optional audit metadata for this flow. Your platform still authenticates the clinician and collects explicit signing intent.
Legacy requests using `userId` require the clinician's matching `externalId` in user actor headers.

Do not send an email address, API key ID, signing credential, or patient information in these headers.
Actor IDs containing `@` return `400 invalid_request`. Use a stable opaque identifier from your application.

## Pin the API version

Send the dated version in each direct HTTP request.

```http
Affinity-Version: 2026-09-28
```

The TypeScript SDK sends its supported version automatically.
An unsupported version returns `400 UNSUPPORTED_API_VERSION`.

Without a version header, authenticated API-key requests use the version stored on the key's service account.
Keys belonging to the same service account share that default. New service accounts default to `2026-09-28`.
Existing accounts retain their stored version.

An explicit header overrides the default for that request only. It does not update the key or service account.
Two clients can use different supported versions with the same key, including concurrent requests.
The response header `X-Api-Version` identifies the selected contract.

The SDK sends its own version header, so its default takes precedence over the service-account default.
Webhook payload versions are separate from HTTP request versions.

## Handle rate limits and failures

Each API key allows 300 requests per minute. Authenticated responses include these headers:

| Header                  | Meaning                                   |
| ----------------------- | ----------------------------------------- |
| `X-RateLimit-Limit`     | Requests allowed in the current window.   |
| `X-RateLimit-Remaining` | Requests remaining in the current window. |
| `X-RateLimit-Reset`     | Window reset time as Unix seconds.        |

When a request exceeds the limit, the API returns `429` with `Retry-After` in seconds. Wait for that interval before retrying.

Preserve the request body and `Idempotency-Key` when retrying an order creation. Reusing the key with different input returns `409`.

Errors use Problem Details JSON with a stable `code`. Keep the `X-Request-Id` response header when reporting a failed request.
Input validation can return `400` before credential checks. This does not authenticate the caller or
run the operation. Requests with valid input and a missing or invalid key return `401`.

## Store the key

- Keep the key in server-side secret storage.
- Use a separate key for each service.
- Grant only the scopes that the service needs.
- Revoke a key immediately after an exposure.

Affinity shows the complete key one time. Do not copy the key into a support message.

## Choose an expiration

When creating a Test or Live key, select **Never expires** to keep the key active until you revoke
it or its account loses access. You can also select **30 days**, **90 days**, or a date.
Live expiration dates must be within 90 days. Creating a key through the API with `expiresAt: null`
has the same effect as **Never expires**.

## Rotate a key

Open API keys in the dashboard for the organization and mode that own the key. Select an active key, then select **Rotate key**.

Save the replacement secret and update your service. The dashboard allows up to 60 minutes of overlap with the previous key.
An earlier expiration still applies. Revoke the previous key after you confirm requests use the replacement.

Rotation preserves the key's permissions, IP restrictions, mode, and expiration. It does not extend access to another practice or mode.

## Keep modes separate

Patient, order, and webhook data are scoped to the API key’s mode.
Team membership and practice locations are shared between Test and Live for the same practice.

Do not replace a Test key with a Live key in the same running process. Restart the service with the Live configuration.

Source: https://docs-staging.affinityrx.com/guides/authentication/index.mdx
