Authorization
Peak Gateway uses two authorization models: OAuth2 scopes for API clients (machine-to-machine) and Firebase roles for portal users (human users). Gateway tenant targeting is organization/location based.
OAuth2 Scopes (API Clients)
API clients authenticate via OAuth2 client_credentials flow with the auth service, then use Bearer tokens with scoped access.
| Scope | Description | Endpoints |
|---|---|---|
txn:process | Process scoped payment transactions on API-client surfaces | Card-not-present transaction APIs on online-txn; processing endpoints that explicitly use OAuth scope checks |
txn:sync | Sync terminal-approved card-present transactions and refunds | POST /api/v1/card-present/transactions/sync, POST /api/v1/card-present/transactions/sync/refund on card-present |
session:create | Create online checkout sessions and hosted payment forms | POST /api/v1/checkout/sessions, POST /api/v1/hosted/* on online-txn |
merchant:activate | Create or manage merchant onboarding/provisioning jobs | /api/v1/provisioning/jobs/* on merchant-onboarding |
organizations:read | Read organization and org-scoped location records | GET /api/v1/organizations/{organizationId}/locations/* on management |
organizations:write | Create organizations and create, provision, or update organization-bound locations | POST /api/v1/organizations, POST/PUT /api/v1/organizations/{organizationId}/locations/* on management |
products:read / products:write | Read / manage storefront products, prices, and promotion codes | /api/v1/products/*, /api/v1/prices/*, /api/v1/promotion-codes/* on online-txn |
coupons:read / coupons:write | Read / manage storefront coupons | /api/v1/coupons/* on online-txn |
customer:read / customer:write | Read / manage stored storefront customers | /api/v1/customers/* on online-txn |
shipping:read / shipping:write | Read / manage storefront shipping configuration | /api/v1/shipping/config on online-txn |
admin:* | Wildcard admin access, grants all scopes | All endpoints across all services |
admin:* controls scopes, not portal roles. Organization targeting is independent of scopes:
organization_idsbinds an OAuth client to specific organizations- an empty
organization_idsset is treated as a platform-admin/global client and is not available through customer-managed OAuth client APIs
Token claims use snake_case (organization_ids), while management API payloads and portal DTOs use camelCase (organizationIds) for the same fields.
An OAuth client with admin:* and organization_ids=["org_123"] can access organization-scoped management endpoints for org_123 only. Customer-managed OAuth clients cannot request admin:*, organizations:write, platform-admin ownership, global access, or unbound all-organization access.
Management authorizes OAuth tenant access from signed token claims only. It does
not call auth during request authorization to recover stored client
organization bindings. After changing a client's organization bindings, mint a
new access token before calling management; already-issued tokens without the
required organization_ids / organizationAccess claims are rejected until
they expire or are replaced.
Typical Client Configurations
| Client Type | Scopes |
|---|---|
| POS Terminal sync client | txn:sync |
| E-Commerce Integration | session:create, txn:process |
| Agent onboarding/MID management client | merchant:activate, credential-profiles:write |
| Management/support onboarding client | organizations:read, organizations:write |
| Admin Dashboard | admin:* |
Customer-Managed OAuth Clients
Merchant portal users can manage their own machine clients through the customer OAuth client surface:
| Method | Path | Purpose |
|---|---|---|
GET | /api/v1/customer-oauth-clients | List customer-owned clients for the caller's organizations |
POST | /api/v1/customer-oauth-clients | Create an organization-bound customer client |
GET | /api/v1/customer-oauth-clients/{clientId} | Get an owned client |
PUT | /api/v1/customer-oauth-clients/{clientId}/scopes | Update allowed scopes |
POST | /api/v1/customer-oauth-clients/{clientId}/secret:rotate | Rotate the client secret |
DELETE | /api/v1/customer-oauth-clients/{clientId} | Revoke the client |
Customer-managed clients are persisted in the canonical oauth_clients registry with owner_type=CUSTOMER. Their organization_ids are auto-bound to the caller's allowed organization set or to a requested subset. Reads, updates, rotations, and revocations require both owner_type=CUSTOMER and organization ownership.
Customer-created clients can request only customer-safe scopes: txn:process, txn:sync, session:create, gift_cards:read, gift_cards:write, and organizations:read.
This surface is separate from:
- platform-admin OAuth client management at
/api/v1/oauth-clients, which can manage all registry rows - auth-service admin management at
/api/v1/clients customer_identities, which links end-customer identity-provider subjects and does not store Gateway OAuth client credentials
Registering a Platform OAuth Client
- Admin Portal → OAuth Clients → Register
- Assign scopes matching the client's use case
- Bind the client to organization IDs when it should be restricted to specific customer organizations
- Note the
client_idandclient_secret - Client calls
POST /auth/oauth2/tokenwithgrant_type=client_credentialson the public gateway domain
Portal Roles (Firebase Auth)
Portal users authenticate via Firebase Auth (Google SSO or SAML). Roles are hierarchical, higher ranks include all lower rank permissions.
| Role | Rank | Description |
|---|---|---|
super_admin | 0 | Full access including SAML SSO configuration and user deletion |
admin | 1 | Full operational access across organizations, locations, users, and transactions; read settlement/reconciliation evidence and request a location-scoped manual close through the Management BFF |
location_admin | 2 | Location-scoped operator who manages assigned organization/location settings, users, transactions, and subscriptions |
location_user | 3 | Location-scoped portal user with read-heavy access to assigned organization/location data |
readonly | 4 | Read-only access to assigned organization/location data |
Settlement and reconciliation GET routes expose historical evidence. The
exception is the location-scoped Management BFF
POST /api/v1/settlements/manual-close, which is annotated
@RequireManagementPermission(REPORTS_READ), requires the portal's second
confirmation, and forwards to Processing as the pinned Management service
identity. No public settlement mutation scope exists: batch:manage is not
defined, and admin:* does not add retry, force-close, or adjudication routes.
SETTLEMENT_REPAIR_WRITE is a separate platform-only permission for the
controlled unmatched-settlement repair paths. It does not authorize manual
close, retry, force-close, or adjudication.
Fraud transaction-detail score display is a read-only projection. The
organization-scoped FRAUD_DECISIONS_READ permission gates the Management BFF;
the Processing fraud query route is internal service-to-service only. A score
is returned/displayed only for the exact organization, location, and
transaction, and only as score, status, and model version. Missing,
insufficient, unknown, malformed, or mismatched data is unavailable; raw fraud
evidence and shopper-facing exposure are not authorized.
Token model
Firebase custom claims carry direct grants only (anchors) — never an expanded, pre-computed access list:
role: platform tier (super_admin,admin), if anyorganizationAccess: explicit per-organization role entrieslocationAccess: explicit per-location role entriesagentAccess: explicit per-agent (ISO) role entries — deliberately compact; an agent's descendant subtree is never embedded in the token
Every wider scope — an organization's full location set, an agent's descendant subtree — is expanded server-side from these anchors on each request, by consulting the database. A token never carries a derived or promoted grant.
A user can hold at most 25 direct locationAccess entries. This cap keeps
the worst-case claims payload comfortably under Firebase's 1000-byte
custom-claim limit; a user needing broader reach takes an explicit
organization-level grant instead, which is a single claim entry regardless
of how many locations the organization has. Requests that would exceed the
cap are rejected at write time.
Revocation is bounded at the check interval (60 seconds by default):
whenever a grant is revoked, downgraded, or a user is deactivated, the
service rewrites custom claims and calls Firebase's refresh-token
revocation for that user. Token signatures are verified locally on every
request; the remote revocation lookup (tokensValidAfterTime + disabled
flag) runs at most once per user per interval and is cached in between —
so a revoked or disabled principal keeps working for at most one interval,
the same bound as the server-side grant and subtree caches, without a
Firebase round-trip on every request.
Authorization Annotations
Services enforce authorization via two primary authorization patterns:
@RequireScope(...): Used on controllers that serve API clients, including online-txn payment/session APIs, merchant-onboarding provisioning APIs, processing OAuth exception paths, and card-present terminal sync.@RequireManagementPermission(...): Used on management controllers for portal users and backed byManagementAuthorizer
OAuth clients are not promoted into Firebase-style portal roles. Merchant targeting comes from OAuth token merchant claims, while management permissions remain role-based unless an endpoint explicitly opts into OAuth scope checks.
The scope checks live in libs/security, while management permission enforcement lives under services/management/.../security.
Endpoint Authorization Matrix
Auth Service
| Method | Path | Auth |
|---|---|---|
GET | /health | Public |
POST | /api/v1/clients | Admin (super_admin, admin) |
GET | /api/v1/clients | Admin (super_admin, admin) |
POST | /api/v1/clients/{clientId}/secret:rotate | Admin (super_admin, admin) |
DELETE | /api/v1/clients/{clientId} | Admin (super_admin, admin) |
GET | /api/v1/me | Authenticated |
Processing Service
Most processing controllers are role-protected internal/portal surfaces. OAuth-exposed processing paths are listed only when the controller declares @RequireScope(...).
| Method | Path | Auth |
|---|---|---|
POST | /api/v1/gift-cards/* | gift_cards:write / gift_cards:read by operation |
GET | /api/v1/gift-cards/* | gift_cards:read |
GET | /api/v1/settlements, /api/v1/settlements/batches, /api/v1/settlements/batches/{gatewayBatchId}, /api/v1/settlements/{batchId}, /api/v1/settlements/{batchId}/transactions | @RequireScope("reports:export"); tenant/location checks apply, and Management proxy calls use the configured internal caller |
POST | /api/v1/internal/processor-settlement/manual-close | ROLE_INTERNAL_SERVICE; configured Management caller allowlist; verified forwarded portal operator; Idempotency-Key must equal the body key |
GET | /api/v1/internal/processor-settlement/unmatched, /api/v1/internal/processor-settlement/unmatched/{rowId} | ROLE_INTERNAL_SERVICE; configured unmatched caller; explicit feature gate |
POST | /api/v1/internal/processor-settlement/unmatched/{rowId}/convert, /api/v1/internal/processor-settlement/unmatched/{rowId}/mark-external | ROLE_INTERNAL_SERVICE; configured unmatched caller; explicit feature gate; public BFF additionally requires platform-only SETTLEMENT_REPAIR_WRITE |
GET | /api/v1/internal/organizations/{organizationId}/fraud-decisions* | ROLE_INTERNAL_SERVICE; organization/location/transaction scope is rechecked by Processing |
Card-Present Service
| Method | Path | Auth |
|---|---|---|
POST | /api/v1/card-present/transactions/sale | location_user or higher |
POST | /api/v1/card-present/transactions/auth | location_user or higher |
POST | /api/v1/card-present/transactions/sync | txn:sync |
POST | /api/v1/card-present/transactions/sync/refund | txn:sync |
Management Service
Most management endpoints remain portal-role-based. The OAuth-exposed paths are the explicit exceptions below.
| Method | Path | Auth |
|---|---|---|
POST | /api/v1/organizations/{organizationId}/locations | agent_user or higher within the owning agent subtree; organization_admin within its organization |
GET | /api/v1/locations | readonly |
PUT | /api/v1/locations/{id} | location_admin |
POST | /api/v1/saml-providers | super_admin |
GET | /api/v1/audit-log | admin |
GET | /api/v1/customer-oauth-clients | organization_admin/location_admin/readonly scoped to caller organizations |
POST | /api/v1/customer-oauth-clients | organization_admin/location_admin scoped to caller organizations |
GET | /api/v1/settlements | REPORTS_READ, scoped to the requested merchant/location |
POST | /api/v1/settlements/manual-close | REPORTS_READ, exact authorized location; portal second confirmation; Management BFF forwards to the pinned Processing caller |
GET | /api/v1/settlements/batches | REPORTS_READ, exact authorized organization/location |
GET | /api/v1/settlements/batches/{gatewayBatchId} | REPORTS_READ, exact authorized organization/location |
GET | /api/v1/settlements/{batchId} | REPORTS_READ, exact authorized settlement merchant/location |
GET | /api/v1/settlements/{batchId}/transactions | REPORTS_READ, exact authorized settlement merchant/location |
GET | /api/v1/settlements/unmatched, /api/v1/settlements/unmatched/{rowId} | REPORTS_READ, support-safe and organization/location scoped |
POST | /api/v1/settlements/unmatched/{rowId}/convert, /api/v1/settlements/unmatched/{rowId}/mark-external | SETTLEMENT_REPAIR_WRITE (platform-only) plus explicit admin/super_admin role; controlled feature gate |
GET | /api/v1/organizations/{organizationId}/fraud-decisions (with optional locationId and transactionId) | FRAUD_DECISIONS_READ, exact organization scope; transaction-detail score projection is exact organization/location/transaction |
GET | /api/v1/organizations/{organizationId}/fraud-decisions/overview | FRAUD_DECISIONS_READ, exact organization scope |
GET | /api/v1/organizations/{organizationId}/fraud-decisions/top-rules | FRAUD_DECISIONS_READ, exact organization scope |
GET | /api/v1/organizations/{organizationId}/fraud-decisions/recent | FRAUD_DECISIONS_READ, exact organization scope |
GET | /api/v1/organizations/{organizationId}/fraud-decisions/{decisionId} | FRAUD_DECISIONS_READ, exact organization scope |
GET | /api/v1/organizations/{organizationId}/fraud-decisions/{decisionId}/related-activity | FRAUD_DECISIONS_READ, exact organization scope |
Management Service (OAuth exceptions)
| Method | Path | Scope |
|---|---|---|
POST | /api/v1/organizations | organizations:write, admin:* |
POST | /api/v1/organizations/{organizationId}/locations | organizations:write or admin:* |
GET | /api/v1/organizations/{organizationId}/locations/{locationId} | organizations:read or admin:* |
PUT | /api/v1/organizations/{organizationId}/locations/{locationId} | organizations:write or admin:* |
GET | /api/v1/organizations/{organizationId}/locations/by-external-id?externalId=... | organizations:read or admin:* |
POST | /api/v1/organizations/{organizationId}/locations/online/provision | organizations:write or admin:* |
GET | /api/v1/event-subscriptions | admin:* |
GET | /api/v1/event-subscriptions/{subscriptionId} | admin:* |
POST | /api/v1/event-subscriptions | admin:* |
PUT | /api/v1/event-subscriptions/{subscriptionId} | admin:* |
DELETE | /api/v1/event-subscriptions/{subscriptionId} | admin:* |
Online-Txn Service
| Method | Path | Auth |
|---|---|---|
POST | /api/v1/checkout/sessions | session:create |
POST | /api/v1/checkout/sessions/{id}/pay | Public |
POST | /api/v1/api-keys | Merchant auth |
GET | /api/v1/webhooks/events | Public |
GET/POST/PATCH | /api/v1/products/*, /api/v1/prices/*, /api/v1/promotion-codes/* | products:read / products:write |
GET/POST/PATCH | /api/v1/coupons/* | coupons:read / coupons:write |
GET/PUT | /api/v1/customers/* | customer:read / customer:write |
GET/PUT/DELETE | /api/v1/shipping/config | shipping:read / shipping:write |
Merchant-Onboarding Service
| Method | Path | Scope |
|---|---|---|
POST | /api/v1/provisioning/jobs | merchant:activate |
GET | /api/v1/provisioning/jobs/{id} | merchant:activate |