Journey Docs
Authentication

API Keys

M2M API key authentication for service-to-service integrations

API Key Authentication

Journey API keys provide secure machine-to-machine (M2M) access for backend services, integrations, and automated workflows. The key system uses direct permissions and optional resource scopes — not role-based access.

API keys are managed through the Admin API. You need an admin account with the admin:api-keys:manage permission to create keys.

These keys do not authenticate HXP Threads, messaging event polling, or Action commands. HXP's operator MCP also requires its own audience-bound OAuth token, not a jny_ key. See HXP integration availability.

Key Format

All Journey API keys use a jny_ prefix:

jny_xxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxx

The first 8 characters (the prefix) are stored publicly and appear in logs and admin views. The full value is shown only once at creation time and cannot be retrieved again.

Auth Levels

API keys carry an authLevel that controls which surfaces they can access:

Auth LevelAccessUse Case
partnerPartner-scoped endpointsHotel partners, external integrations
adminAdmin API endpointsInternal tooling, dashboards
serviceCross-surface service accessPMS integrations, webhook delivery, automation

member auth level is not available for API keys — member authentication always uses Clerk JWT.

/v1/partner/* accepts partner and admin keys only. A service key is rejected there with 403, even though it works on other surfaces.

Using API Keys

Pass the key as a Bearer token in the Authorization header:

Authorization: Bearer jny_xxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxx

The Partner API is the exception. Routes under /v1/partner/* read the key from X-API-Key; on those routes Authorization: Bearer is parsed as a WorkOS access token, so a jny_ key sent there returns 401. Accepting the key as a Bearer token everywhere is the target behaviour — see Partner authentication.

X-API-Key: jny_xxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxx
const response = await fetch('https://api.journey.com/public/reservations/lookup', {
  method: 'POST',
  headers: {
    'Authorization': `Bearer ${process.env.JOURNEY_API_KEY}`,
    'Content-Type': 'application/json'
  },
  body: JSON.stringify({ ... })
});

Permissions

Permissions follow a domain:resource:action format. A key only has access to what its permissions explicitly grant.

Common Permissions

PermissionDescription
reservations:readRead reservation data
reservations:writeCreate and modify reservations
members:readRead member profiles
admin:api-keys:readList and view API keys
admin:api-keys:manageFull API key management

You can assign permission groups when creating a key — these expand to a flat list of permissions at creation time. Call GET /admin/api-keys/permission-groups to see available groups.

// Using permission groups
{ "permissionGroups": ["reservations-read-write"] }

// Using explicit permissions
{ "permissions": ["reservations:read", "reservations:write"] }

// Both merged and deduplicated
{ "permissions": ["members:read"], "permissionGroups": ["reservations-read-write"] }

Resource Scopes

Optionally restrict a key to specific resources:

{
  "resourceScope": {
    "orgIds": ["org_01J5XYZ"],
    "brandIds": ["brand_abc"],
    "propertyIds": ["prop_123"],
    "global": true   // no restriction
  }
}

Creating a Key

const response = await fetch('https://api.journey.com/admin/api-keys', {
  method: 'POST',
  headers: {
    'Authorization': `Bearer ${workosToken}`,
    'Content-Type': 'application/json'
  },
  body: JSON.stringify({
    name: 'PMS Cloudbeds Integration',
    serviceId: 'pms-cloudbeds',
    authLevel: 'service',
    permissionGroups: ['reservations-read-write'],
    rateLimit: 500,
    expiresAt: '2025-12-31T23:59:59Z'
  })
});

const { apiKey, ...keyRecord } = await response.json();
// apiKey: "jny_xxxx..." — store securely, shown only once

See Admin API Keys for the full management API.

Rate Limiting

Each key has a rateLimit (requests per minute, default 1000). Exceeding it returns 429:

{ "statusCode": 429, "message": "Rate limit exceeded" }

Key Lifecycle

  • Regenerate: POST /admin/api-keys/:id/regenerate — new secret, old one immediately invalidated
  • Revoke: POST /admin/api-keys/:id/revoke — permanent, cannot be undone
  • Expire: set expiresAt at creation time for automatic expiry

Security Best Practices

Never expose API keys in client-side code, browser requests, or public repositories.

// ✅ Good: Environment variables
const apiKey = process.env.JOURNEY_API_KEY;

// ❌ Bad: Hardcoded in source
const apiKey = 'jny_live_abc123...';

Grant only the permissions your service needs. Rotate keys on a schedule or immediately when potentially compromised.

Error Responses

StatusDescription
401Invalid, expired, or revoked key
403Key lacks required permission
429Key rate limit exceeded

Bruno Examples

  • bruno/Admin/API Keys/Create API Key.bru
  • bruno/Admin/API Keys/Create Service API Key.bru
  • bruno/Admin/API Keys/List API Keys.bru
  • bruno/Admin/API Keys/Revoke API Key.bru
  • bruno/Admin/API Keys/Query Usage Logs.bru

On this page