Authentication

Every request to $CXH_BASE_URL/v1/oura/* MUST include an Authorization header with a CollectiveX Health partner API key.

Header format

Authorization: ApiKey zpka_<32+ alphanumeric chars>

ApiKey is the only accepted scheme. Bearer is rejected with 401.

Example:

curl -sS "$CXH_BASE_URL/v1/oura/recommendation" \
  -H "Authorization: ApiKey $CXH_API_KEY" \
  -H "Content-Type: application/json" \
  -d '...'

How keys are issued

Rotation

Standard rotation cadence is quarterly or on demand.

To request rotation:

  1. Contact support with your current tenant identifier and the reason (scheduled rotation / suspected compromise / developer churn).
  2. CollectiveX mints a new key and hands it over via the same out-of-band channel used for the original.
  3. You have a 24-hour overlap window where both the old and new keys are valid. Deploy the new key to all production callers within that window.
  4. The old key is revoked automatically at the end of the 24-hour window. CollectiveX notifies you once revocation is confirmed.

Revocation (emergency)

If you suspect a key has leaked (committed to a public repo, exposed in logs, lost laptop, departed team member with access):

  1. Contact support immediately with subject URGENT: key revocation — <tenant-id>.
  2. CollectiveX revokes the key within 15 minutes during business hours, or within 1 hour outside business hours.
  3. Revoked keys begin returning 401 Unauthorized (problem+json envelope per errors.md) within ~30 seconds of revocation.
  4. A replacement key is issued within the same support response.

There is no self-service revocation endpoint for partner keys — this is deliberate. Zuplo dashboard access is CollectiveX-side only.

Why API keys instead of OAuth 2.0

Partner traffic is server-to-server, low-cardinality (one key per partner tenant), and trust-bootstrapped through a signed Data Processing Agreement. OAuth client-credentials flow adds a token-minting round-trip with no security benefit at our scale.

For end-user-facing integrations, CollectiveX has a separate Keycloak-based OIDC flow. That path is out of scope for /v1/oura/*.

What the gateway validates

Zuplo validates:

  1. Key presence — missing or malformed Authorization header → 401. The body is Zuplo's RFC 7807 problem+json envelope (see errors.md for the actual shape).
  2. Key validity — key exists in Zuplo's key store and is not revoked → 401 on failure.
  3. Key-to-route scope — key is entitled to hit /v1/oura/*403 on mismatch.
  4. Rate limit — key is under 30 req/min → 429 on breach. See rate-limits.md.

After validation, Zuplo injects the X-Zuplo-Partner-Id header and forwards the request to the CollectiveX origin. The origin then validates:

  1. Partner consent — active consent record exists for the partner tenant → 403 on revocation. See errors.md for consent_not_provisioned vs consent_revoked.

All five checks happen before your request body is parsed. A valid request body with an invalid key never triggers clinical processing.

Key storage recommendations

Questions?

See support.md.