Skip to main content

POS Partner API — Authentication Contract

Supported auth path: x-partner-key​

The Trident Phase 1 integration uses a shared API key delivered via the x-partner-key HTTP header.

HeaderValueSource
x-partner-keyHMAC-compared against POS_PARTNER_API_KEY or optional POS_PARTNER_API_NEXT_KEYInfisical /pos/partner-api

All v1/pos/partners/* endpoints require this header. Missing or invalid keys return:

{ "message": "Invalid partner API key", "error": "Forbidden", "statusCode": 403 }

The generated OpenAPI document exposes this as the partnerApiKey security scheme. The Swagger UI is available at /api/docs, with machine-readable documents at /api/docs-json and /api/docs-yaml.

Key rotation​

The runtime accepts two partner-key slots:

SlotSecret env varOptional label env varDefault key ID
CurrentPOS_PARTNER_API_KEYPOS_PARTNER_API_KEY_IDcurrent
NextPOS_PARTNER_API_NEXT_KEYPOS_PARTNER_API_NEXT_KEY_IDnext

Rotation procedure:

  1. Seed POS_PARTNER_API_NEXT_KEY and POS_PARTNER_API_NEXT_KEY_ID in Infisical.
  2. Confirm both current and next keys pass the UAT smoke script.
  3. Move the staged next key into POS_PARTNER_API_KEY, update POS_PARTNER_API_KEY_ID, and remove the next slot after callers have cut over.

POS_PARTNER_ID is an optional non-secret label for the attached auth context. If it is unset, modern partner-key requests use trident and legacy-token requests use legacy-xano.

Auth context, audit logs, and metrics​

Successful auth attaches a sanitized request context for downstream handlers and audit telemetry:

FieldSourceSecret-safe rule
authModepartner-key or legacy-tokenStatic label only
keyIdconfigured key IDMust be a generation label, not a secret
partnerIdPOS_PARTNER_ID or default labelNon-secret partner label
clubCoderequest query/bodyLower-cased club code only
routePrefixroute familyStatic label only
routePathExpress path without query stringNever includes token query values
dataSourcematched or not-applicableNever stores the raw x-data-source
legacyTokenTransportquery or body on legacy-token useStatic transport label only
timestampruntime clockISO timestamp

The app records structured success and failure logs. Logs and Prometheus labels must never include raw x-partner-key, legacy token, or raw x-data-source values.

Prometheus counters are exposed through /api/admin/pos/metrics:

MetricLabels
pos_partner_auth_success_totalauth_mode, route_prefix
pos_partner_auth_failure_totalreason, route_prefix
pos_partner_auth_rate_limited_totalroute_prefix

Failure reasons are invalid_key, missing_key, data_source_mismatch, and rate_limited.

Rate limiting​

Partner auth requests pass through an app-layer fixed-window limiter before credential comparison. Defaults:

Env varDefault
POS_PARTNER_AUTH_RATE_LIMIT_MAX120
POS_PARTNER_AUTH_RATE_LIMIT_WINDOW_MS60000

The limiter buckets by route prefix and remote address only. It deliberately excludes clubCode because that value is supplied by the caller; including it would let an attacker mint fresh buckets by rotating club values. This is acceptable only under the current single-replica deployment policy. If pos-partner-api is scaled above one replica, move abuse protection to ingress or a distributed store before claiming equivalent protection.

Deprecated compatibility auth path​

The /pos/* and /transactional/* routes are retained as deprecated compatibility-only surfaces for legacy Xano-era integrations. They are not a supported path for new Trident work, UAT sign-off, smoke validation, or partner onboarding.

The compatibility routes use LegacyPartnerApiKeyGuard, which accepts two auth modes:

Mode 1 — modern key on legacy routes:

InputSourceChecked against
x-partner-key headerHTTP headerPOS_PARTNER_API_KEY or POS_PARTNER_API_NEXT_KEY
x-data-source headerHTTP headerMCA_API_DATA_SOURCE

Both must be present and valid. This allows the modern partner key to work on legacy routes when the data source matches.

Mode 2 — legacy token:

InputSourceChecked against
tokenQuery string or request bodyMCA_API_KEY
x-data-source headerHTTP headerMCA_API_DATA_SOURCE

Both must be present and valid.

Note: This is NOT Authorization: Bearer. The legacy path reads token from the query string or JSON body, not from an Authorization header.

Status: Deprecated compatibility-only. Keep the legacy token path wired until a live traffic audit proves no caller depends on /api/pos/*, bare /transactional/*, or query/body token auth. Do not expand this surface.

MCA_API_KEY also authenticates outbound calls to the legacy Xano wallet API. Removing inbound legacy-token compatibility must not remove that secret unless the outbound Xano wallet dependency has been retired too.

This legacy auth decision is separate from the active MCA v1 member read-side. libs/prisma/mca_v1_za and libs/prisma/mca_v1_uk remain valid POS dependencies for routed member/card lookup. The older libs/mca/**, @digiwedge/mca-*, and MCA GraphQL application libraries must not be used as the POS partner API integration boundary.

Unauthenticated endpoints​

Health endpoints (/api/health/ready, /api/health/live) are not guarded and require no authentication.

Gateway smoke​

A repeatable smoke test is available at tools/scripts/smoke/pos-partner-api.sh:

POS_BASE_URL=https://pos-partner-api.uat.digiwedge.com \
POS_PARTNER_KEY=<key> \
POS_PARTNER_NEXT_KEY=<optional-next-key> \
POS_PARTNER_ADMIN_KEY=<optional-admin-key> \
bash tools/scripts/smoke/pos-partner-api.sh

Smoke matrix​

TestMethodPathExpected
HealthGET/api/health/ready200, ok=true
No authPOST/api/v1/pos/partners/members/lookup403
Wrong keyPOST/api/v1/pos/partners/members/lookup403
Valid key, disabled clubPOST/api/v1/pos/partners/members/lookup200, reasonCode=club_pos_disabled
Balance, disabled clubGET/api/v1/pos/partners/balance403
Next key, disabled clubPOST/api/v1/pos/partners/members/lookup200 when POS_PARTNER_NEXT_KEY set
Auth metricsGET/api/admin/pos/metricscounter present when admin key set

Note on HTTP semantics​

Member lookup returns 200 with status=BLOCKED for disabled clubs. Balance returns 403. This is the current behavior and is documented here for contract stability. If uniform semantics are required, open a consistency review.