From 6dc6521296198079eb2b0121124cfe5158563c04 Mon Sep 17 00:00:00 2001 From: carsonp6 Date: Wed, 19 Aug 2026 18:15:01 -0700 Subject: [PATCH 1/6] feat: add WalletOperationProcessing 200 response to add-credential and challenge endpoints MIME-Version: 1.0 Content-Type: text/plain; charset=UTF-8 Content-Transfer-Encoding: 8bit Grid's embedded-wallet auth endpoints forward to Turnkey, whose consensus- or approval-gated activities are only optimistically synchronous. When the underlying activity is still in flight, POST /auth/credentials and POST /auth/credentials/{id}/challenge now document a 200 response carrying a WalletOperationProcessing body (status: "PROCESSING") instead of forcing a 5xx or blocking — the client re-sends the byte-identical request until the operation settles, and the backend reconciles it to terminal on its own. WalletOperationProcessing lives in components/schemas/common since follow-on endpoints (contact update, session refresh, OTP verify) will reuse the same shape. The challenge endpoint's prior single-schema 200 response becomes an anyOf-wrapped AuthCredentialChallengeResponse (not a discriminated oneOf — WalletOperationProcessing has no shared discriminator field with AuthCredentialResponseOneOf's `type`). Co-Authored-By: Claude Opus 4.8 --- mintlify/openapi.yaml | 36 +++++++++++++++++-- openapi.yaml | 36 +++++++++++++++++-- .../auth/AuthCredentialChallengeResponse.yaml | 11 ++++++ .../common/WalletOperationProcessing.yaml | 30 ++++++++++++++++ openapi/paths/auth/auth_credentials.yaml | 12 +++++++ .../auth/auth_credentials_{id}_challenge.yaml | 8 +++-- 6 files changed, 127 insertions(+), 6 deletions(-) create mode 100644 openapi/components/schemas/auth/AuthCredentialChallengeResponse.yaml create mode 100644 openapi/components/schemas/common/WalletOperationProcessing.yaml diff --git a/mintlify/openapi.yaml b/mintlify/openapi.yaml index e2b98ad11..15ce5a796 100644 --- a/mintlify/openapi.yaml +++ b/mintlify/openapi.yaml @@ -5968,6 +5968,12 @@ paths: - internal - hybrid responses: + '200': + description: 'An additional-credential setup leg''s underlying wallet-provider activity is still in flight — a `WalletOperationProcessing` body with `status: "PROCESSING"`. The client re-sends the byte-identical stamped retry (same `Request-Id`) until the credential is added; the backend also reconciles the activity to terminal on its own.' + content: + application/json: + schema: + $ref: '#/components/schemas/WalletOperationProcessing' '201': description: Authentication credential created successfully. The body is the created `AuthMethod`. For `EMAIL_OTP`, the nickname is the customer email tied to the internal account; for `SMS_OTP`, it is the customer phone number. OTP responses that trigger a secure OTP challenge carry `otpEncryptionTargetBundle` — the HPKE target bundle the client uses to encrypt the OTP attempt on the subsequent `POST /auth/credentials/{id}/verify`. First-time EMAIL_OTP wallet bootstrap responses may omit that bundle; if it is absent, call `POST /auth/credentials/{id}/challenge` for the new credential to issue a fresh OTP and receive `otpEncryptionTargetBundle` before verifying. For `PASSKEY`, the credential must be authenticated for the first time via `POST /auth/credentials/{id}/challenge` followed by `POST /auth/credentials/{id}/verify` to produce a session — there is no inline authentication challenge on the registration response. content: @@ -6391,11 +6397,11 @@ paths: value: {} responses: '200': - description: Challenge re-issued for the authentication credential. For `EMAIL_OTP` and `SMS_OTP` the body is a plain `AuthMethod` and a new OTP has been sent. For `PASSKEY` the body is a `PasskeyAuthChallenge` carrying the passkey `credentialId`, freshly issued `challenge`, `requestId`, and `expiresAt` required to complete reauthentication via `POST /auth/credentials/{id}/verify`. + description: Challenge re-issued for the authentication credential. For `EMAIL_OTP` and `SMS_OTP` the body is a plain `AuthMethod` and a new OTP has been sent. For `PASSKEY` the body is a `PasskeyAuthChallenge` carrying the passkey `credentialId`, freshly issued `challenge`, `requestId`, and `expiresAt` required to complete reauthentication via `POST /auth/credentials/{id}/verify`. When the OTP send's underlying wallet-provider activity is still in flight, the body is instead a `WalletOperationProcessing` carrying a `PROCESSING` status — re-request the challenge until the send settles; the backend also reconciles it to terminal on its own. content: application/json: schema: - $ref: '#/components/schemas/AuthCredentialResponseOneOf' + $ref: '#/components/schemas/AuthCredentialChallengeResponse' examples: emailOtp: summary: Email OTP challenge re-issued @@ -23455,6 +23461,26 @@ components: SMS_OTP: '#/components/schemas/SmsOtpCredentialCreateRequest' OAUTH: '#/components/schemas/OauthCredentialCreateRequest' PASSKEY: '#/components/schemas/PasskeyCredentialCreateRequest' + WalletOperationProcessing: + title: Wallet Operation Processing + description: '`200` response returned by an Embedded Wallet operation that the wallet provider has accepted but not yet settled — a consensus- or approval-gated activity that is still in flight. It is not an error and needs no client action beyond patience: the backend reconciles the operation to its terminal state on its own. The client MAY re-send the byte-identical request to converge sooner; the request is idempotent and returns the settled success response once the operation completes.' + type: object + required: + - status + properties: + status: + type: string + enum: + - PROCESSING + description: Always `PROCESSING`. Marks a still-in-flight operation whose terminal result is not yet available. + example: PROCESSING + message: + type: string + description: Human-readable explanation that the operation is still being processed and the same request may be retried. + example: This login is still being processed. Retry the same request in a moment. + example: + status: PROCESSING + message: This login is still being processed. Retry the same request in a moment. AuthMethodResponse: title: Auth Method Response description: |- @@ -23703,6 +23729,12 @@ components: EMAIL_OTP: '#/components/schemas/AuthMethodResponse' SMS_OTP: '#/components/schemas/AuthMethodResponse' PASSKEY: '#/components/schemas/PasskeyAuthChallenge' + AuthCredentialChallengeResponse: + title: Auth Credential Challenge Response + description: 'Response body for `POST /auth/credentials/{id}/challenge`. Normally an `AuthCredentialResponseOneOf` — the re-issued challenge or re-sent OTP. When the OTP send''s underlying wallet-provider activity is still in flight, this is instead a `WalletOperationProcessing` body with `status: "PROCESSING"` — re-request the challenge until the send settles; the backend also reconciles it to terminal on its own.' + anyOf: + - $ref: '#/components/schemas/AuthCredentialResponseOneOf' + - $ref: '#/components/schemas/WalletOperationProcessing' SessionListResponse: type: object required: diff --git a/openapi.yaml b/openapi.yaml index e2b98ad11..15ce5a796 100644 --- a/openapi.yaml +++ b/openapi.yaml @@ -5968,6 +5968,12 @@ paths: - internal - hybrid responses: + '200': + description: 'An additional-credential setup leg''s underlying wallet-provider activity is still in flight — a `WalletOperationProcessing` body with `status: "PROCESSING"`. The client re-sends the byte-identical stamped retry (same `Request-Id`) until the credential is added; the backend also reconciles the activity to terminal on its own.' + content: + application/json: + schema: + $ref: '#/components/schemas/WalletOperationProcessing' '201': description: Authentication credential created successfully. The body is the created `AuthMethod`. For `EMAIL_OTP`, the nickname is the customer email tied to the internal account; for `SMS_OTP`, it is the customer phone number. OTP responses that trigger a secure OTP challenge carry `otpEncryptionTargetBundle` — the HPKE target bundle the client uses to encrypt the OTP attempt on the subsequent `POST /auth/credentials/{id}/verify`. First-time EMAIL_OTP wallet bootstrap responses may omit that bundle; if it is absent, call `POST /auth/credentials/{id}/challenge` for the new credential to issue a fresh OTP and receive `otpEncryptionTargetBundle` before verifying. For `PASSKEY`, the credential must be authenticated for the first time via `POST /auth/credentials/{id}/challenge` followed by `POST /auth/credentials/{id}/verify` to produce a session — there is no inline authentication challenge on the registration response. content: @@ -6391,11 +6397,11 @@ paths: value: {} responses: '200': - description: Challenge re-issued for the authentication credential. For `EMAIL_OTP` and `SMS_OTP` the body is a plain `AuthMethod` and a new OTP has been sent. For `PASSKEY` the body is a `PasskeyAuthChallenge` carrying the passkey `credentialId`, freshly issued `challenge`, `requestId`, and `expiresAt` required to complete reauthentication via `POST /auth/credentials/{id}/verify`. + description: Challenge re-issued for the authentication credential. For `EMAIL_OTP` and `SMS_OTP` the body is a plain `AuthMethod` and a new OTP has been sent. For `PASSKEY` the body is a `PasskeyAuthChallenge` carrying the passkey `credentialId`, freshly issued `challenge`, `requestId`, and `expiresAt` required to complete reauthentication via `POST /auth/credentials/{id}/verify`. When the OTP send's underlying wallet-provider activity is still in flight, the body is instead a `WalletOperationProcessing` carrying a `PROCESSING` status — re-request the challenge until the send settles; the backend also reconciles it to terminal on its own. content: application/json: schema: - $ref: '#/components/schemas/AuthCredentialResponseOneOf' + $ref: '#/components/schemas/AuthCredentialChallengeResponse' examples: emailOtp: summary: Email OTP challenge re-issued @@ -23455,6 +23461,26 @@ components: SMS_OTP: '#/components/schemas/SmsOtpCredentialCreateRequest' OAUTH: '#/components/schemas/OauthCredentialCreateRequest' PASSKEY: '#/components/schemas/PasskeyCredentialCreateRequest' + WalletOperationProcessing: + title: Wallet Operation Processing + description: '`200` response returned by an Embedded Wallet operation that the wallet provider has accepted but not yet settled — a consensus- or approval-gated activity that is still in flight. It is not an error and needs no client action beyond patience: the backend reconciles the operation to its terminal state on its own. The client MAY re-send the byte-identical request to converge sooner; the request is idempotent and returns the settled success response once the operation completes.' + type: object + required: + - status + properties: + status: + type: string + enum: + - PROCESSING + description: Always `PROCESSING`. Marks a still-in-flight operation whose terminal result is not yet available. + example: PROCESSING + message: + type: string + description: Human-readable explanation that the operation is still being processed and the same request may be retried. + example: This login is still being processed. Retry the same request in a moment. + example: + status: PROCESSING + message: This login is still being processed. Retry the same request in a moment. AuthMethodResponse: title: Auth Method Response description: |- @@ -23703,6 +23729,12 @@ components: EMAIL_OTP: '#/components/schemas/AuthMethodResponse' SMS_OTP: '#/components/schemas/AuthMethodResponse' PASSKEY: '#/components/schemas/PasskeyAuthChallenge' + AuthCredentialChallengeResponse: + title: Auth Credential Challenge Response + description: 'Response body for `POST /auth/credentials/{id}/challenge`. Normally an `AuthCredentialResponseOneOf` — the re-issued challenge or re-sent OTP. When the OTP send''s underlying wallet-provider activity is still in flight, this is instead a `WalletOperationProcessing` body with `status: "PROCESSING"` — re-request the challenge until the send settles; the backend also reconciles it to terminal on its own.' + anyOf: + - $ref: '#/components/schemas/AuthCredentialResponseOneOf' + - $ref: '#/components/schemas/WalletOperationProcessing' SessionListResponse: type: object required: diff --git a/openapi/components/schemas/auth/AuthCredentialChallengeResponse.yaml b/openapi/components/schemas/auth/AuthCredentialChallengeResponse.yaml new file mode 100644 index 000000000..1dd58d310 --- /dev/null +++ b/openapi/components/schemas/auth/AuthCredentialChallengeResponse.yaml @@ -0,0 +1,11 @@ +title: Auth Credential Challenge Response +description: >- + Response body for `POST /auth/credentials/{id}/challenge`. Normally an + `AuthCredentialResponseOneOf` — the re-issued challenge or re-sent OTP. + When the OTP send's underlying wallet-provider activity is still in + flight, this is instead a `WalletOperationProcessing` body with + `status: "PROCESSING"` — re-request the challenge until the send + settles; the backend also reconciles it to terminal on its own. +anyOf: + - $ref: ./AuthCredentialResponseOneOf.yaml + - $ref: ../common/WalletOperationProcessing.yaml diff --git a/openapi/components/schemas/common/WalletOperationProcessing.yaml b/openapi/components/schemas/common/WalletOperationProcessing.yaml new file mode 100644 index 000000000..6eecae4ad --- /dev/null +++ b/openapi/components/schemas/common/WalletOperationProcessing.yaml @@ -0,0 +1,30 @@ +title: Wallet Operation Processing +description: >- + `200` response returned by an Embedded Wallet operation that the wallet + provider has accepted but not yet settled — a consensus- or + approval-gated activity that is still in flight. It is not an error and + needs no client action beyond patience: the backend reconciles the + operation to its terminal state on its own. The client MAY re-send the + byte-identical request to converge sooner; the request is idempotent + and returns the settled success response once the operation completes. +type: object +required: + - status +properties: + status: + type: string + enum: + - PROCESSING + description: >- + Always `PROCESSING`. Marks a still-in-flight operation whose + terminal result is not yet available. + example: PROCESSING + message: + type: string + description: >- + Human-readable explanation that the operation is still being + processed and the same request may be retried. + example: This login is still being processed. Retry the same request in a moment. +example: + status: PROCESSING + message: This login is still being processed. Retry the same request in a moment. diff --git a/openapi/paths/auth/auth_credentials.yaml b/openapi/paths/auth/auth_credentials.yaml index f0232cbe2..7cc6f4b81 100644 --- a/openapi/paths/auth/auth_credentials.yaml +++ b/openapi/paths/auth/auth_credentials.yaml @@ -90,6 +90,18 @@ post: - internal - hybrid responses: + '200': + description: >- + An additional-credential setup leg's underlying wallet-provider + activity is still in flight — a `WalletOperationProcessing` body + with `status: "PROCESSING"`. The client re-sends the + byte-identical stamped retry (same `Request-Id`) until the + credential is added; the backend also reconciles the activity + to terminal on its own. + content: + application/json: + schema: + $ref: ../../components/schemas/common/WalletOperationProcessing.yaml '201': description: >- Authentication credential created successfully. The body is the diff --git a/openapi/paths/auth/auth_credentials_{id}_challenge.yaml b/openapi/paths/auth/auth_credentials_{id}_challenge.yaml index 5df07e520..dc2dc7e05 100644 --- a/openapi/paths/auth/auth_credentials_{id}_challenge.yaml +++ b/openapi/paths/auth/auth_credentials_{id}_challenge.yaml @@ -80,11 +80,15 @@ post: new OTP has been sent. For `PASSKEY` the body is a `PasskeyAuthChallenge` carrying the passkey `credentialId`, freshly issued `challenge`, `requestId`, and `expiresAt` required to complete reauthentication - via `POST /auth/credentials/{id}/verify`. + via `POST /auth/credentials/{id}/verify`. When the OTP send's + underlying wallet-provider activity is still in flight, the body + is instead a `WalletOperationProcessing` carrying a `PROCESSING` + status — re-request the challenge until the send settles; the + backend also reconciles it to terminal on its own. content: application/json: schema: - $ref: ../../components/schemas/auth/AuthCredentialResponseOneOf.yaml + $ref: ../../components/schemas/auth/AuthCredentialChallengeResponse.yaml examples: emailOtp: summary: Email OTP challenge re-issued From c9451ce2948f5c92c5a65f70d55bca50fb3c01cc Mon Sep 17 00:00:00 2001 From: carsonp6 Date: Wed, 19 Aug 2026 23:35:14 -0700 Subject: [PATCH 2/6] feat: extend WalletOperationProcessing to verify, session-refresh, and contact-update MIME-Version: 1.0 Content-Type: text/plain; charset=UTF-8 Content-Transfer-Encoding: 8bit Rounds out the 200/PROCESSING contract to the remaining Turnkey sync-SM endpoints (SP-3579), matching the reshape stack's actual return sites: - POST /auth/credentials/{id}/verify (VERIFY_OTP/OTP_LOGIN/OAUTH_LOGIN/ STAMP_LOGIN): 200 widened to a new AuthCredentialVerifyResponse wrapper — anyOf: [AuthSession, WalletOperationProcessing]. - POST /auth/sessions/{id}/refresh (CREATE_READ_WRITE_SESSION): new 200 sibling to the existing 201, single $ref to WalletOperationProcessing (no union needed — 201 is a distinct success code, same shape as the add-credential endpoint from the prior commit). - PATCH /customers/{customerId} (contact-update: email/phone + tied OTP credentials): 200 widened to a new CustomerUpdateResponse wrapper — anyOf: [CustomerOneOf, WalletOperationProcessing]. CustomerOneOf keeps its own discriminator untouched; get/delete on this resource still return CustomerOneOf directly, unaffected. Same anyOf-over-oneOf reasoning as the challenge endpoint: none of AuthSession or CustomerOneOf share a discriminator field with WalletOperationProcessing's `status`, so a discriminated oneOf doesn't fit and this repo's spectral rules require one on every oneOf. Existing 202 responses on these endpoints are untouched — they signal a different state (client must sign and resubmit a challenge), not "still settling." No inconsistency found across the reshape stack's five endpoint groups (this commit's three plus the prior commit's add-credential and challenge): all uniformly answer still-in-flight activity with 200/PROCESSING, never 202. Co-Authored-By: Claude Opus 4.8 --- mintlify/openapi.yaml | 66 ++++++++++++------- openapi.yaml | 66 ++++++++++++------- .../auth/AuthCredentialVerifyResponse.yaml | 12 ++++ .../customers/CustomerUpdateResponse.yaml | 13 ++++ .../auth/auth_credentials_{id}_verify.yaml | 10 ++- .../auth/auth_sessions_{id}_refresh.yaml | 11 ++++ .../customers/customers_{customerId}.yaml | 8 ++- 7 files changed, 134 insertions(+), 52 deletions(-) create mode 100644 openapi/components/schemas/auth/AuthCredentialVerifyResponse.yaml create mode 100644 openapi/components/schemas/customers/CustomerUpdateResponse.yaml diff --git a/mintlify/openapi.yaml b/mintlify/openapi.yaml index 15ce5a796..5002c6bde 100644 --- a/mintlify/openapi.yaml +++ b/mintlify/openapi.yaml @@ -740,11 +740,11 @@ paths: phoneNumber: '+14155559876' responses: '200': - description: Customer updated successfully. For Embedded Wallet email or SMS auth phone updates, this is returned only on the signed retry after the customer contact field and all tied matching OTP credentials have been updated. + description: Customer updated successfully. For Embedded Wallet email or SMS auth phone updates, this is returned only on the signed retry after the customer contact field and all tied matching OTP credentials have been updated. When the contact update's underlying wallet-provider activity is still in flight, the body is instead a `WalletOperationProcessing` carrying a `PROCESSING` status — re-send the byte-identical signed retry until it settles; the backend also reconciles it to terminal on its own. content: application/json: schema: - $ref: '#/components/schemas/CustomerOneOf' + $ref: '#/components/schemas/CustomerUpdateResponse' '202': description: Challenge issued for an Embedded Wallet email or SMS auth phone update. The response contains `payloadToSign` plus a `requestId`. Build an API-key stamp over `payloadToSign` with the session API keypair from a verified authentication credential on one of the customer's tied Embedded Wallets, then retry the same request with `Grid-Wallet-Signature` and `Request-Id`. content: @@ -6300,11 +6300,11 @@ paths: signature: MEUCIQDYXBOpCWSWq2Ll4558GJKD2RoWg958lvJSB_GdeokxogIgWuEVQ7ee6AswQY0OsuQ6y8Ks6jhd45bDx92wjXKs900 responses: '200': - description: Authentication credential verified and session issued + description: 'Authentication credential verified and session issued (`AuthSession`); or, when the underlying wallet-provider activity is still in flight, a `WalletOperationProcessing` body with `status: "PROCESSING"` — the client re-sends the byte-identical request until it gets the session, and the backend also reconciles the activity to terminal on its own.' content: application/json: schema: - $ref: '#/components/schemas/AuthSession' + $ref: '#/components/schemas/AuthCredentialVerifyResponse' '202': description: Verification challenge issued. Returned only for OTP credentials, on the first leg of the secure OTP login flow. Build an API-key stamp over `payloadToSign` (the `verificationToken`) with the TEK keypair the client generated for this login, then resubmit the same request with that full stamp as `Grid-Wallet-Signature` and `requestId` echoed as `Request-Id` to receive the issued session on the signed retry. content: @@ -6626,6 +6626,12 @@ paths: value: clientPublicKey: 02f45f2a22c908b9ce09a7150e514afd24627c401c38a4afc164e1ea783adaaa31 responses: + '200': + description: 'The session refresh''s underlying wallet-provider activity is still in flight — a `WalletOperationProcessing` body with `status: "PROCESSING"`. The client re-sends the byte-identical signed retry until it gets the `201`; the backend also reconciles the activity to terminal on its own.' + content: + application/json: + schema: + $ref: '#/components/schemas/WalletOperationProcessing' '201': description: New authentication session created successfully. content: @@ -13790,6 +13796,32 @@ components: mapping: INDIVIDUAL: '#/components/schemas/IndividualCustomerUpdateRequest' BUSINESS: '#/components/schemas/BusinessCustomerUpdateRequest' + WalletOperationProcessing: + title: Wallet Operation Processing + description: '`200` response returned by an Embedded Wallet operation that the wallet provider has accepted but not yet settled — a consensus- or approval-gated activity that is still in flight. It is not an error and needs no client action beyond patience: the backend reconciles the operation to its terminal state on its own. The client MAY re-send the byte-identical request to converge sooner; the request is idempotent and returns the settled success response once the operation completes.' + type: object + required: + - status + properties: + status: + type: string + enum: + - PROCESSING + description: Always `PROCESSING`. Marks a still-in-flight operation whose terminal result is not yet available. + example: PROCESSING + message: + type: string + description: Human-readable explanation that the operation is still being processed and the same request may be retried. + example: This login is still being processed. Retry the same request in a moment. + example: + status: PROCESSING + message: This login is still being processed. Retry the same request in a moment. + CustomerUpdateResponse: + title: Customer Update Response + description: 'Response body for `PATCH /customers/{customerId}`. Normally a `CustomerOneOf` — the updated customer. When the update is an Embedded Wallet email or SMS auth phone change and the underlying wallet-provider activity is still in flight on the signed retry, this is instead a `WalletOperationProcessing` body with `status: "PROCESSING"` — the client re-sends the byte-identical signed retry until it settles; the backend also reconciles the activity to terminal on its own.' + anyOf: + - $ref: '#/components/schemas/CustomerOneOf' + - $ref: '#/components/schemas/WalletOperationProcessing' SignedRequestChallenge: title: Signed Request Challenge type: object @@ -23461,26 +23493,6 @@ components: SMS_OTP: '#/components/schemas/SmsOtpCredentialCreateRequest' OAUTH: '#/components/schemas/OauthCredentialCreateRequest' PASSKEY: '#/components/schemas/PasskeyCredentialCreateRequest' - WalletOperationProcessing: - title: Wallet Operation Processing - description: '`200` response returned by an Embedded Wallet operation that the wallet provider has accepted but not yet settled — a consensus- or approval-gated activity that is still in flight. It is not an error and needs no client action beyond patience: the backend reconciles the operation to its terminal state on its own. The client MAY re-send the byte-identical request to converge sooner; the request is idempotent and returns the settled success response once the operation completes.' - type: object - required: - - status - properties: - status: - type: string - enum: - - PROCESSING - description: Always `PROCESSING`. Marks a still-in-flight operation whose terminal result is not yet available. - example: PROCESSING - message: - type: string - description: Human-readable explanation that the operation is still being processed and the same request may be retried. - example: This login is still being processed. Retry the same request in a moment. - example: - status: PROCESSING - message: This login is still being processed. Retry the same request in a moment. AuthMethodResponse: title: Auth Method Response description: |- @@ -23676,6 +23688,12 @@ components: format: date-time description: Timestamp after which the session is no longer valid and the session signing key must not be used to sign further requests. example: '2026-04-09T15:30:01Z' + AuthCredentialVerifyResponse: + title: Auth Credential Verify Response + description: 'Response body for `POST /auth/credentials/{id}/verify`. Normally an `AuthSession` — the issued session on successful verification. When the underlying wallet-provider activity is still in flight, this is instead a `WalletOperationProcessing` body with `status: "PROCESSING"` — the client re-sends the byte-identical request until it gets the session; the backend also reconciles the activity to terminal on its own.' + anyOf: + - $ref: '#/components/schemas/AuthSession' + - $ref: '#/components/schemas/WalletOperationProcessing' AuthCredentialChallengeRequest: title: Auth Credential Challenge Request description: Request body for `POST /auth/credentials/{id}/challenge`. Required when re-challenging a `PASSKEY` credential — must carry `clientPublicKey` so Grid can bake it into the session-creation payload the returned challenge is computed from. Ignored for `EMAIL_OTP` and `SMS_OTP`, where the credential type alone is sufficient because the OTP is delivered out-of-band. OAuth credentials do not use this endpoint; authenticate or reauthenticate them with `POST /auth/credentials/{id}/verify`. diff --git a/openapi.yaml b/openapi.yaml index 15ce5a796..5002c6bde 100644 --- a/openapi.yaml +++ b/openapi.yaml @@ -740,11 +740,11 @@ paths: phoneNumber: '+14155559876' responses: '200': - description: Customer updated successfully. For Embedded Wallet email or SMS auth phone updates, this is returned only on the signed retry after the customer contact field and all tied matching OTP credentials have been updated. + description: Customer updated successfully. For Embedded Wallet email or SMS auth phone updates, this is returned only on the signed retry after the customer contact field and all tied matching OTP credentials have been updated. When the contact update's underlying wallet-provider activity is still in flight, the body is instead a `WalletOperationProcessing` carrying a `PROCESSING` status — re-send the byte-identical signed retry until it settles; the backend also reconciles it to terminal on its own. content: application/json: schema: - $ref: '#/components/schemas/CustomerOneOf' + $ref: '#/components/schemas/CustomerUpdateResponse' '202': description: Challenge issued for an Embedded Wallet email or SMS auth phone update. The response contains `payloadToSign` plus a `requestId`. Build an API-key stamp over `payloadToSign` with the session API keypair from a verified authentication credential on one of the customer's tied Embedded Wallets, then retry the same request with `Grid-Wallet-Signature` and `Request-Id`. content: @@ -6300,11 +6300,11 @@ paths: signature: MEUCIQDYXBOpCWSWq2Ll4558GJKD2RoWg958lvJSB_GdeokxogIgWuEVQ7ee6AswQY0OsuQ6y8Ks6jhd45bDx92wjXKs900 responses: '200': - description: Authentication credential verified and session issued + description: 'Authentication credential verified and session issued (`AuthSession`); or, when the underlying wallet-provider activity is still in flight, a `WalletOperationProcessing` body with `status: "PROCESSING"` — the client re-sends the byte-identical request until it gets the session, and the backend also reconciles the activity to terminal on its own.' content: application/json: schema: - $ref: '#/components/schemas/AuthSession' + $ref: '#/components/schemas/AuthCredentialVerifyResponse' '202': description: Verification challenge issued. Returned only for OTP credentials, on the first leg of the secure OTP login flow. Build an API-key stamp over `payloadToSign` (the `verificationToken`) with the TEK keypair the client generated for this login, then resubmit the same request with that full stamp as `Grid-Wallet-Signature` and `requestId` echoed as `Request-Id` to receive the issued session on the signed retry. content: @@ -6626,6 +6626,12 @@ paths: value: clientPublicKey: 02f45f2a22c908b9ce09a7150e514afd24627c401c38a4afc164e1ea783adaaa31 responses: + '200': + description: 'The session refresh''s underlying wallet-provider activity is still in flight — a `WalletOperationProcessing` body with `status: "PROCESSING"`. The client re-sends the byte-identical signed retry until it gets the `201`; the backend also reconciles the activity to terminal on its own.' + content: + application/json: + schema: + $ref: '#/components/schemas/WalletOperationProcessing' '201': description: New authentication session created successfully. content: @@ -13790,6 +13796,32 @@ components: mapping: INDIVIDUAL: '#/components/schemas/IndividualCustomerUpdateRequest' BUSINESS: '#/components/schemas/BusinessCustomerUpdateRequest' + WalletOperationProcessing: + title: Wallet Operation Processing + description: '`200` response returned by an Embedded Wallet operation that the wallet provider has accepted but not yet settled — a consensus- or approval-gated activity that is still in flight. It is not an error and needs no client action beyond patience: the backend reconciles the operation to its terminal state on its own. The client MAY re-send the byte-identical request to converge sooner; the request is idempotent and returns the settled success response once the operation completes.' + type: object + required: + - status + properties: + status: + type: string + enum: + - PROCESSING + description: Always `PROCESSING`. Marks a still-in-flight operation whose terminal result is not yet available. + example: PROCESSING + message: + type: string + description: Human-readable explanation that the operation is still being processed and the same request may be retried. + example: This login is still being processed. Retry the same request in a moment. + example: + status: PROCESSING + message: This login is still being processed. Retry the same request in a moment. + CustomerUpdateResponse: + title: Customer Update Response + description: 'Response body for `PATCH /customers/{customerId}`. Normally a `CustomerOneOf` — the updated customer. When the update is an Embedded Wallet email or SMS auth phone change and the underlying wallet-provider activity is still in flight on the signed retry, this is instead a `WalletOperationProcessing` body with `status: "PROCESSING"` — the client re-sends the byte-identical signed retry until it settles; the backend also reconciles the activity to terminal on its own.' + anyOf: + - $ref: '#/components/schemas/CustomerOneOf' + - $ref: '#/components/schemas/WalletOperationProcessing' SignedRequestChallenge: title: Signed Request Challenge type: object @@ -23461,26 +23493,6 @@ components: SMS_OTP: '#/components/schemas/SmsOtpCredentialCreateRequest' OAUTH: '#/components/schemas/OauthCredentialCreateRequest' PASSKEY: '#/components/schemas/PasskeyCredentialCreateRequest' - WalletOperationProcessing: - title: Wallet Operation Processing - description: '`200` response returned by an Embedded Wallet operation that the wallet provider has accepted but not yet settled — a consensus- or approval-gated activity that is still in flight. It is not an error and needs no client action beyond patience: the backend reconciles the operation to its terminal state on its own. The client MAY re-send the byte-identical request to converge sooner; the request is idempotent and returns the settled success response once the operation completes.' - type: object - required: - - status - properties: - status: - type: string - enum: - - PROCESSING - description: Always `PROCESSING`. Marks a still-in-flight operation whose terminal result is not yet available. - example: PROCESSING - message: - type: string - description: Human-readable explanation that the operation is still being processed and the same request may be retried. - example: This login is still being processed. Retry the same request in a moment. - example: - status: PROCESSING - message: This login is still being processed. Retry the same request in a moment. AuthMethodResponse: title: Auth Method Response description: |- @@ -23676,6 +23688,12 @@ components: format: date-time description: Timestamp after which the session is no longer valid and the session signing key must not be used to sign further requests. example: '2026-04-09T15:30:01Z' + AuthCredentialVerifyResponse: + title: Auth Credential Verify Response + description: 'Response body for `POST /auth/credentials/{id}/verify`. Normally an `AuthSession` — the issued session on successful verification. When the underlying wallet-provider activity is still in flight, this is instead a `WalletOperationProcessing` body with `status: "PROCESSING"` — the client re-sends the byte-identical request until it gets the session; the backend also reconciles the activity to terminal on its own.' + anyOf: + - $ref: '#/components/schemas/AuthSession' + - $ref: '#/components/schemas/WalletOperationProcessing' AuthCredentialChallengeRequest: title: Auth Credential Challenge Request description: Request body for `POST /auth/credentials/{id}/challenge`. Required when re-challenging a `PASSKEY` credential — must carry `clientPublicKey` so Grid can bake it into the session-creation payload the returned challenge is computed from. Ignored for `EMAIL_OTP` and `SMS_OTP`, where the credential type alone is sufficient because the OTP is delivered out-of-band. OAuth credentials do not use this endpoint; authenticate or reauthenticate them with `POST /auth/credentials/{id}/verify`. diff --git a/openapi/components/schemas/auth/AuthCredentialVerifyResponse.yaml b/openapi/components/schemas/auth/AuthCredentialVerifyResponse.yaml new file mode 100644 index 000000000..451c61b8a --- /dev/null +++ b/openapi/components/schemas/auth/AuthCredentialVerifyResponse.yaml @@ -0,0 +1,12 @@ +title: Auth Credential Verify Response +description: >- + Response body for `POST /auth/credentials/{id}/verify`. Normally an + `AuthSession` — the issued session on successful verification. When + the underlying wallet-provider activity is still in flight, this is + instead a `WalletOperationProcessing` body with `status: "PROCESSING"` + — the client re-sends the byte-identical request until it gets the + session; the backend also reconciles the activity to terminal on its + own. +anyOf: + - $ref: ./AuthSession.yaml + - $ref: ../common/WalletOperationProcessing.yaml diff --git a/openapi/components/schemas/customers/CustomerUpdateResponse.yaml b/openapi/components/schemas/customers/CustomerUpdateResponse.yaml new file mode 100644 index 000000000..4896afb97 --- /dev/null +++ b/openapi/components/schemas/customers/CustomerUpdateResponse.yaml @@ -0,0 +1,13 @@ +title: Customer Update Response +description: >- + Response body for `PATCH /customers/{customerId}`. Normally a + `CustomerOneOf` — the updated customer. When the update is an + Embedded Wallet email or SMS auth phone change and the underlying + wallet-provider activity is still in flight on the signed retry, this + is instead a `WalletOperationProcessing` body with `status: + "PROCESSING"` — the client re-sends the byte-identical signed retry + until it settles; the backend also reconciles the activity to + terminal on its own. +anyOf: + - $ref: ./CustomerOneOf.yaml + - $ref: ../common/WalletOperationProcessing.yaml diff --git a/openapi/paths/auth/auth_credentials_{id}_verify.yaml b/openapi/paths/auth/auth_credentials_{id}_verify.yaml index ae40fcd88..e6d050957 100644 --- a/openapi/paths/auth/auth_credentials_{id}_verify.yaml +++ b/openapi/paths/auth/auth_credentials_{id}_verify.yaml @@ -140,11 +140,17 @@ post: signature: MEUCIQDYXBOpCWSWq2Ll4558GJKD2RoWg958lvJSB_GdeokxogIgWuEVQ7ee6AswQY0OsuQ6y8Ks6jhd45bDx92wjXKs900 responses: '200': - description: Authentication credential verified and session issued + description: >- + Authentication credential verified and session issued + (`AuthSession`); or, when the underlying wallet-provider + activity is still in flight, a `WalletOperationProcessing` body + with `status: "PROCESSING"` — the client re-sends the + byte-identical request until it gets the session, and the + backend also reconciles the activity to terminal on its own. content: application/json: schema: - $ref: ../../components/schemas/auth/AuthSession.yaml + $ref: ../../components/schemas/auth/AuthCredentialVerifyResponse.yaml '202': description: >- Verification challenge issued. Returned only for OTP credentials, diff --git a/openapi/paths/auth/auth_sessions_{id}_refresh.yaml b/openapi/paths/auth/auth_sessions_{id}_refresh.yaml index 9cf9182aa..4e5647547 100644 --- a/openapi/paths/auth/auth_sessions_{id}_refresh.yaml +++ b/openapi/paths/auth/auth_sessions_{id}_refresh.yaml @@ -73,6 +73,17 @@ post: value: clientPublicKey: 02f45f2a22c908b9ce09a7150e514afd24627c401c38a4afc164e1ea783adaaa31 responses: + '200': + description: >- + The session refresh's underlying wallet-provider activity is + still in flight — a `WalletOperationProcessing` body with + `status: "PROCESSING"`. The client re-sends the byte-identical + signed retry until it gets the `201`; the backend also + reconciles the activity to terminal on its own. + content: + application/json: + schema: + $ref: ../../components/schemas/common/WalletOperationProcessing.yaml '201': description: New authentication session created successfully. content: diff --git a/openapi/paths/customers/customers_{customerId}.yaml b/openapi/paths/customers/customers_{customerId}.yaml index 7f72bd663..05fa1f8ca 100644 --- a/openapi/paths/customers/customers_{customerId}.yaml +++ b/openapi/paths/customers/customers_{customerId}.yaml @@ -168,11 +168,15 @@ patch: Customer updated successfully. For Embedded Wallet email or SMS auth phone updates, this is returned only on the signed retry after the customer contact field and all tied matching OTP credentials have been - updated. + updated. When the contact update's underlying wallet-provider activity + is still in flight, the body is instead a `WalletOperationProcessing` + carrying a `PROCESSING` status — re-send the byte-identical signed + retry until it settles; the backend also reconciles it to terminal on + its own. content: application/json: schema: - $ref: ../../components/schemas/customers/CustomerOneOf.yaml + $ref: ../../components/schemas/customers/CustomerUpdateResponse.yaml '202': description: >- Challenge issued for an Embedded Wallet email or SMS auth phone update. From 881a75fe0166897f0c9b928f492c167e91d3816d Mon Sep 17 00:00:00 2001 From: carsonp6 Date: Thu, 20 Aug 2026 00:07:26 -0700 Subject: [PATCH 3/6] feat: unify revoke-credential and revoke-session onto 200/PROCESSING MIME-Version: 1.0 Content-Type: text/plain; charset=UTF-8 Content-Transfer-Encoding: 8bit DELETE /auth/credentials/{id} (revokeAuthCredential, DELETE_AUTHENTICATORS / DELETE_OAUTH_PROVIDERS) and DELETE /auth/sessions/{id} (revokeAuthSession, DELETE_API_KEYS) are moving off the older SP-3611 async 202+operationId shape onto the same sync-SM 200/PROCESSING contract as the other 5 reshape endpoints (Carson-approved unification; sparkcore side lands in parallel). Both get a new 200 sibling response, single $ref to common/WalletOperationProcessing — no union needed, matching the add-credential and session-refresh pattern (200 sits alongside a separate terminal code, here 204, not competing with it). The existing 202 (AuthSignedRequestChallenge, the sign-and-resubmit challenge leg) is untouched — that's a different state in the flow and stays as-is. The old still-processing 202+operationId shape was never documented in this spec, so there's nothing to remove. Co-Authored-By: Claude Opus 4.8 --- mintlify/openapi.yaml | 12 ++++++++++++ openapi.yaml | 12 ++++++++++++ openapi/paths/auth/auth_credentials_{id}.yaml | 11 +++++++++++ openapi/paths/auth/auth_sessions_{id}.yaml | 11 +++++++++++ 4 files changed, 46 insertions(+) diff --git a/mintlify/openapi.yaml b/mintlify/openapi.yaml index 5002c6bde..5544bf51f 100644 --- a/mintlify/openapi.yaml +++ b/mintlify/openapi.yaml @@ -6190,6 +6190,12 @@ paths: type: string example: Request:7c4a8d09-ca37-4e3e-9e0d-8c2b3e9a1f21 responses: + '200': + description: 'The revocation''s underlying wallet-provider activity is still in flight — a `WalletOperationProcessing` body with `status: "PROCESSING"`. The client re-sends the same signed `DELETE` until it settles; the backend also reconciles the revocation to terminal on its own.' + content: + application/json: + schema: + $ref: '#/components/schemas/WalletOperationProcessing' '202': description: Challenge issued. The response contains `payloadToSign` plus a `requestId`. Build an API-key stamp over `payloadToSign` with the session API keypair of an existing verified credential on the same internal account (other than the one being revoked), then echo `requestId` on the retry. content: @@ -6544,6 +6550,12 @@ paths: type: string example: Request:7c4a8d09-ca37-4e3e-9e0d-8c2b3e9a1f21 responses: + '200': + description: 'The revocation''s underlying wallet-provider activity is still in flight — a `WalletOperationProcessing` body with `status: "PROCESSING"`. The client re-sends the same signed `DELETE` until it settles; the backend also reconciles the revocation to terminal on its own.' + content: + application/json: + schema: + $ref: '#/components/schemas/WalletOperationProcessing' '202': description: Challenge issued. The response contains `payloadToSign` plus a `requestId`. Build an API-key stamp over `payloadToSign` with the session API keypair of a verified session on the same internal account, then echo `requestId` on the retry. content: diff --git a/openapi.yaml b/openapi.yaml index 5002c6bde..5544bf51f 100644 --- a/openapi.yaml +++ b/openapi.yaml @@ -6190,6 +6190,12 @@ paths: type: string example: Request:7c4a8d09-ca37-4e3e-9e0d-8c2b3e9a1f21 responses: + '200': + description: 'The revocation''s underlying wallet-provider activity is still in flight — a `WalletOperationProcessing` body with `status: "PROCESSING"`. The client re-sends the same signed `DELETE` until it settles; the backend also reconciles the revocation to terminal on its own.' + content: + application/json: + schema: + $ref: '#/components/schemas/WalletOperationProcessing' '202': description: Challenge issued. The response contains `payloadToSign` plus a `requestId`. Build an API-key stamp over `payloadToSign` with the session API keypair of an existing verified credential on the same internal account (other than the one being revoked), then echo `requestId` on the retry. content: @@ -6544,6 +6550,12 @@ paths: type: string example: Request:7c4a8d09-ca37-4e3e-9e0d-8c2b3e9a1f21 responses: + '200': + description: 'The revocation''s underlying wallet-provider activity is still in flight — a `WalletOperationProcessing` body with `status: "PROCESSING"`. The client re-sends the same signed `DELETE` until it settles; the backend also reconciles the revocation to terminal on its own.' + content: + application/json: + schema: + $ref: '#/components/schemas/WalletOperationProcessing' '202': description: Challenge issued. The response contains `payloadToSign` plus a `requestId`. Build an API-key stamp over `payloadToSign` with the session API keypair of a verified session on the same internal account, then echo `requestId` on the retry. content: diff --git a/openapi/paths/auth/auth_credentials_{id}.yaml b/openapi/paths/auth/auth_credentials_{id}.yaml index 0978bd58d..381dad8df 100644 --- a/openapi/paths/auth/auth_credentials_{id}.yaml +++ b/openapi/paths/auth/auth_credentials_{id}.yaml @@ -63,6 +63,17 @@ delete: type: string example: Request:7c4a8d09-ca37-4e3e-9e0d-8c2b3e9a1f21 responses: + '200': + description: >- + The revocation's underlying wallet-provider activity is still + in flight — a `WalletOperationProcessing` body with `status: + "PROCESSING"`. The client re-sends the same signed `DELETE` + until it settles; the backend also reconciles the revocation to + terminal on its own. + content: + application/json: + schema: + $ref: ../../components/schemas/common/WalletOperationProcessing.yaml '202': description: >- Challenge issued. The response contains `payloadToSign` plus a diff --git a/openapi/paths/auth/auth_sessions_{id}.yaml b/openapi/paths/auth/auth_sessions_{id}.yaml index 7b2fa0755..c2b121dbd 100644 --- a/openapi/paths/auth/auth_sessions_{id}.yaml +++ b/openapi/paths/auth/auth_sessions_{id}.yaml @@ -59,6 +59,17 @@ delete: type: string example: Request:7c4a8d09-ca37-4e3e-9e0d-8c2b3e9a1f21 responses: + '200': + description: >- + The revocation's underlying wallet-provider activity is still + in flight — a `WalletOperationProcessing` body with `status: + "PROCESSING"`. The client re-sends the same signed `DELETE` + until it settles; the backend also reconciles the revocation to + terminal on its own. + content: + application/json: + schema: + $ref: ../../components/schemas/common/WalletOperationProcessing.yaml '202': description: >- Challenge issued. The response contains `payloadToSign` plus a From e1168f32825705c173f0f0cb6f68ede3aeab6c24 Mon Sep 17 00:00:00 2001 From: carsonp6 Date: Thu, 20 Aug 2026 00:41:25 -0700 Subject: [PATCH 4/6] docs: document the 200/PROCESSING contract for integrators MIME-Version: 1.0 Content-Type: text/plain; charset=UTF-8 Content-Transfer-Encoding: 8bit Grid's Embedded Wallet auth endpoints forward to a wallet provider that is only optimistically synchronous, so the signed-retry step can settle non-terminal instead of completing inline. Document this as the normal case to build for, not a rare edge — it surfaces most when integrators have the most traffic, since provider-side consensus delay under load is exactly when a "rare" response becomes routine. Extends the canonical "signed-retry pattern" section (authentication.mdx, linked from managing-sessions.mdx for session refresh/revoke) with: - the WalletOperationProcessing body shape and what it means - the full list of the 7 endpoints that can return it - how to handle it: re-send the identical original request (not a fresh one), show a pending UI state, and rely on Grid's own reconciliation as a backstop - a callout that verify/challenge/customer-update share their terminal status code (200) with the still-processing response, so those three need a body check where the other four (201/204 terminal) don't Also drops a one-line pointer at each of the 7 endpoints' existing worked examples (add-credential, revoke-credential, verify, challenge, email-change, session refresh, session revoke) so a reader following just one flow still finds it. Co-Authored-By: Claude Opus 4.8 --- .../global-accounts/authentication.mdx | 49 +++++++++++++++++-- .../global-accounts/managing-sessions.mdx | 4 +- 2 files changed, 47 insertions(+), 6 deletions(-) diff --git a/mintlify/snippets/global-accounts/authentication.mdx b/mintlify/snippets/global-accounts/authentication.mdx index 1f91faeaa..594a79660 100644 --- a/mintlify/snippets/global-accounts/authentication.mdx +++ b/mintlify/snippets/global-accounts/authentication.mdx @@ -582,13 +582,15 @@ curl -X POST "$GRID_BASE_URL/auth/credentials/AuthMethod:019542f5-b3e7-1d02-0000 The TEK public key becomes the session API key. Unlike `OAUTH` and `PASSKEY` flows, `EMAIL_OTP` does **not** return `encryptedSessionSigningKey` — the client already holds the session signing key (the TEK private key it generated). +`verify` shares its terminal status code with the still-processing response: a `200` here is only a session if the body isn't `{ "status": "PROCESSING" }`. See handling a still-processing response — if you get `PROCESSING`, re-send the identical signed retry (same `encryptedOtpBundle`, `Grid-Wallet-Signature`, and `Request-Id`) until you get the session shown above. + **In sandbox, the OTP code is always `000000`** — encrypt that value in the bundle. The sandbox runs real HPKE end-to-end; the only shortcut is skipping email delivery. See Client keys for the encryption flow. ### Resending an OTP -If the code expires or the email didn't arrive, re-issue the challenge with `POST /auth/credentials/{id}/challenge`. This sends a fresh OTP email and leaves the `AuthMethod` otherwise untouched. +If the code expires or the email didn't arrive, re-issue the challenge with `POST /auth/credentials/{id}/challenge`. This sends a fresh OTP email and leaves the `AuthMethod` otherwise untouched. `challenge` can also return a still-processing `200` while the resend settles at the wallet provider — re-request the challenge until you get the `AuthMethod` back rather than a fresh OTP being sent twice. ```bash curl -X POST "$GRID_BASE_URL/auth/credentials/AuthMethod:019542f5-b3e7-1d02-0000-000000000004/challenge" \ @@ -605,7 +607,7 @@ Same pattern as the first activation: call `/challenge` to send a new OTP and re ### Changing the email OTP address -The `EMAIL_OTP` address comes from the customer email on file. To change it, update the customer with `PATCH /customers/{customerId}`. If the customer has tied Embedded Wallet `EMAIL_OTP` credentials, Grid returns a signed-retry challenge; stamp the returned `payloadToSign` with an active session signing key, then retry the same customer update with `Grid-Wallet-Signature` and `Request-Id`. Grid syncs the customer email and tied `EMAIL_OTP` credential email together. +The `EMAIL_OTP` address comes from the customer email on file. To change it, update the customer with `PATCH /customers/{customerId}`. If the customer has tied Embedded Wallet `EMAIL_OTP` credentials, Grid returns a signed-retry challenge; stamp the returned `payloadToSign` with an active session signing key, then retry the same customer update with `Grid-Wallet-Signature` and `Request-Id`. Grid syncs the customer email and tied `EMAIL_OTP` credential email together. The signed retry can also return a still-processing `200` while the tied credential update settles — re-send the identical customer update until you get the updated customer back. ## Managing credentials @@ -662,6 +664,10 @@ sequenceDiagram C->>C: stamp(payloadToSign, sessionPrivateKey) C->>IB: { stamp } IB->>G: Same request
Grid-Wallet-Signature: stamp
Request-Id: requestId + alt still settling at the wallet provider + G-->>IB: 200 { status: "PROCESSING" } + IB->>G: Re-send the identical signed request + end G-->>IB: 2xx (terminal success) IB-->>C: done ``` @@ -673,6 +679,41 @@ Key rules: - The retry must reach Grid before `expiresAt` (typically 5 minutes from issue). - The `requestId` is returned as `Request:` and is single-use; reusing one yields `401`. +### Handling a still-processing response + +The signed retry can come back `200` with a `WalletOperationProcessing` body instead of the terminal success shown above: + +```json +{ + "status": "PROCESSING", + "message": "This login is still being processed. Retry the same request in a moment." +} +``` + +This means Grid's wallet provider accepted the operation but hasn't settled it yet — the request is not lost, and nothing failed. **Design for this as the normal path, not a rare edge case.** It shows up whenever the provider needs an extra moment to reach consensus, and that happens more, not less, exactly when you have the most traffic — a spike in logins or registrations is the scenario where a "rare" response becomes routine. + +Every signed-retry endpoint can return it on the retry step: + +| Endpoint | Action | +|---|---| +| `POST /auth/credentials` | Add a credential | +| `POST /auth/credentials/{id}/challenge` | Re-send an OTP / re-issue a challenge | +| `POST /auth/credentials/{id}/verify` | Log in / verify a credential | +| `POST /auth/sessions/{id}/refresh` | Refresh a session | +| `PATCH /customers/{customerId}` | Change the email or phone tied to an `EMAIL_OTP` / `SMS_OTP` credential | +| `DELETE /auth/credentials/{id}` | Revoke a credential | +| `DELETE /auth/sessions/{id}` | Revoke a session | + +To handle it: + +1. **Re-send the exact same signed request** — identical body, `Grid-Wallet-Signature`, and `Request-Id`. Don't regenerate the stamp or start a fresh request; Grid correlates the retry with the same underlying operation and returns its real outcome once the provider settles, rather than starting a second one. +2. **Show a pending state in your UI** while you retry — "removing…" for a credential revoke, "signing in…" for a login — instead of treating `PROCESSING` as an error or leaving the caller blocked with no feedback. +3. **Keep retrying with backoff** until you get a terminal response — the success code above, or an error. Grid also reconciles the operation to its terminal state on its own, so even a client that stops retrying and checks back later (for example via `GET /auth/credentials` or `GET /auth/sessions`) will see the settled result. + + + `verify`, `challenge`, and the customer update endpoint already use `200` for their terminal success body (`AuthSession`, the challenge response, or the updated customer). For those three, `200` alone doesn't tell you which case you're in — check the response body's `status` field for `"PROCESSING"` to distinguish it from a settled success. The other four endpoints (`add credential`, `session refresh`, `revoke credential`, `revoke session`) use `201` or `204` on success, so a `200` by itself already means still-processing. + + ### Add an additional credential Requires an active session on an *existing* credential on the same account. The first call uses the normal credential-create body; Grid detects the pre-existing credential and responds `202` instead of `201`. `OAUTH` and `PASSKEY` are the typical additional credential types. `EMAIL_OTP` can be added back only after the existing email OTP credential has been removed, because each account supports one. @@ -720,7 +761,7 @@ Requires an active session on an *existing* credential on the same account. The }' ``` - **Response (201):** a plain `AuthMethod`. + **Response (201):** a plain `AuthMethod`. If the underlying wallet-provider activity is still settling, this returns `200 { status: "PROCESSING" }` instead — see handling a still-processing response; re-send the identical signed request until you get the `201`. Activate the new credential the same way you would activate the first credential of that type — `OAUTH` goes straight to `POST /auth/credentials/{id}/verify` with a fresh `clientPublicKey`; `EMAIL_OTP` uses the `otpEncryptionTargetBundle` from the signed-retry registration response when present, or first calls `POST /auth/credentials/{id}/challenge` if the bundle is absent; `PASSKEY` first calls `POST /auth/credentials/{id}/challenge` with the `clientPublicKey` to get a Grid-issued WebAuthn challenge, then `POST /auth/credentials/{id}/verify` with the assertion and the `Request-Id` header. @@ -764,6 +805,6 @@ A credential is revoked by signing with a session from **a different credential -H "Request-Id: Request:9f7a2c10-5e88-4fb1-bd0e-1c3a8e7b2d45" ``` - **Response:** `204 No Content`. All active sessions issued by the revoked credential are also revoked. + **Response:** `204 No Content`. All active sessions issued by the revoked credential are also revoked. If the revocation is still settling at the wallet provider, this returns `200 { status: "PROCESSING" }` instead — re-send the identical `DELETE` until you get the `204`, and show the credential as "removing…" in your UI in the meantime rather than assuming success or failure. diff --git a/mintlify/snippets/global-accounts/managing-sessions.mdx b/mintlify/snippets/global-accounts/managing-sessions.mdx index 8f03b14b4..fbe41030b 100644 --- a/mintlify/snippets/global-accounts/managing-sessions.mdx +++ b/mintlify/snippets/global-accounts/managing-sessions.mdx @@ -38,7 +38,7 @@ The list endpoint returns all **active** sessions; expired sessions are not incl ## Refresh a session -Session refresh creates a new session signing key from an existing active session. Use this when the customer is still present and the current session is close to expiration. If the session has already expired, reauthenticate with the original credential instead. +Session refresh creates a new session signing key from an existing active session. Use this when the customer is still present and the current session is close to expiration. If the session has already expired, reauthenticate with the original credential instead. Refresh uses the same signed-retry pattern as credential management, including the possibility of a still-processing `200` response on the signed retry — re-send the same request until you get the `201` below. @@ -82,7 +82,7 @@ Session refresh creates a new session signing key from an existing active sessio ## Revoke a session -Session revocation uses the same signed-retry pattern as credential management. Unlike credential revocation, a session **can revoke itself** — this is how self-logout works: sign with the session key you are about to invalidate. +Session revocation uses the same signed-retry pattern as credential management, including a possible still-processing `200` response on the signed retry. Unlike credential revocation, a session **can revoke itself** — this is how self-logout works: sign with the session key you are about to invalidate. From 17326cab8fa52449ce49c36c997d7aa05d813f26 Mon Sep 17 00:00:00 2001 From: carsonp6 Date: Thu, 20 Aug 2026 00:48:28 -0700 Subject: [PATCH 5/6] docs: drop traffic-spike framing from the 200/PROCESSING doc Tying "still processing" to traffic spikes could read as advice to throttle integrator traffic to avoid triggering it. Keep the "this is the normal path, build for it" framing without linking it to load. Co-Authored-By: Claude Opus 4.8 --- mintlify/snippets/global-accounts/authentication.mdx | 2 +- 1 file changed, 1 insertion(+), 1 deletion(-) diff --git a/mintlify/snippets/global-accounts/authentication.mdx b/mintlify/snippets/global-accounts/authentication.mdx index 594a79660..8d3c4bea2 100644 --- a/mintlify/snippets/global-accounts/authentication.mdx +++ b/mintlify/snippets/global-accounts/authentication.mdx @@ -690,7 +690,7 @@ The signed retry can come back `200` with a `WalletOperationProcessing` body ins } ``` -This means Grid's wallet provider accepted the operation but hasn't settled it yet — the request is not lost, and nothing failed. **Design for this as the normal path, not a rare edge case.** It shows up whenever the provider needs an extra moment to reach consensus, and that happens more, not less, exactly when you have the most traffic — a spike in logins or registrations is the scenario where a "rare" response becomes routine. +This means Grid's wallet provider accepted the operation but hasn't settled it yet — the request is not lost, and nothing failed. **Design for this as the normal path, not a rare edge case**: handle it the same way you handle the terminal success and error responses below, not as a bolted-on afterthought. Every signed-retry endpoint can return it on the retry step: From ee4ec5b12cd5dfef3b9613e9753de845646aa025 Mon Sep 17 00:00:00 2001 From: carsonp6 Date: Thu, 20 Aug 2026 18:01:47 -0700 Subject: [PATCH 6/6] =?UTF-8?q?Drop=20the=20PATCH-customers=20widening=20?= =?UTF-8?q?=E2=80=94=20contact=20updates=20are=20moving=20to=20dedicated?= =?UTF-8?q?=20endpoints?= MIME-Version: 1.0 Content-Type: text/plain; charset=UTF-8 Content-Transfer-Encoding: 8bit Removes the WalletOperationProcessing additions from PATCH /customers/{customerId} (the new CustomerUpdateResponse schema, the 200-response description, and the three supporting mentions in the auth guide) so this PR only covers the six auth-credential and auth-session endpoints. --- mintlify/openapi.yaml | 50 ++++++++----------- .../global-accounts/authentication.mdx | 5 +- openapi.yaml | 50 ++++++++----------- .../customers/CustomerUpdateResponse.yaml | 13 ----- .../customers/customers_{customerId}.yaml | 8 +-- 5 files changed, 48 insertions(+), 78 deletions(-) delete mode 100644 openapi/components/schemas/customers/CustomerUpdateResponse.yaml diff --git a/mintlify/openapi.yaml b/mintlify/openapi.yaml index 5544bf51f..1e90eb416 100644 --- a/mintlify/openapi.yaml +++ b/mintlify/openapi.yaml @@ -740,11 +740,11 @@ paths: phoneNumber: '+14155559876' responses: '200': - description: Customer updated successfully. For Embedded Wallet email or SMS auth phone updates, this is returned only on the signed retry after the customer contact field and all tied matching OTP credentials have been updated. When the contact update's underlying wallet-provider activity is still in flight, the body is instead a `WalletOperationProcessing` carrying a `PROCESSING` status — re-send the byte-identical signed retry until it settles; the backend also reconciles it to terminal on its own. + description: Customer updated successfully. For Embedded Wallet email or SMS auth phone updates, this is returned only on the signed retry after the customer contact field and all tied matching OTP credentials have been updated. content: application/json: schema: - $ref: '#/components/schemas/CustomerUpdateResponse' + $ref: '#/components/schemas/CustomerOneOf' '202': description: Challenge issued for an Embedded Wallet email or SMS auth phone update. The response contains `payloadToSign` plus a `requestId`. Build an API-key stamp over `payloadToSign` with the session API keypair from a verified authentication credential on one of the customer's tied Embedded Wallets, then retry the same request with `Grid-Wallet-Signature` and `Request-Id`. content: @@ -13808,32 +13808,6 @@ components: mapping: INDIVIDUAL: '#/components/schemas/IndividualCustomerUpdateRequest' BUSINESS: '#/components/schemas/BusinessCustomerUpdateRequest' - WalletOperationProcessing: - title: Wallet Operation Processing - description: '`200` response returned by an Embedded Wallet operation that the wallet provider has accepted but not yet settled — a consensus- or approval-gated activity that is still in flight. It is not an error and needs no client action beyond patience: the backend reconciles the operation to its terminal state on its own. The client MAY re-send the byte-identical request to converge sooner; the request is idempotent and returns the settled success response once the operation completes.' - type: object - required: - - status - properties: - status: - type: string - enum: - - PROCESSING - description: Always `PROCESSING`. Marks a still-in-flight operation whose terminal result is not yet available. - example: PROCESSING - message: - type: string - description: Human-readable explanation that the operation is still being processed and the same request may be retried. - example: This login is still being processed. Retry the same request in a moment. - example: - status: PROCESSING - message: This login is still being processed. Retry the same request in a moment. - CustomerUpdateResponse: - title: Customer Update Response - description: 'Response body for `PATCH /customers/{customerId}`. Normally a `CustomerOneOf` — the updated customer. When the update is an Embedded Wallet email or SMS auth phone change and the underlying wallet-provider activity is still in flight on the signed retry, this is instead a `WalletOperationProcessing` body with `status: "PROCESSING"` — the client re-sends the byte-identical signed retry until it settles; the backend also reconciles the activity to terminal on its own.' - anyOf: - - $ref: '#/components/schemas/CustomerOneOf' - - $ref: '#/components/schemas/WalletOperationProcessing' SignedRequestChallenge: title: Signed Request Challenge type: object @@ -23505,6 +23479,26 @@ components: SMS_OTP: '#/components/schemas/SmsOtpCredentialCreateRequest' OAUTH: '#/components/schemas/OauthCredentialCreateRequest' PASSKEY: '#/components/schemas/PasskeyCredentialCreateRequest' + WalletOperationProcessing: + title: Wallet Operation Processing + description: '`200` response returned by an Embedded Wallet operation that the wallet provider has accepted but not yet settled — a consensus- or approval-gated activity that is still in flight. It is not an error and needs no client action beyond patience: the backend reconciles the operation to its terminal state on its own. The client MAY re-send the byte-identical request to converge sooner; the request is idempotent and returns the settled success response once the operation completes.' + type: object + required: + - status + properties: + status: + type: string + enum: + - PROCESSING + description: Always `PROCESSING`. Marks a still-in-flight operation whose terminal result is not yet available. + example: PROCESSING + message: + type: string + description: Human-readable explanation that the operation is still being processed and the same request may be retried. + example: This login is still being processed. Retry the same request in a moment. + example: + status: PROCESSING + message: This login is still being processed. Retry the same request in a moment. AuthMethodResponse: title: Auth Method Response description: |- diff --git a/mintlify/snippets/global-accounts/authentication.mdx b/mintlify/snippets/global-accounts/authentication.mdx index 8d3c4bea2..f6813890e 100644 --- a/mintlify/snippets/global-accounts/authentication.mdx +++ b/mintlify/snippets/global-accounts/authentication.mdx @@ -607,7 +607,7 @@ Same pattern as the first activation: call `/challenge` to send a new OTP and re ### Changing the email OTP address -The `EMAIL_OTP` address comes from the customer email on file. To change it, update the customer with `PATCH /customers/{customerId}`. If the customer has tied Embedded Wallet `EMAIL_OTP` credentials, Grid returns a signed-retry challenge; stamp the returned `payloadToSign` with an active session signing key, then retry the same customer update with `Grid-Wallet-Signature` and `Request-Id`. Grid syncs the customer email and tied `EMAIL_OTP` credential email together. The signed retry can also return a still-processing `200` while the tied credential update settles — re-send the identical customer update until you get the updated customer back. +The `EMAIL_OTP` address comes from the customer email on file. To change it, update the customer with `PATCH /customers/{customerId}`. If the customer has tied Embedded Wallet `EMAIL_OTP` credentials, Grid returns a signed-retry challenge; stamp the returned `payloadToSign` with an active session signing key, then retry the same customer update with `Grid-Wallet-Signature` and `Request-Id`. Grid syncs the customer email and tied `EMAIL_OTP` credential email together. ## Managing credentials @@ -700,7 +700,6 @@ Every signed-retry endpoint can return it on the retry step: | `POST /auth/credentials/{id}/challenge` | Re-send an OTP / re-issue a challenge | | `POST /auth/credentials/{id}/verify` | Log in / verify a credential | | `POST /auth/sessions/{id}/refresh` | Refresh a session | -| `PATCH /customers/{customerId}` | Change the email or phone tied to an `EMAIL_OTP` / `SMS_OTP` credential | | `DELETE /auth/credentials/{id}` | Revoke a credential | | `DELETE /auth/sessions/{id}` | Revoke a session | @@ -711,7 +710,7 @@ To handle it: 3. **Keep retrying with backoff** until you get a terminal response — the success code above, or an error. Grid also reconciles the operation to its terminal state on its own, so even a client that stops retrying and checks back later (for example via `GET /auth/credentials` or `GET /auth/sessions`) will see the settled result. - `verify`, `challenge`, and the customer update endpoint already use `200` for their terminal success body (`AuthSession`, the challenge response, or the updated customer). For those three, `200` alone doesn't tell you which case you're in — check the response body's `status` field for `"PROCESSING"` to distinguish it from a settled success. The other four endpoints (`add credential`, `session refresh`, `revoke credential`, `revoke session`) use `201` or `204` on success, so a `200` by itself already means still-processing. + `verify` and `challenge` already use `200` for their terminal success body (`AuthSession` or the challenge response). For those two, `200` alone doesn't tell you which case you're in — check the response body's `status` field for `"PROCESSING"` to distinguish it from a settled success. The other four endpoints (`add credential`, `session refresh`, `revoke credential`, `revoke session`) use `201` or `204` on success, so a `200` by itself already means still-processing. ### Add an additional credential diff --git a/openapi.yaml b/openapi.yaml index 5544bf51f..1e90eb416 100644 --- a/openapi.yaml +++ b/openapi.yaml @@ -740,11 +740,11 @@ paths: phoneNumber: '+14155559876' responses: '200': - description: Customer updated successfully. For Embedded Wallet email or SMS auth phone updates, this is returned only on the signed retry after the customer contact field and all tied matching OTP credentials have been updated. When the contact update's underlying wallet-provider activity is still in flight, the body is instead a `WalletOperationProcessing` carrying a `PROCESSING` status — re-send the byte-identical signed retry until it settles; the backend also reconciles it to terminal on its own. + description: Customer updated successfully. For Embedded Wallet email or SMS auth phone updates, this is returned only on the signed retry after the customer contact field and all tied matching OTP credentials have been updated. content: application/json: schema: - $ref: '#/components/schemas/CustomerUpdateResponse' + $ref: '#/components/schemas/CustomerOneOf' '202': description: Challenge issued for an Embedded Wallet email or SMS auth phone update. The response contains `payloadToSign` plus a `requestId`. Build an API-key stamp over `payloadToSign` with the session API keypair from a verified authentication credential on one of the customer's tied Embedded Wallets, then retry the same request with `Grid-Wallet-Signature` and `Request-Id`. content: @@ -13808,32 +13808,6 @@ components: mapping: INDIVIDUAL: '#/components/schemas/IndividualCustomerUpdateRequest' BUSINESS: '#/components/schemas/BusinessCustomerUpdateRequest' - WalletOperationProcessing: - title: Wallet Operation Processing - description: '`200` response returned by an Embedded Wallet operation that the wallet provider has accepted but not yet settled — a consensus- or approval-gated activity that is still in flight. It is not an error and needs no client action beyond patience: the backend reconciles the operation to its terminal state on its own. The client MAY re-send the byte-identical request to converge sooner; the request is idempotent and returns the settled success response once the operation completes.' - type: object - required: - - status - properties: - status: - type: string - enum: - - PROCESSING - description: Always `PROCESSING`. Marks a still-in-flight operation whose terminal result is not yet available. - example: PROCESSING - message: - type: string - description: Human-readable explanation that the operation is still being processed and the same request may be retried. - example: This login is still being processed. Retry the same request in a moment. - example: - status: PROCESSING - message: This login is still being processed. Retry the same request in a moment. - CustomerUpdateResponse: - title: Customer Update Response - description: 'Response body for `PATCH /customers/{customerId}`. Normally a `CustomerOneOf` — the updated customer. When the update is an Embedded Wallet email or SMS auth phone change and the underlying wallet-provider activity is still in flight on the signed retry, this is instead a `WalletOperationProcessing` body with `status: "PROCESSING"` — the client re-sends the byte-identical signed retry until it settles; the backend also reconciles the activity to terminal on its own.' - anyOf: - - $ref: '#/components/schemas/CustomerOneOf' - - $ref: '#/components/schemas/WalletOperationProcessing' SignedRequestChallenge: title: Signed Request Challenge type: object @@ -23505,6 +23479,26 @@ components: SMS_OTP: '#/components/schemas/SmsOtpCredentialCreateRequest' OAUTH: '#/components/schemas/OauthCredentialCreateRequest' PASSKEY: '#/components/schemas/PasskeyCredentialCreateRequest' + WalletOperationProcessing: + title: Wallet Operation Processing + description: '`200` response returned by an Embedded Wallet operation that the wallet provider has accepted but not yet settled — a consensus- or approval-gated activity that is still in flight. It is not an error and needs no client action beyond patience: the backend reconciles the operation to its terminal state on its own. The client MAY re-send the byte-identical request to converge sooner; the request is idempotent and returns the settled success response once the operation completes.' + type: object + required: + - status + properties: + status: + type: string + enum: + - PROCESSING + description: Always `PROCESSING`. Marks a still-in-flight operation whose terminal result is not yet available. + example: PROCESSING + message: + type: string + description: Human-readable explanation that the operation is still being processed and the same request may be retried. + example: This login is still being processed. Retry the same request in a moment. + example: + status: PROCESSING + message: This login is still being processed. Retry the same request in a moment. AuthMethodResponse: title: Auth Method Response description: |- diff --git a/openapi/components/schemas/customers/CustomerUpdateResponse.yaml b/openapi/components/schemas/customers/CustomerUpdateResponse.yaml deleted file mode 100644 index 4896afb97..000000000 --- a/openapi/components/schemas/customers/CustomerUpdateResponse.yaml +++ /dev/null @@ -1,13 +0,0 @@ -title: Customer Update Response -description: >- - Response body for `PATCH /customers/{customerId}`. Normally a - `CustomerOneOf` — the updated customer. When the update is an - Embedded Wallet email or SMS auth phone change and the underlying - wallet-provider activity is still in flight on the signed retry, this - is instead a `WalletOperationProcessing` body with `status: - "PROCESSING"` — the client re-sends the byte-identical signed retry - until it settles; the backend also reconciles the activity to - terminal on its own. -anyOf: - - $ref: ./CustomerOneOf.yaml - - $ref: ../common/WalletOperationProcessing.yaml diff --git a/openapi/paths/customers/customers_{customerId}.yaml b/openapi/paths/customers/customers_{customerId}.yaml index 05fa1f8ca..7f72bd663 100644 --- a/openapi/paths/customers/customers_{customerId}.yaml +++ b/openapi/paths/customers/customers_{customerId}.yaml @@ -168,15 +168,11 @@ patch: Customer updated successfully. For Embedded Wallet email or SMS auth phone updates, this is returned only on the signed retry after the customer contact field and all tied matching OTP credentials have been - updated. When the contact update's underlying wallet-provider activity - is still in flight, the body is instead a `WalletOperationProcessing` - carrying a `PROCESSING` status — re-send the byte-identical signed - retry until it settles; the backend also reconciles it to terminal on - its own. + updated. content: application/json: schema: - $ref: ../../components/schemas/customers/CustomerUpdateResponse.yaml + $ref: ../../components/schemas/customers/CustomerOneOf.yaml '202': description: >- Challenge issued for an Embedded Wallet email or SMS auth phone update.