YesCash Authentication API

Version 1.0.0

Ctomer authentication API for partner applications integrating with YesCash.

Overview

# YesCash Authentication API

This OpenAPI file is the partner-facing contract for ctomer authentication.

It covers:

- login;
- registration;
- token refresh;
- logout;
- password reset;
- identifier management;
- device registration;
- MFA response handling where applicable;
- authentication error responses.

Partner applications e tokens returned by this API to call the Core API.

Base URL:

```text
https://api.yes.cash
```

Servers

Endpoints

Endpoint Reference

POST /v1/auth/start

Initiate ctomer login (step 1 of 2).

First step of the two-step ctomer-login flow per `§A.2.1`. The ctomer submits an identifier (email or phone); the principal resolves the identifier against the per-YesCash `iam.IdentifierRegistry` and returns a `loginAttemptId` opaque session reference. The response is uniform across the three Identifier Registry resolution outcomes per `§A.2.1.2` neutral-response posture (defeats identifier enumeration). Timing is normalised at the BFF. Anonymo on the ctomer side (APIM Posture A per ADR 20 v2.3.4 `§D3.7.8`). Partner context provided via APIM subscription key; APIM derives `X-Client-Id` server-side per Architecture `§14.3`. No password is submitted at this step — the two-step separation defeats step-1 credential validation by an attacker holding the subscription key but not the ctomer's password. Flow-level idempotency via `loginAttemptId`; no header-based idempotency key per FM.5.2.

Parameters

NameInRequiredTypeDescription
XCorrelationIdHeader
AcceptLanguageHeader

Request body

application/json: AuthStartRequest

Responses

StatusDescriptionSchema
200Login-attempt session initiated. Response shape uniform across the three Identifier Registry resolution outcomes per `§A.2.1.2`.application/json: AuthStartResponse
400BadRequest
401Unauthorized
429TooManyRequests
500InternalServerError
503ServiceUnavailable

POST /v1/auth/login

Complete ctomer login (step 2 of 2; also MFA challenge / enrolment continuation).

Second step of the two-step ctomer-login flow per `§A.2.2`. The ctomer-app presents the `loginAttemptId` (from `/auth/start`) and the ctomer's password; the BFF resolves the login-attempt session, calls Cognito `InitiateAuth`, and on credential-match returns the token bundle. **Three call shapes — `authStat` discrimination per `§A.12.7.a`:** - **Initial credential submission** — `loginAttemptId` + `password`. Response shape `AUTHENTICATED` (token bundle) or `MFA_CHALLENGE_REQUIRED` (MFA challenge) or `MFA_ENROLMENT_REQUIRED` (enrolment required). - **MFA challenge completion** — `loginAttemptId` + `mfaChallengeId` + `mfaCode` + `mfaMethod`. Same `loginAttemptId` from the prior call. - **MFA enrolment commit** — `loginAttemptId` + `mfaEnrolmentSessionId` + `mfaEnrolmentMethod` + `mfaEnrolmentCode`. Generic-rejection family discipline per Charter `§11` row 2: a non-existent identifier and an incorrect password BOTH surface as `auth.credentialMismatch` (AML-tipping-off framework). No sub-codes are emitted to distinguish the two cases. Cognito-side state is consulted read-only at the first call; `Subscription` allocation occurs atomically with the `AUTHENTICATED` response per `§A.2.2.3`. Anonymo on the ctomer side (APIM Posture A). Flow-level idempotency via `loginAttemptId`; per-`loginAttemptId` 1-attempt limit per Step 6a Q2.

Parameters

NameInRequiredTypeDescription
XCorrelationIdHeader
AcceptLanguageHeader

Request body

application/json: AuthLoginRequest

Responses

StatusDescriptionSchema
200Login outcome — discriminated by `authStat`: `AUTHENTICATED` (tokens issued); `MFA_CHALLENGE_REQUIRED` (challenge issued; partner-app re-calls `/auth/login` with `mfaChallengeId` + `mfaCode` + `mfaMethod`); `MFA_ENROLMENT_REQUIRED` (enrolment required; partner-app re-calls `/auth/login` with `mfaEnrolmentSessionId` + enrolment method + verification code).application/json: AuthLoginResponse
400BadRequest
401Unauthorized
403Forbidden
429TooManyRequests
500InternalServerError
502BadGateway
503ServiceUnavailable

POST /v1/auth/refresh

Refresh the access token.

Mint a fresh access-token ing the current refresh-token. APIM Posture B per ADR 20 v2.3.4 `§D3.7.8` — the JWT cryptographic integrity validation is performed but the expiry check is skipped; the ctomer-app MT present an access-token (expired or near-expiry) in the `Authorization` header as the carrier for APIM's `X-Client-Id` derivation. Refresh-token rotation per `§A.3.1`: on success the response contains a rotated refresh-token; the prior refresh-token is invalidated at Cognito atomically with the rotation. The partner- app MT persist the rotated value. Ctomer-app that has discarded its access-token cannot e `/auth/refresh`; the ctomer mt re-authenticate from `/auth/start`.

Parameters

NameInRequiredTypeDescription
XCorrelationIdHeader

Request body

application/json: AuthRefreshRequest

Responses

StatusDescriptionSchema
200Fresh access-token (and rotated refresh-token) issued.application/json: AuthRefreshResponse
400BadRequest
401Unauthorized
429TooManyRequests
500InternalServerError
503ServiceUnavailable

POST /v1/auth/logout

Revoke the current ctomer session.

Per-session refresh-token revocation per `§A.3.3` and ADR 20 v2.3.4 `§10` entry 3 leading recommendation (Cognito-side revocation semantic S2). APIM Posture C — a valid (non-expired) access-token is required. Ctomer-app recovery semantic for expired access-token per `§A.3.4`: call `/auth/refresh` first to mint a fresh access-token, then retry `/auth/logout`. Effect: the presented refresh-token is invalidated at Cognito. Other refresh-tokens for the same `MasterCtomer` (other devices, other partner-apps) REMAIN VALID. The ctomer's mental model is "log out of this app on this device" — matched by the S2 semantic. Administrative global sign-out is a separate operations-mediated path per `§A.3.5`.

Parameters

NameInRequiredTypeDescription
XCorrelationIdHeader

Request body

application/json: AuthLogoutRequest

Responses

StatusDescriptionSchema
204Logout successful. No response body. The ctomer-app deletes access-token and refresh-token from local storage.
400BadRequest
401Unauthorized
429TooManyRequests
500InternalServerError
503ServiceUnavailable

POST /v1/auth/register/start

Initiate ctomer-direct registration (step 1 of 3).

First step of the ctomer-direct registration flow per `§A.4`. The ctomer submits an identifier (email or phone); the principal dispatches a 6-digit OTP to that identifier and returns a `registrationId` opaque session reference. Anonymo endpoint (APIM Posture A); partner context via APIM subscription key. Neutral-response posture per `§A.4.2` — the response shape is uniform across the three Identifier Registry resolution outcomes (defeats identifier enumeration). The OTP carrier-side branding per `§A.4.5` preserves Charter `§11` row 5 cosmetic-separation defence: from-display carries the YCMT entity name; partner brand appears in body content for cohesion. Flow-level idempotency via `registrationId`; no header-based idempotency key per FM.5.2.

