# Partner Authentication
> Source: /api-reference/partner/authentication
> How partner integrations authenticate against the Journey Partner API
> Status: PREVIEW
> Note: Partly live. `X-API-Key` with a Journey `jny_` key works today; WorkOS organization keys and `Authorization: Bearer <key>` are coming soon and are marked inline.

Partner integrations authenticate with a **secret API key scoped to your
WorkOS organization**. The key identifies your organization, carries the
`partner:*` scopes your integration was granted, and is subject to the same
venue reach as a signed-in teammate.

<Warning>
  **Read this before copying an example.** Part of the model below is still
  shipping. Anything marked **Coming soon** will fail today. What works right
  now is a Journey `jny_` key sent as `X-API-Key`.
</Warning>

## What works today [#what-works-today]

<CodeGroup>

```bash cURL
curl "https://api-prd.cf.journey.com/v1/partner/scope" \
  -H "X-API-Key: jny_xxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxx"
```

```typescript TypeScript
const res = await fetch('https://api-prd.cf.journey.com/v1/partner/scope', {
  headers: { 'X-API-Key': process.env.JOURNEY_PARTNER_API_KEY! },
});
```

</CodeGroup>

The key must have `authLevel: "partner"` or `authLevel: "admin"`. A `service`
key is rejected with `403` on `/partner` routes, even though it works on other
surfaces.

Call `GET /v1/partner/scope` first: it tells you which organization you are in
and which venues you can reach, which is what should drive your navigation.

## Coming soon [#coming-soon]

<Note>
  The two items below are the agreed target. They are not live until the Core
  guard ships. Build against `X-API-Key` and a Journey key today; switching
  later is a one-line change.
</Note>

### WorkOS organization keys [#workos-organization-keys]

Keys will be minted through **WorkOS API Keys**, owned by your WorkOS
organization, and created in HXP under **Settings → Developer API**. The secret
is shown once at creation.

Core will validate each key against WorkOS, map `owner.id` to your Journey
organization, and translate the key's permissions into the `partner:*` scopes
below. This is the same mechanism HXP already uses for its external API.

### `Authorization: Bearer <key>` [#authorization-bearer-key]

Once organization keys ship, the key is accepted in either header, and
`Authorization: Bearer` becomes the documented primary form:

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

```http
X-API-Key: <key>
```

<Warning>
  Send **one** of them. If both are present and carry different credentials the
  API responds `400` with `code: "ambiguous_credentials"`.
</Warning>

Today, `Authorization: Bearer` is interpreted as a **WorkOS operator access
token**, not an API key — which is why sending a `jny_` key there returns
`401`.

## Operator tokens [#operator-tokens]

Surfaces where a person is signed in to HXP pass that operator's WorkOS access
token as `Authorization: Bearer <token>`. Permissions come from the
`app:hxp:permissions` claim and the organization from `app:hxp:org_id`. This
path is live and unchanged.

## Scopes [#scopes]

| Area | Scopes |
|---|---|
| Loyalty programs and tiers | `partner:loyalty:programs:read`, `partner:loyalty:programs:write` |
| Benefits and entitlements | `partner:loyalty:benefits:read`, `partner:loyalty:benefits:write` |
| Arrivals | `partner:arrivals:read`, `partner:arrivals:write` |
| Fulfillments | `partner:fulfillments:read`, `partner:fulfillments:write` |
| Reservations | `partner:reservations:read` |
| Guests and members | `partner:guests:read` |
| Guest PII (invites, Windfall) | `partner:guests:read` **and** `partner:guests:pii:read` |
| Teammate venue reach | `partner:access:read`, `partner:access:write` |
| Theme kits | `partner:theme-kits:read`, `partner:theme-kits:manage` |
| Custom domains | `partner:custom-domains:read`, `partner:custom-domains:manage` |
| Partner pages | `partner:pages:read`, `partner:pages:manage` |
| Point multipliers | `partner-details:read` |
| Offer code pools | `offers:codes:read`, `offers:codes:write` |

Holding a scope says what you may *do*, never *which rows* you may touch — that
is organization scope plus venue reach. Each endpoint in the
[API explorer](/api-reference/partner/explorer/scope/get-scope) shows its
required scopes as pills, joined by `and` / `or` where more than one applies.

## Environments [#environments]

| Environment | Base URL | Credentials |
|---|---|---|
| Production | `https://api-prd.cf.journey.com/v1` | Production WorkOS organization |
| Sandbox (staging) | `https://api-stg.cf.journey.com/v1` | WorkOS **staging** organization |

Keys never cross environments. Build and test against staging, then mint a
separate production key.

## Rotation and revocation [#rotation-and-revocation]

1. Create the replacement key.
2. Deploy it.
3. Revoke the old key.

<Warning>
  Revocation can take up to **60 seconds** to take effect across all instances.
  Plan rotations so both keys are briefly valid, and do not treat an immediate
  `200` on a revoked key as a failure of the revocation.
</Warning>

## Errors [#errors]

Every failure uses the standard envelope:

```json
{
  "type": "authentication_error",
  "timestamp": "2026-10-09T18:22:41.218Z",
  "code": "invalid_api_key",
  "message": "The supplied credential is not valid.",
  "details": {
    "reason": "Key was revoked."
  }
}
```

| Status | When |
|---|---|
| 400 | `ambiguous_credentials` — `Authorization` and `X-API-Key` disagreed |
| 401 | Missing, malformed, expired or revoked credential |
| 403 | Valid credential, but missing scope, wrong `authLevel`, or no organization |
| 404 | Resource is outside your organization or venue reach |
| 429 | Rate limited |

Read `details.reason` when debugging; `message` is for humans and may change.

## Storing keys [#storing-keys]

<Warning>
  A partner key grants access to guest data for your whole organization. Keep it
  server-side only — never in a browser, mobile app, or public repository.
</Warning>

```typescript
// ✅ server-side, from the environment
const key = process.env.JOURNEY_PARTNER_API_KEY;

// ❌ never
const key = 'jny_live_abc123...';
```
