POS Partner API — Authentication Contract
Supported auth path: x-partner-key
The Trident Phase 1 integration uses a single shared API key delivered via the x-partner-key HTTP header.
| Header | Value | Source |
|---|---|---|
x-partner-key | HMAC-compared against POS_PARTNER_API_KEY env var | Infisical /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}
Legacy compatibility auth path
The /pos/* and /transactional/* routes use LegacyPartnerApiKeyGuard, which accepts two auth modes:
Mode 1 — modern key on legacy routes:
| Input | Source | Checked against |
|---|---|---|
x-partner-key header | HTTP header | POS_PARTNER_API_KEY |
x-data-source header | HTTP header | MCA_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:
| Input | Source | Checked against |
|---|---|---|
token | Query string or request body | MCA_API_KEY |
x-data-source header | HTTP header | MCA_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: The legacy auth path is wired but is not the supported path for Trident Phase 1. See the auth hardening issue for the deprecation/retention decision.
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> \
bash tools/scripts/smoke/pos-partner-api.sh
Smoke matrix
| Test | Method | Path | Expected |
|---|---|---|---|
| Health | GET | /api/health/ready | 200, ok=true |
| No auth | POST | /api/v1/pos/partners/members/lookup | 403 |
| Wrong key | POST | /api/v1/pos/partners/members/lookup | 403 |
| Valid key, disabled club | POST | /api/v1/pos/partners/members/lookup | 200, reasonCode=club_pos_disabled |
| Balance, disabled club | GET | /api/v1/pos/partners/balance | 403 |
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.