Parameters

NameInRequiredTypeDescription
XCorrelationIdHeader
AcceptLanguageHeader

Request body

application/json: RegisterStartRequest

Responses

StatusDescriptionSchema
200Registration session initiated; OTP dispatched. Response uniform across the three Identifier Registry resolution outcomes per `§A.4.2`.application/json: RegisterStartResponse
400BadRequest
401Unauthorized
429TooManyRequests
500InternalServerError
502BadGateway
503ServiceUnavailable

POST /v1/auth/register/verify-otp

Verify registration OTP (step 2 of 3).

Second step of the ctomer-direct registration flow per `§A.5`. The ctomer-app presents the `registrationId` and the OTP delivered to the ctomer's identifier; on OTP success the BFF returns a `branch` discriminator (`NEW_CTOMER` / `EXISTING_CTOMER`) per `§A.5.3` branch-keying, directing the partner-app's `/register/set-password` UI flow. Branch keying is on Cognito-credential existence per `§A.5.3`, NOT on `MasterCtomer` existence — a skeletal `MasterCtomer` (post-abandoned-flow) routes back to `NEW_CTOMER`. Branch disclosure is gated on OTP success per ADR 20 v2.3.4 `§D3.7.1` (post-OTP-ownership-proof); does not weaken the `§A.4.2` neutral-response defence at the prior step. Flow-level idempotency via `registrationId` (single-e after success); per-`registrationId` 3-attempt OTP counter per `§A.5.5`.

Parameters

NameInRequiredTypeDescription
XCorrelationIdHeader
AcceptLanguageHeader

Request body

application/json: RegisterVerifyOtpRequest

Responses

StatusDescriptionSchema
200OTP verified. Branch discriminator returned per `§A.5.3`.application/json: RegisterVerifyOtpResponse
400BadRequest
401Unauthorized
429TooManyRequests
500InternalServerError
503ServiceUnavailable

POST /v1/auth/register/set-password

Complete registration by setting / verifying password (step 3 of 3).

Final step of the ctomer-direct registration flow per `§A.6`. The endpoint path is `/v1/auth/register/set-password` per `§A.6.1` wire convention and Appendix A wire traces; this path supersedes the `§A.0.3` section-label editorial title "Registration — complete". **Branch behaviour per `§A.6.2` / `§A.6.3`:** - `NEW_CTOMER` branch: validates password against YesCash policy `§A.6.5`; creates Cognito credential; creates `MasterCtomer` (or rebinds skeletal `MasterCtomer` per `§A.6.7`); creates `Subscription` atomically; emits token bundle. `ctomerStat = PENDING` at issuance (KYC not yet completed). - `EXISTING_CTOMER` branch: verifies password against existing Cognito credential; on success creates or resolves `Subscription` for the `(MasterCtomer, App Client)` pair; emits token bundle. On `auth.credentialMismatch` the response may include the `details.next = PASSWORD_FORGOT` carry-forward hint per `§A.6.4`. Idempotency: per-`registrationId` (single-e after success); `X-Idempotency-Key` header SHOULD be supplied per FM.5 for the partner-app's retry policy; absence is acceptable per FM.5.2 anonymo-ctomer-driven-flow carve-out. Server-side dedup is keyed on `(clientId, idempotencyKey, endpoint)` when the header is present.

Parameters

NameInRequiredTypeDescription
XCorrelationIdHeader
XIdempotencyKeyHeaderOptional
AcceptLanguageHeader

Request body

application/json: RegisterSetPasswordRequest

Responses

StatusDescriptionSchema
200Registration complete; credentials created (`NEW_CTOMER`) or verified (`EXISTING_CTOMER`); `Subscription` allocated; token bundle issued.application/json: RegisterSetPasswordResponse
400BadRequest
401Unauthorized
403Forbidden
409Conflict
429TooManyRequests
500InternalServerError
502BadGateway
503ServiceUnavailable

GET /v1/auth/identifiers

List the authenticated ctomer's active identifiers.

Returns the currently-active identifier set for the authenticated `MasterCtomer` per `§A.8.2`. Ctomer-context bearer token required (APIM Posture C). The identifier set is `MasterCtomer`-scoped at the YesCash level per `§A.7.6` — the same set is visible to any partner-app whose ctomer-app authenticates the same `MasterCtomer`. Activity history is partner-scoped via `clientId` audit-event attribution (`identifier.list_accessed` carries the calling `clientId`). Identifier-management-context surface; structurally distinct from the Part B `/v1/core/profile` endpoint per `§A.8.2.2` — `canRemove` is identifier-management-specific and has no place in profile read. Safe read; no idempotency key.

Parameters

NameInRequiredTypeDescription
XCorrelationIdHeader

Responses

StatusDescriptionSchema
200Ctomer's active identifier set.application/json: IdentifierListResponse
401Unauthorized
403Forbidden
429TooManyRequests
500InternalServerError
503ServiceUnavailable

POST /v1/auth/identifiers/add/start

Initiate identifier-add (step 1 of 2).

First step of the two-step OTP-protected identifier-add per `§A.8.3`. The ctomer-app submits a new identifier; the principal evaluates against the per-YesCash `iam.IdentifierRegistry` per the distinguishable-rejection posture of `§A.8.3.2` Q2: - Identifier already owned by the authenticated `MasterCtomer` → `validation.identifierAlreadyOwned` (400). - Identifier owned by a DIFFERENT `MasterCtomer` → `validation.identifierNotAvailable` (400; neutral wording per AML-tipping-off framework — no disclosure of the other ctomer's existence). - Identifier not yet owned → OTP dispatched to the new identifier; `identifierAddId` returned. Ctomer-context (APIM Posture C). Flow-level idempotency via `identifierAddId`.

Parameters

NameInRequiredTypeDescription
XCorrelationIdHeader
AcceptLanguageHeader

Request body

application/json: IdentifierAddStartRequest

Responses

StatusDescriptionSchema
200Identifier-add session initiated; OTP dispatched.application/json: IdentifierAddStartResponse
400BadRequest
401Unauthorized
403Forbidden
429TooManyRequests
500InternalServerError
502BadGateway
503ServiceUnavailable

POST /v1/auth/identifiers/add/verify-otp

Complete identifier-add (step 2 of 2).

Verifies the OTP dispatched at `/identifiers/add/start` and writes the new identifier to `iam.IdentifierRegistry` bound to the authenticated `MasterCtomer` per `§A.8.4`. The `iam.IdentifierRegistry` per-YesCash uniqueness constraint is enforced at write time as defence-in-depth against staging-vs- commit race conditions. Ctomer-context (APIM Posture C). Flow-level idempotency via `identifierAddId` (single-e after success); per-`identifierAddId` 3-attempt OTP counter.

Parameters

NameInRequiredTypeDescription
XCorrelationIdHeader

Request body

application/json: IdentifierAddVerifyOtpRequest

Responses

StatusDescriptionSchema
200Identifier added. Response shape matches the per-identifier shape from `GET /v1/auth/identifiers` per `§A.8.4.4`.application/json: IdentifierAddVerifyOtpResponse
400BadRequest
401Unauthorized
403Forbidden
429TooManyRequests
500InternalServerError
503ServiceUnavailable

POST /v1/auth/identifiers/{identifierId}/remove/start

Initiate identifier-remove (step 1 of 2).

First step of the two-step OTP-protected identifier-remove per `§A.8.5`. The ctomer-app specifies (via `otpDeliveryIdentifierId`) which of the ctomer's OTHER active identifiers receives the verification OTP — the ctomer proves continued control of an identifier they are RETAINING per the substantive remove-scenario framing. At-least-one-active-identifier invariant per `§A.8.1 Q4`: if the path-parameter `identifierId` is the ctomer's only active identifier, returns `validation.identifierLastActiveCannotBeRemoved` (400). Ctomer directed to `§A.10` recovery posture for sole- identifier-lost cases. Ctomer-context (APIM Posture C). Flow-level idempotency via `identifierRemoveId`.

Parameters

NameInRequiredTypeDescription
XCorrelationIdHeader
AcceptLanguageHeader
identifierIdpathYesstringThe partner-visible UUID v4 of the identifier being removed.

Request body

application/json: IdentifierRemoveStartRequest

Responses

StatusDescriptionSchema
200Identifier-remove session initiated; OTP dispatched to the OTHER identifier.application/json: IdentifierRemoveStartResponse
400BadRequest
401Unauthorized
403Forbidden
404NotFound
429TooManyRequests
500InternalServerError
502BadGateway
503ServiceUnavailable

POST /v1/auth/identifiers/{identifierId}/remove/verify-otp

Complete identifier-remove (step 2 of 2).

Verifies the OTP dispatched at `/identifiers/{identifierId}/remove/start` and deactivates the path-parameter identifier in `iam.IdentifierRegistry` per `§A.8.6`. Defence-in-depth: at commit time the at-least-one-active-identifier invariant is re-checked; a rare race condition where another concurrent flow leaves the path-parameter identifier as the sole active one returns `validation.identifierLastActiveCannotBeRemoved` (400). Ctomer-context (APIM Posture C). Flow-level idempotency via `identifierRemoveId`.

Parameters

NameInRequiredTypeDescription
XCorrelationIdHeader
identifierIdpathYesstring

Request body

application/json: IdentifierRemoveVerifyOtpRequest

Responses

StatusDescriptionSchema
200Identifier deactivated.application/json: IdentifierRemoveVerifyOtpResponse
400BadRequest
401Unauthorized
403Forbidden
404NotFound
429TooManyRequests
500InternalServerError
503ServiceUnavailable

POST /v1/auth/password/forgot

Initiate password reset (step 1 of 2).

First step of the two-step OTP-protected password reset per `§A.9.2`. The ctomer-app submits an identifier (and optionally a carry-forward `registrationId` from a prior `auth.credentialMismatch` rejection); the principal dispatches a 6-digit OTP to the ctomer's verified identifier and returns an opaque `passwordResetId`. Anonymo endpoint (APIM Posture A); partner context via APIM subscription key. Neutral-response posture per `§A.9.2.2` — response shape uniform across the three Identifier Registry resolution outcomes. On the not-resolved path the BFF performs a timing-equalised no-op dispatch. Carry-forward path per `§A.9.2.3`: when `registrationId` is present and valid, the principal binds the `passwordResetId` to the same `MasterCtomer`, places the `registrationId` in paed-TTL state, and (on successful `/password/reset`) reactivates with refreshed 30-min TTL. Flow-level idempotency via `passwordResetId`; no header-based idempotency key per FM.5.2.

Parameters

NameInRequiredTypeDescription
XCorrelationIdHeader
AcceptLanguageHeader

Request body

application/json: PasswordForgotRequest

Responses

StatusDescriptionSchema
200Password-reset session initiated; OTP dispatched.application/json: PasswordForgotResponse
400BadRequest
401Unauthorized
429TooManyRequests
500InternalServerError
502BadGateway
503ServiceUnavailable

POST /v1/auth/password/reset

Complete password reset (step 2 of 2).

Verifies the OTP dispatched at `/password/forgot`, validates the new password against the YesCash password policy per `§A.6.5`, and sets the new password at Cognito per `§A.9.3`. On success, the BFF invokes Cognito `AdminerGlobalSignOut` per `§A.9.3.5` Q8 — ALL refresh-tokens for the `MasterCtomer` across all partner-app sessions are invalidated. The ctomer's first post-reset authentication produces a fresh session bound to the fresh credential. The endpoint does NOT issue new tokens at success — the response directs the ctomer-app to re-authenticate via `/auth/start` + `/auth/login` (standalone path; `next = LOGIN`) or to re-present at `/register/set-password` with the reactivated `registrationId` and fresh password (carry-forward path; `next = REGISTER_COMPLETE`). Anonymo endpoint (APIM Posture A). Flow-level idempotency via `passwordResetId`.

Parameters

NameInRequiredTypeDescription
XCorrelationIdHeader

Request body

application/json: PasswordResetRequest

Responses

StatusDescriptionSchema
200Password reset successful. Ctomer-app routed per `next` discriminator to re-authenticate or re-present at `/register/set-password`.application/json: PasswordResetResponse
400BadRequest
401Unauthorized
429TooManyRequests
500InternalServerError
502BadGateway
503ServiceUnavailable

POST /v1/auth/device-registration/start

Initiate device-registration ceremony (step 1 of 2).

First step of device-bound authentication registration per `§A.11.2`. The ctomer-app initiates a device-registration ceremony; the principal generates a fresh `registrationChallenge` (32 bytes cryptographically-random; 5-minute TTL) and returns it pl `publicKeyRequirements` to the ctomer-app. The ctomer- app's device generates an RSA key-pair locally (private key into OS keystore; public key in memory for transmission at `/complete`) and signs the registration proof JWS over the challenge pl the public-key thumbprint. **For a ctomer with an existing active registration**: returns `202 Accepted` with `stepUpRequired = LIVENESS_CHECK` per `§A.11.2.6`. The ctomer-app mt route to the liveness webview (re-ing the `§B.4 / §B.5` KYC capture surface as a step-up surface), then re-call `/device-registration/start` with the resulting `stepUpToken` to receive a 201 Created with `stepUpApplied = true`. Ctomer-stat gating per `§A.11.2.2`: `PENDING` / `VALID` / `MONITORED` allow; `RESTRICTED` / `BLOCKED` / `CLOSED` reject with `ctomer.statBlocking` (403). Ctomer-context (APIM Posture C). Idempotency-Key required per FM.5.

Parameters

NameInRequiredTypeDescription
XCorrelationIdHeader
XIdempotencyKeyHeader

Request body

application/json: DeviceRegistrationStartRequest

Responses

StatusDescriptionSchema
201Registration challenge issued; partner-app proceeds to `/device-registration/complete`.application/json: DeviceRegistrationStartHappyPath
202Step-up required (ctomer has existing active registration). Ctomer-app opens `livenessWebviewUrl` per `§B.4 / §B.5` pattern; on successful liveness check, retries `/device-registration/start` with the resulting `stepUpToken`.application/json: DeviceRegistrationStartStepUpRequired
400BadRequest
401Unauthorized
403Forbidden
409Conflict
410Gone
429TooManyRequests
500InternalServerError
503ServiceUnavailable

POST /v1/auth/device-registration/complete

Complete device-registration with proof-of-possession (step 2 of 2).

Submits the freshly-generated RSA public key together with the `DeviceRegistrationProof` JWS signed by the corresponding device private key per `§A.11.3`. The proof binds to the `registrationChallenge` issued at `/start` pl the RFC 7638 SHA-256 JWK thumbprint of the submitted public key per App C `§C.2`. **Cryptographic discipline — RS256 + RSA-2048 minimum.** Per App C `§C.0.2` + ADR 25 v1.1 `§D5.1` + FAPI 1.0 Baseline + NIST SP 800-131A. v1 closes on RS256 only; ES256 / EdDSA are forward- watched at v1.1. **The Step 23 Appendix B flow B.3 step 5 erratum is corrected by App C `§C.3` corrective reference and by the `DeviceRegistrationProof` schema description in this document.** Verification chain (principal-side) per App C `§C.2.7` — five steps: header discipline → public-key acceptance → self-signature verification → challenge binding → thumbprint binding. Each step produces a specific error code on failure per `§A.14.2.3`. On success: the public key is stored against `(MasterCtomerId, ctomer_device_id)` per ADR 25 `§D5`; the principal-allocated `deviceId` (ULID, `DEV-` prefix) is returned; the partner-app es this `deviceId` in subsequent `§B.12` transfer-confirmation calls. The verbatim `registrationProof` JWS is preserved in the audit trail per `§A.13.5`. Ctomer-stat gating identical to `/start` per `§A.11.3.2`. Idempotency-Key required per FM.5.

Parameters

NameInRequiredTypeDescription
XCorrelationIdHeader
XIdempotencyKeyHeader

Request body

application/json: DeviceRegistrationCompleteRequest

Responses

StatusDescriptionSchema
201Device registered; `deviceId` returned for subsequent §B.12 calls.application/json: DeviceRegistrationCompleteResponse
400BadRequest
401Unauthorized
403Forbidden
409Conflict
422UnprocessableEntity
429TooManyRequests
500InternalServerError
503ServiceUnavailable

Schemas

Error

The canonical error envelope per FM.6.1. Every 4xx and 5xx response on every Part A endpoint follows this shape. The `error.code` value is the partner- consumed contract surface; `error.message` is partner-side diagnostic text in English (ctomer-facing localisation is partner-app responsibility); `error.correlationId` mirrors the `X-Correlation-Id` response header for partner log-correlation when intermediate infrastructure strips headers; `error.details` carries code-specific structured detail per `§A.14`.

FieldRequiredTypeDescription
errorYesobject

ValidationIssue

Per-field validation issue per FM.6.5. The summary code on the envelope is one of `validation.fieldInvalid`, `validation.fieldMissing`, `validation.bodyMalformed`, `validation.contentTypeUnsupported`; the per-field issue carries the specific issue code drawn from the Part-A-specific controlled vocabulary at `§A.14.3` and `§A.14.5.3`.

FieldRequiredTypeDescription
fieldYesstringField path ing dotted notation for nested fields (e.g., `recipient.bankAccount.iban`); array indices e bracket notation (e.g., `fees[2].amount`).
codeYesstringSpecific per-field issue code. Drawn from the controlled vocabulary at `§A.14.3` (Part-A-specific) + `§A.14.5.3` (Part-A-generic).
messageNostringOptional per-field diagnostic message.

PasswordPolicyDetails

`details` shape for `validation.passwordPolicyViolation` per `§A.6.5`. Surfaced on `POST /v1/auth/register/set-password` and `POST /v1/auth/password/reset` when the ctomer-supplied password fails the YesCash password policy.

FieldRequiredTypeDescription
rulesYesarray<string>The full set of rules in force at the YesCash. Stable for a given YesCash; surfacing the full set helps partner-app implementers build the ctomer-app's password-strength indicator UI.
violationsYesarray<string>The subset of rules the submitted password failed. The ctomer-app surfaces remediation guidance per the specific violations; the partner- app does NOT see the password itself.

OtpAttemptDetails

`details` shape for `auth.otpInvalid` carrying remaining-attempt diagnostic per `§A.5.5` / `§A.8.4.4` / `§A.9.3.4`.

FieldRequiredTypeDescription
attemptsRemainingNointegerRemaining OTP-verification attempts before the per-session OTP counter exhats (3-attempt limit per `§A.4.5`). On the final attempt's failure, the response code transitions to `auth.otpAttemptsExhated` or `auth.otpExhated` (the latter at password-reset per `§A.9.3.4`) and `attemptsRemaining` is no longer carried.

CustomerStatusDetails

`details` shape for `ctomer.statBlocking` / `ctomer.statInsufficient` per FM.6.7. Carries the current ctomer stat and a remediation hint per `§B.17.3` / App G `remediation` identifier registry.

FieldRequiredTypeDescription
currentStatusNostringThe ctomer's current matrix v4 stat. Enum values per `§B.2 / matrix v4 §2`.
requiredStatusNostringFor `ctomer.statInsufficient`, the minimum stat the endpoint requires. Omitted for `ctomer.statBlocking` (where the stat is in an absolutely blocking state regardless of endpoint).
remediationHintNostringApp G `remediation` identifier per App G `§G.2`, indicating the ctomer-UX action to surface. The partner-app's UX layer translates the identifier to localised ctomer-facing copy.

RateLimitDetails

`details` shape for `validation.rateLimitExceeded` (429) per FM.7.

FieldRequiredTypeDescription
scopeNostringThe rate-limit scope that breached. Per FM.7 scopes: `partnerCtomer` `(partnerId, MasterCtomerId)`, `partnerIp` `(partnerId, sourceIp)`, `partnerIdentifier` `(partnerId, identifier)`, `partnerGlobal` `(partnerId)`.
retryAfterSecondsNointegerSeconds to wait before retry; mirrors `Retry-After` header.

IdentifierType

Ctomer identifier type. Disambiguates the OTP-dispatch carrier path (SMS vs email) and the format-validation rule (RFC 5321/5322 vs E.164).

FieldRequiredTypeDescription

Identifier

Ctomer login handle in normalised form. Email lowercase RFC 5321/5322 compliant; phone in E.164 format with leading `+`. The principal normalises before `iam.IdentifierRegistry` lookup; partner-app SHOULD submit pre-normalised values for predictable UX (e.g., the partner-app's text-input field MAY enforce E.164 entry, but the BFF does not require it — the BFF normalises and validates).

FieldRequiredTypeDescription

IdentifierMasked

Ctomer-display-safe masked form of an identifier. Email: first character + `****` + `@` + domain. Phone: country-code prefix + `*` digits + last four digits. The raw identifier value is NEVER returned via the ctomer-context API; the masked form is the ctomer-recognition signal.

FieldRequiredTypeDescription

TokenBundle

The Cognito-issued token bundle per `§A.2.2.5` / `§A.6` / `§A.3`. Treated as opaque by partner-apps except for the documented inspection carve-outs at FM.4.2.

FieldRequiredTypeDescription
accessTokenYesstringThe Cognito-issued access-token JWT. ed as `Authorization: Bearer <accessToken>` on every ctomer-context endpoint. TTL bounded at 60 minutes by Architecture `§14.3` invariant (ADR 20 v2.2 token-TTL invariant); concrete value communicated via `expiresIn`.
refreshTokenYesstringThe Cognito-issued refresh-token. Opaque to the partner-app. Submitted to `POST /v1/auth/refresh` to mint fresh access-tokens. TTL operationally configured per YesCash (typically 10 days for YesCash phase-1, bounded at 30 days by Architecture `§14.3`).
idTokenNostringThe Cognito-issued ID-token JWT. Optional; some partner-app integrations surface limited ctomer-identity claims from the ID token; the partner-app's authorisation decisions remain governed by SQL-resolved request context, NOT by ID-token claims.
tokenTypeYesstring
expiresInYesintegerAccess-token TTL in seconds. Per `§A.3` YesCash-operational configuration; 3600 (60 minutes) for YesCash phase-1.
accessTokenExpiresAtNostringAbsolute access-token expiry timestamp; mirrors the JWT `exp` claim. Surfaced for partner-app proactive-refresh logic that avoids inspecting the JWT directly.
refreshTokenExpiresAtNostringAbsolute refresh-token expiry timestamp.
idTokenExpiresAtNostringAbsolute ID-token expiry timestamp.

AuthStartRequest

Request body for `POST /v1/auth/start`. The first step of the two-step ctomer-login flow per `§A.2.1`. Anonymo on the ctomer-side (partner-app context provided via APIM subscription key). No password at this step — the two-step separation defeats step-1 credential validation by an attacker holding the subscription key but not the ctomer password.

FieldRequiredTypeDescription
identifierYesIdentifier
identifierTypeYesIdentifierType

AuthStartResponse

Response body for `POST /v1/auth/start`. Uniform across the three Identifier Registry resolution outcomes per `§A.2.1.2` neutral-response posture; the response shape is identical whether the ctomer-supplied identifier resolves to a registered `MasterCtomer`, a skeletal `MasterCtomer`, or no `MasterCtomer`. Timing is normalised at the BFF per `§A.2.1.2` to defeat side-channel inference.

FieldRequiredTypeDescription
loginAttemptIdYesstringOpaque, single-e, short-lived login-attempt session identifier per ADR 20 v2.3.4 `§D3.7.7`. Server-side-bound to the ctomer- supplied identifier (hashed), the App Client, the resolution outcome at this step (not surfaced; consumed at `/auth/login`), and `loginAttemptExpiresAt`. Format illtrative; partners treat opaquely.
loginAttemptExpiresAtYesstringAbsolute expiry timestamp. TTL 10 minutes from issuance per Step 6a Q1 decision; shorter than `registrationId`'s 30 minutes becae login is single-action with no intermediate OTP gate.

AuthLoginRequest

Request body for `POST /v1/auth/login`. Three legal call shapes: (1) initial password submission (after `/auth/start`), (2) MFA challenge completion (after a `MFA_CHALLENGE_REQUIRED` response per `§A.12.7.a`), (3) MFA enrolment commit (after a `MFA_ENROLMENT_REQUIRED` response per `§A.12.7.a`). The `loginAttemptId` is preserved across the multi-call sequence per `§A.12`; the partner-app does NOT generate a fresh `loginAttemptId` for the MFA continuation calls.

FieldRequiredTypeDescription
loginAttemptIdYesstringThe login-attempt session identifier from `/auth/start`.
passwordNostringCtomer-supplied password. Required on the initial (post-`/auth/start`) call; omitted on MFA challenge/enrolment continuation calls. The BFF passes the password to Cognito `InitiateAuth`; the BFF does NOT enforce password policy at this surface (policy is enforced at `/auth/register/set-password` and `/auth/password/reset` per `§A.6.5`).
mfaChallengeIdNostringRequired for MFA challenge completion (after a `MFA_CHALLENGE_REQUIRED` response). The principal-issued challenge identifier from the prior `/auth/login` response.
mfaCodeNostringRequired for MFA challenge completion. The MFA code from the ctomer's enrolled factor (TOTP code from authenticator app; SMS-MFA code dispatched out-of-band).
mfaMethodNostringRequired for MFA challenge completion or enrolment commit. The factor the ctomer is ing.
mfaEnrolmentSessionIdNostringRequired for MFA enrolment commit (after a `MFA_ENROLMENT_REQUIRED` response). The enrolment-session identifier from the prior `/auth/login` response.
mfaEnrolmentMethodNostringRequired for MFA enrolment commit. The selected enrolment factor.
mfaEnrolmentCodeNostringRequired for MFA enrolment commit. The first TOTP code from the authenticator app (proves the ctomer scanned the setup secret) or the SMS-MFA code dispatched to the ctomer's enrolling phone.

AuthLoginResponse

Response body for `POST /v1/auth/login`. The polymorphic response is discriminated by `authStat` per `§A.12.7.a`: - `AUTHENTICATED` — ctomer authenticated; token bundle issued - `MFA_CHALLENGE_REQUIRED` — MFA challenge issued; partner-app collects MFA code and re-calls `/auth/login` with `mfaChallengeId` + `mfaCode` + `mfaMethod` - `MFA_ENROLMENT_REQUIRED` — MFA enrolment required; partner-app surfaces the `supportedMethods[]` array, collects ctomer enrolment selection and verification code, re-calls `/auth/login` with `mfaEnrolmentSessionId` + `mfaEnrolmentMethod` + `mfaEnrolmentCode` The partner-app MT recognise all three `authStat` values per `§B.17.2.3` and route correctly.

FieldRequiredTypeDescription

AuthLoginResponseAuthenticated

`AUTHENTICATED` response shape per `§A.2.2.5`. The ctomer's session is established; the token bundle is issued; the partner-scoped `subscriptionId` is allocated or reed per `§A.2.2.3`.

FieldRequiredTypeDescription
authStatusYesstring
accessTokenYesstringCognito access-token JWT per `TokenBundle.accessToken`.
refreshTokenYesstringCognito refresh-token per `TokenBundle.refreshToken`.
idTokenNostringOptional Cognito ID-token JWT per `TokenBundle.idToken`.
tokenTypeYesstring
expiresInYesinteger
accessTokenExpiresAtNostring
refreshTokenExpiresAtNostring
idTokenExpiresAtNostring
subscriptionIdYesstringPartner-scoped ctomer reference allocated per ADR 22 v3 `§D2.6`. Always populated. Informational on the wire per `§B.1.5` — partner- app does NOT e `subscriptionId` in subsequent Part B URLs (self-referential URL convention per `§B.1.4`); recorded for partner-side logging and per-partner-app ctomer-listing.
customerStatusNostringMatrix v4 ctomer stat at the moment of token issuance. Subsequent stat reads via `GET /v1/core/profile` (Part B).
mfaEnrolmentNoobjectPresent on the enrolment-commit success path per Appendix A flow A.5; carries the jt-enrolled factor information.

AuthLoginResponseMfaChallengeRequired

`MFA_CHALLENGE_REQUIRED` response shape per `§A.12.7.a`. The ctomer's credentials verified; a risk signal fired (EU posture per `§A.12.4`) or the YesCash posture requires MFA at every login ( YesCash per `§500.12`); the partner-app collects the MFA code and re-calls `/auth/login` with the challenge fields.

FieldRequiredTypeDescription
authStatusYesstring
mfaChallengeIdYesstringPrincipal-issued challenge identifier; consumed at challenge completion.
mfaMethodYesstringThe factor the ctomer has enrolled (singular at v1 per single-active-factor invariant).
mfaChallengeExpiresAtYesstring5-minute TTL per `§A.12.7.a`.
supportedMethodsNoarray<string>For ctomers with multiple enrolled factors (forward gap; single-factor at v1), enumerates the methods available to the ctomer. Singular array at v1.

AuthLoginResponseMfaEnrolmentRequired

`MFA_ENROLMENT_REQUIRED` response shape per `§A.12.7.a` / `§A.12.6.1`. Ctomer-app surfaces the `supportedMethods[]` array with TOTP first (the recommended default); partner-app SHOULD display the `rationale` field accompanying each method so the ctomer is informed of the assurance-tradeoff between TOTP and SMS_MFA.

FieldRequiredTypeDescription
authStatusYesstring
mfaEnrolmentSessionIdYesstring
supportedMethodsYesarray<object>
mfaEnrolmentSessionExpiresAtYesstring5-minute TTL per `§A.12.7.a`.

AuthRefreshRequest

FieldRequiredTypeDescription
refreshTokenYesstringThe current refresh-token. Consumed at Cognito `InitiateAuth` `REFRESH_TOKEN_AUTH`; rotated per `§A.3.1` YesCash policy. The ctomer-app's `Authorization` header carries the expired or near-expiry access-token; APIM Posture B per ADR 20 v2.3.4 `§D3.7.8` skips the expiry check for `/auth/refresh`.

AuthRefreshResponse

Fresh access-token issuance per `§A.3.2.4`. The refresh-token is rotated per the `§A.3.1` rotation posture (always present in the response; if rotation is operationally disabled the value is the same as submitted, preserving contract uniformity).

FieldRequiredTypeDescription
accessTokenYesstring
refreshTokenYesstring
idTokenNostring
tokenTypeYesstring
expiresInYesinteger
accessTokenExpiresAtNostring
idTokenExpiresAtNostring

AuthLogoutRequest

FieldRequiredTypeDescription
refreshTokenYesstringThe current refresh-token. The BFF passes to Cognito `RevokeToken` per `§A.3.3.2`. Per ADR 20 v2.3.4 `§10` entry 3 leading recommendation and Step 6b Q6 acceptance, the revocation semantic is S2 (per-session revocation) — only the presented refresh-token is invalidated; other refresh-tokens for the same `MasterCtomer` (other devices, other partner-apps) remain valid.

RegisterStartRequest

FieldRequiredTypeDescription
identifierYesIdentifier
identifierTypeYesIdentifierType
partnerCustomerRefNostringOptional partner-side opaque reference for the ctomer per `§A.4.3`. Recorded on `Subscription.OriginatingChannelMetadata` per ADR 22 v3 `§D2` for partner-side correlation. NOT ed as an identity input to SQL per Architecture `§14.3` invariant 3.

RegisterStartResponse

Response body for `POST /v1/auth/register/start`. Uniform across the three Identifier Registry resolution outcomes per `§A.4.2` neutral-response posture.

FieldRequiredTypeDescription
registrationIdYesstringOpaque, single-e, short-lived registration session identifier per ADR 20 v2.3.4 `§D3.7.7`. Server-side-bound to the ctomer- supplied identifier (normalised), the App Client, the resolution outcome at this step, and `registrationIdExpiresAt`. Format illtrative.
nextYesstringAlways `OTP` at v1 — directs the partner-app to `POST /v1/auth/register/verify-otp`.
registrationIdExpiresAtYesstring30-minute TTL from issuance per `§A.4.4` Step 5 Q5 / Q7b decision. The OTP dispatched alongside has its own shorter 5-minute TTL.

RegisterVerifyOtpRequest

FieldRequiredTypeDescription
registrationIdYesstring
otpYesstring6-digit numeric OTP. Accepted as a string to preserve leading zeros; non-numeric or non-6-character input rejected as `validation.fieldFormatInvalid`.

RegisterVerifyOtpResponse

Response body for `POST /v1/auth/register/verify-otp`. The `branch` discriminator drives the partner-app's `/register/set-password` UI flow per `§A.5.3`: `NEW_CTOMER` → "create your password" UI; `EXISTING_CTOMER` → "verify your password to register this app" UI. Both values target the same endpoint at the next step; the value distinguishes the ctomer-app UI mode.

FieldRequiredTypeDescription
registrationIdYesstring
branchYesstringPer `§A.5.3` branch keying. Keyed on Cognito-credential existence for the resolved `MasterCtomer`, NOT on `MasterCtomer` existence; the third row of the §A.5.3 mapping (skeletal `MasterCtomer`) routes to `NEW_CTOMER`. The substantive disclosure of branch is gated on OTP success per ADR 20 v2.3.4 `§D3.7.1` (post-OTP-ownership-proof; does not weaken the `§A.4.2` neutral-response defence at the prior step).
nextYesstringDirects the partner-app to `POST /v1/auth/register/set-password`. `SET_PASSWORD` for `NEW_CTOMER`; `VERIFY_PASSWORD` for `EXISTING_CTOMER`. The value `PASSWORD` is preserved at v1 as an Appendix A wire-trace convention; partner-apps SHOULD accept any of the three values as a directive to call `/register/set-password`.

RegisterSetPasswordRequest

Request body for `POST /v1/auth/register/set-password`. The endpoint path is per `§A.6.1` wire convention and per the Appendix A wire traces; the path supersedes the §A.0.3 section-label "Registration — complete" editorial title. Both `NEW_CTOMER` and `EXISTING_CTOMER` branches consume the same request shape; the BFF differentiates semantics by the post-OTP-state recorded against the `registrationId` per `§A.6.2` / `§A.6.3`.

FieldRequiredTypeDescription
registrationIdYesstring
passwordYesstringFor `NEW_CTOMER` branch: the new password to create against the Cognito er. Validated against the YesCash password policy per `§A.6.5` BEFORE the Cognito-create-er call. For `EXISTING_CTOMER` branch: the ctomer's existing password, verified against Cognito.

RegisterSetPasswordResponse

On `NEW_CTOMER` branch success: the Cognito credential is created, the `MasterCtomer` and `Subscription` records are written atomically per ADR 22 v3 `§D2`, and the initial token bundle is issued. On `EXISTING_CTOMER` branch success: the credential is verified, the partner-scoped `Subscription` is created or resolved, and the initial token bundle is issued. Both branches return the same token-bundle shape with `ctomerStat = PENDING` for new ctomers (KYC not yet completed) or the ctomer's pre-existing stat for the `EXISTING_CTOMER` branch.

FieldRequiredTypeDescription
accessTokenYesstring
refreshTokenYesstring
idTokenNostring
tokenTypeYesstring
expiresInYesinteger
accessTokenExpiresAtNostring
refreshTokenExpiresAtNostring
idTokenExpiresAtNostring
subscriptionIdNostringPartner-scoped ctomer reference per ADR 22 v3 `§D2.6`.
customerStatusYesstring

IdentifierListResponse

FieldRequiredTypeDescription
identifiersYesarray<IdentifierListItem>Zero-or-more currently-active identifiers for the authenticated `MasterCtomer`. Order: ascending by `verifiedAt`. The at-least-one-active-identifier invariant per `§A.8.1 Q4` means an authenticated ctomer always has at least one active identifier; the empty-array case is structurally impossible at this endpoint but the shape accommodates it.

IdentifierListItem

FieldRequiredTypeDescription
identifierIdYesstringPartner-visible UUID v4 (ULID rendered with `ID-` prefix in the wire form per FM.3.3). ed as the path parameter at the remove endpoint (`§A.8.5`).
identifierTypeYesIdentifierType
identifierMaskedYesIdentifierMasked
verifiedAtYesstring
canRemoveYesboolean`true` if removal would not violate the at-least-one-active- identifier invariant. Partner-app's UI surfaces the per-identifier remove control conditional on this field.

IdentifierAddStartRequest

FieldRequiredTypeDescription
identifierYesIdentifierThe new identifier being added. Normalised per FM.3 before `iam.IdentifierRegistry` lookup. Ctomer-context endpoint implements distinguishable-rejection posture per `§A.8.3.2` Q2: already-owned-by-this-ctomer surfaces `validation.identifierAlreadyOwned`; owned-by-other-ctomer surfaces `validation.identifierNotAvailable`; not-yet-owned proceeds with OTP dispatch.
identifierTypeYesIdentifierType

IdentifierAddStartResponse

FieldRequiredTypeDescription
identifierAddIdYesstringOpaque, single-e, short-lived identifier-add session identifier. Server-side-bound to the authenticated `MasterCtomerId`, the ctomer-supplied new identifier (normalised), the App Client, and the expiry timestamp.
nextYesstring
identifierAddIdExpiresAtYesstring30-minute TTL from issuance per `§A.8.3.4`.

IdentifierAddVerifyOtpRequest

FieldRequiredTypeDescription
identifierAddIdYesstring
otpYesstring

IdentifierAddVerifyOtpResponse

Shape matches the per-identifier shape from `GET /v1/auth/identifiers` per `§A.8.4.4` — allows the partner-app to update its in-app identifier-list cache directly from this response without a follow-up list-fetch.

FieldRequiredTypeDescription

IdentifierRemoveStartRequest

FieldRequiredTypeDescription
otpDeliveryIdentifierIdYesstringThe `identifierId` of the ctomer-selected OTHER active identifier that will receive the verification OTP. Mt be (a) bound to the authenticated `MasterCtomer`, (b) currently active, and (c) distinct from the path-parameter `identifierId` being removed. The substantive control per `§A.8.5.2`: the ctomer proves continued control of an identifier they are RETAINING; the typical remove scenario is the ctomer is losing access to the identifier being removed.

IdentifierRemoveStartResponse

FieldRequiredTypeDescription
identifierRemoveIdYesstring
nextYesstring
otpDeliveryIdentifierMaskedYesIdentifierMaskedMasked form of the ctomer-selected OTHER identifier; surfaces to the ctomer-app's UI so the ctomer sees confirmation of where the OTP was dispatched.
identifierRemoveIdExpiresAtYesstring

IdentifierRemoveVerifyOtpRequest

FieldRequiredTypeDescription
identifierRemoveIdYesstring
otpYesstring

IdentifierRemoveVerifyOtpResponse

FieldRequiredTypeDescription
identifierIdYesstring
deactivatedAtYesstring

PasswordForgotRequest

FieldRequiredTypeDescription
identifierYesIdentifier
identifierTypeYesIdentifierType
registrationIdNostringOptional carry-forward `registrationId` from a prior `auth.credentialMismatch` rejection at `/register/set-password` `EXISTING_CTOMER` branch per `§A.9.2.3` Q5 pae-and-refresh decision. When present, the principal binds the `passwordResetId` to the same `MasterCtomer` as the `registrationId`, places the `registrationId` in paed-TTL state, and on successful `/password/reset` reactivates the `registrationId` with refreshed 30-min TTL and directs the ctomer-app back to `/register/set-password`.

PasswordForgotResponse

Uniform response across the three Identifier Registry resolution outcomes per `§A.9.2.2` neutral-response posture. On the identifier-does-not-resolve path the BFF performs a timing-equalised no-op dispatch — no OTP is actually sent but the response timing matches the dispatch-success path to defeat enumeration via side-channel inference.

FieldRequiredTypeDescription
passwordResetIdYesstring
nextYesstring
passwordResetIdExpiresAtYesstring30-minute TTL; OTP has 5-minute TTL.

PasswordResetRequest

FieldRequiredTypeDescription
passwordResetIdYesstring
otpYesstring
newPasswordYesstringThe new password, validated against the YesCash password policy per `§A.6.5` (same policy as registration-time).

PasswordResetResponse

Polymorphic response per `§A.9.3.4`: - Standalone path: `next = LOGIN` directs the ctomer-app to the login screen; the ctomer re-authenticates via `/auth/start` + `/auth/login` with the fresh password. - Carry-forward path: `next = REGISTER_COMPLETE` directs the ctomer- app to re-call `/auth/register/set-password` with the reactivated `registrationId` and the fresh password. On successful reset the BFF invokes Cognito `AdminerGlobalSignOut` per `§A.9.3.5` Q8 — all existing refresh-tokens across the ctomer's active partner-app sessions are invalidated.

FieldRequiredTypeDescription

PasswordResetResponseStandalone

FieldRequiredTypeDescription
nextYesstring

PasswordResetResponseCarryForward

FieldRequiredTypeDescription
nextYesstring
registrationIdYesstring
registrationIdExpiresAtYesstringRefreshed 30-minute TTL per the Q5 pae-and-refresh decision.

DeviceMetadata

Informational-only device metadata per `§A.11.2.3`. NOT load-bearing for cryptographic verification per ADR 25 v1.1 `§D5.1`; the substantive binding is the public key, not the metadata. Captured for fraud-team forensics. Partner-app collects from the device's platform APIs.

FieldRequiredTypeDescription
deviceModelNostring
osVersionNostring
appVersionNostring
platformNostring

DeviceRegistrationStartRequest

Request for `POST /v1/auth/device-registration/start`. Two legal call shapes: - First call (no `stepUpToken`): onboarding-time registration for a ctomer who has no active device, OR initial step-up-required probe for a ctomer who already has an active device. - Retry call (with `stepUpToken`): re-registration after the liveness step-up flow per `§A.11.4` choreography.

FieldRequiredTypeDescription
deviceMetadataNoDeviceMetadata
stepUpTokenNostringSingle-e 10-minute-TTL token issued by the liveness webview closure per `§A.11.2.4`. Present on the re-registration retry call following a `202` step-up-required response.

DeviceRegistrationStartResponse

Polymorphic response per `§A.11.2.5` / `§A.11.2.6`: - `201 Created` happy-path: registration challenge issued. - `202 Accepted` step-up-required: ctomer has existing active registration; partner-app mt route to the liveness webview before retrying with `stepUpToken`.

FieldRequiredTypeDescription

DeviceRegistrationStartHappyPath

FieldRequiredTypeDescription
deviceRegistrationIdYesstringPrincipal-issued per-attempt identifier (ULID, `DRG-` prefix).
registrationChallengeYesstringBase64url-encoded 32 bytes (256 bits) of cryptographically-random data. Per-`deviceRegistrationId` single-e. The ctomer's freshly-generated device private key signs over this challenge in the `DeviceRegistrationProof` JWS at `/complete`.
expiresAtYesstring5-minute TTL from issuance.
publicKeyRequirementsYesobject
stepUpAppliedYesboolean`false` for onboarding-time first registration; `true` after successful re-registration with `stepUpToken` per `§A.11.4`.

DeviceRegistrationStartStepUpRequired

FieldRequiredTypeDescription
stepUpRequiredYesstring
kycSessionIdYesstring
livenessWebviewUrlYesstringPrincipal-served webview URL per `§B.4 / §B.5` pattern. The ctomer-app opens this in a webview; on successful liveness the principal emits a webview-closure signal carrying a `stepUpToken` (10-min TTL); the ctomer-app re-calls `/device-registration/start` with the token.
expiresAtYesstring
nextStepYesstring

PublicKeyJwk

RSA public key in JWK form per RFC 7517. Submitted at `/device-registration/complete` for principal-side registration. Constraints per ADR 25 v1.1 `§D5.1` + App C `§C.0.2`: `kty=RSA`, `alg=RS256`, `e=sig`, modul ≥ 2048 bits.

FieldRequiredTypeDescription
ktyYesstring
nYesstringBase64url-encoded RSA modul (≥ 2048 bits).
eYesstringBase64url-encoded RSA exponent.
kidNostringDevice-or-app-generated key identifier the device retains for its own bookkeeping. Opaque to the principal at registration; the principal correlates by `publicKeyThumbprint` (RFC 7638 SHA-256), not `kid`. After successful registration the principal-allocated canonical `deviceId` is ed by the partner-app for subsequent `§B.12` calls.
algYesstring
useYesstring

DeviceRegistrationProof

**Device-registration proof JWS (Compact Serialization per RFC 7515).** Signed by the ctomer device's freshly-generated RSA private key (corresponding to the `publicKey` submitted alongside) per App C `§C.2`. **Algorithm: RS256 + RSA-2048 minimum.** Per App C `§C.0.2` + ADR 25 v1.1 `§D5.1`. v1 closes on RS256 only. **The Step 23 Appendix B flow B.3 step 5 erratum** — which silently surfaced `alg: ES256` for the device-bound assertion — **is corrected by App C `§C.0.4` and `§C.3` and is superseded by this schema.** The device-registration-proof JWS (App C `§C.2`) and the device-bound-assertion JWS at `§B.12` (App C `§C.3`) both e RS256. **Protected header.** `{"alg":"RS256","kid":"<publicKey.kid>","typ":"JWT"}`. Closed-header discipline per App C `§C.1.3` (no `crit`, `x5c`). **JCS-canonical payload (RFC 8785)** per App C `§C.2.4`: ```json { "device_registration_id": "DRG-01HX...", "iat": 1715608822, "public_key_thumbprint": "<RFC 7638 SHA-256 JWK thumbprint of publicKey>", "registration_challenge": "<base64url echo of start-issued challenge>", "registration_proof_v1": "v1" } ``` **Verification chain (principal-side at `/complete`)** per App C `§C.2.7`: 1. Verify `header.alg == "RS256"`; reject with `device.publicKeyAlgorithmUnsupported` otherwise. 2. Verify `submittedPublicKey.kty == "RSA"`, `submittedPublicKey.alg == "RS256"`, `submittedPublicKey.modul_bits >= 2048`, `submittedPublicKey.e == "sig"`. 3. Self-signature verification: RSASSA-PKCS1-v1_5-SHA-256 against `submittedPublicKey` over JWS Signing Input. Reject with `device.registrationProofInvalid` on failure. 4. Challenge binding: `payload.registration_challenge == expectehallenge`. Reject with `device.registrationProofChallengeMismatch`. 5. Thumbprint binding: RFC 7638 SHA-256 JWK thumbprint of `submittedPublicKey` equals `payload.public_key_thumbprint`. Reject with `device.registrationProofThumbprintMismatch`. The verbatim JWS string is preserved in the principal-side audit trail per `§A.13.5` + ADR 25 `§D5.2` evidence pattern. **The payload does NOT carry a `master_ctomer_id` claim.** Ctomer- binding is at the registration-record layer per `§A.11.3` + ADR 25 `§D5.1` — the principal looks up the JWT-resolved `(MasterCtomerId, PartnerId)` from `iam.requestContext_Resolve` and binds the registration record to that ctomer.

FieldRequiredTypeDescription

DeviceRegistrationCompleteRequest

FieldRequiredTypeDescription
deviceRegistrationIdYesstringEcho of `/start` response.
publicKeyYesPublicKeyJwk
registrationProofYesDeviceRegistrationProof

DeviceRegistrationCompleteResponse

FieldRequiredTypeDescription
deviceIdYesstringPrincipal-allocated stable per-device identifier (ULID, `DEV-` prefix). The partner-app stores this for e in the device-bound assertion at `§B.12` transfer-confirmation calls (`device_id` claim per App C `§C.3`).
registeredAtYesstring
publicKeyThumbprintYesstringRFC 7638 SHA-256 JWK thumbprint, base64url-encoded. Echo of the submitted thumbprint per defence-in-depth.
stepUpAppliedYesboolean`true` if the registration was preceded by liveness step-up (re-registration path); `false` for onboarding-time first registration.
priorDeviceIdDeactivatedNostringThe prior `deviceId` that was deactivated on re-registration success, if any. `null` for first-time registration.