From 54c4a517ba38ee70c5cc916177e56986befdf9cc Mon Sep 17 00:00:00 2001 From: Dhruv Pareek Date: Thu, 7 May 2026 18:09:56 -0700 Subject: [PATCH 1/2] Add wallet privacy endpoint to OpenAPI --- .stainless/stainless.yml | 4 + mintlify/openapi.yaml | 171 +++++++++++++++--- .../global-accounts/authentication.mdx | 2 +- .../snippets/global-accounts/client-keys.mdx | 2 +- .../snippets/global-accounts/concepts.mdx | 6 +- .../snippets/sandbox-global-account-magic.mdx | 1 + openapi.yaml | 171 +++++++++++++++--- .../schemas/customers/InternalAccount.yaml | 7 +- .../InternalAccountUpdateRequest.yaml | 15 ++ openapi/openapi.yaml | 2 + .../internal_accounts_{id}.yaml | 141 +++++++++++++++ 11 files changed, 472 insertions(+), 50 deletions(-) create mode 100644 openapi/components/schemas/internal_accounts/InternalAccountUpdateRequest.yaml create mode 100644 openapi/paths/internal_accounts/internal_accounts_{id}.yaml diff --git a/.stainless/stainless.yml b/.stainless/stainless.yml index 2c3262e6..ca9bbd0b 100644 --- a/.stainless/stainless.yml +++ b/.stainless/stainless.yml @@ -94,6 +94,7 @@ resources: kyc_provider: "#/components/schemas/KycProvider" internal_account_export_request: '#/components/schemas/InternalAccountExportRequest' internal_account_export_response: '#/components/schemas/InternalAccountExportResponse' + internal_account_update_request: '#/components/schemas/InternalAccountUpdateRequest' methods: create: endpoint: post /customers @@ -109,6 +110,9 @@ resources: body_param_name: KycLinkCreateRequest list_internal_accounts: get /customers/internal-accounts export: post /internal-accounts/{id}/export + update_internal_account: + endpoint: patch /internal-accounts/{id} + body_param_name: InternalAccountUpdateRequest # Subresources define resources that are nested within another for more powerful # logical groupings, e.g. `cards.payments`. subresources: diff --git a/mintlify/openapi.yaml b/mintlify/openapi.yaml index 9235ec34..a0fb3a3d 100644 --- a/mintlify/openapi.yaml +++ b/mintlify/openapi.yaml @@ -3673,6 +3673,118 @@ paths: application/json: schema: $ref: '#/components/schemas/Error500' + /internal-accounts/{id}: + parameters: + - name: id + in: path + description: The id of the internal account to update. + required: true + schema: + type: string + example: InternalAccount:019542f5-b3e7-1d02-0000-000000000002 + patch: + summary: Update internal account + description: | + Update mutable fields on an internal account. Today this supports updating the wallet privacy setting for an Embedded Wallet internal account. + + Updating `privateEnabled` is a two-step signed-retry flow: + + 1. Call `PATCH /internal-accounts/{id}` with the request body `{ "privateEnabled": true }` and no signature headers. Grid returns `202` with `payloadToSign`, `requestId`, and `expiresAt`. + + 2. Use the session API keypair of a verified authentication credential on the same internal account to build an API-key stamp over `payloadToSign`, then retry with that full stamp as the `Grid-Wallet-Signature` header and the `requestId` echoed back as the `Request-Id` header. The retry body must carry the same `privateEnabled` value submitted in step 1. The signed retry returns `200` with the updated internal account. + operationId: updateInternalAccount + tags: + - Internal Accounts + security: + - BasicAuth: [] + parameters: + - name: Grid-Wallet-Signature + in: header + required: false + description: Full API-key stamp built over the prior `payloadToSign` with the session API keypair of a verified authentication credential on the target internal account. Required on the signed retry; ignored on the initial call. + schema: + type: string + example: eyJwdWJsaWNLZXkiOiIwMmExYjIuLi4iLCJzaWduYXR1cmUiOiIzMDQ1MDIyMTAwLi4uIiwic2NoZW1lIjoiUDI1Nl9FQ0RTQV9TSEEyNTYifQ + - name: Request-Id + in: header + required: false + description: The `requestId` returned in a prior `202` response, echoed back on the signed retry so the server can correlate it with the issued challenge. Required on the signed retry; must be paired with `Grid-Wallet-Signature`. + schema: + type: string + example: Request:019542f5-b3e7-1d02-0000-000000000010 + requestBody: + required: true + content: + application/json: + schema: + $ref: '#/components/schemas/InternalAccountUpdateRequest' + examples: + updateWalletPrivacy: + summary: Update wallet privacy request (both steps) + value: + privateEnabled: true + responses: + '200': + description: Signed retry accepted. Returns the updated internal account. + content: + application/json: + schema: + $ref: '#/components/schemas/InternalAccount' + examples: + enabled: + summary: Wallet privacy enabled + value: + id: InternalAccount:019542f5-b3e7-1d02-0000-000000000002 + customerId: Customer:019542f5-b3e7-1d02-0000-000000000001 + type: EMBEDDED_WALLET + balance: + amount: 12550 + currency: + code: USD + name: United States Dollar + symbol: $ + decimals: 2 + fundingPaymentInstructions: [] + privateEnabled: true + createdAt: '2026-04-08T15:30:00Z' + updatedAt: '2026-04-08T15:35:02Z' + '202': + description: Challenge issued. The response contains `payloadToSign` (which binds the submitted `privateEnabled` value) plus a `requestId`. Build an API-key stamp over `payloadToSign` with the session API keypair and echo `requestId` on the retry. + content: + application/json: + schema: + $ref: '#/components/schemas/SignedRequestChallenge' + examples: + challenge: + summary: Internal account update challenge + value: + payloadToSign: Y2hhbGxlbmdlLXBheWxvYWQtdG8tc2lnbg== + requestId: Request:019542f5-b3e7-1d02-0000-000000000010 + expiresAt: '2026-04-08T15:35:00Z' + '400': + description: Bad request + content: + application/json: + schema: + $ref: '#/components/schemas/Error400' + '401': + description: Unauthorized. Returned when the provided `Grid-Wallet-Signature` is missing, malformed, or does not match a pending internal account update challenge, when the `Request-Id` does not match an unexpired pending challenge, or when the retry's `privateEnabled` value does not match the one bound into `payloadToSign` on the initial call. + content: + application/json: + schema: + $ref: '#/components/schemas/Error401' + '404': + description: Internal account not found + content: + application/json: + schema: + $ref: '#/components/schemas/Error404' + '500': + description: Internal service error + content: + application/json: + schema: + $ref: '#/components/schemas/Error500' /internal-accounts/{id}/export: post: summary: Export internal account wallet credentials @@ -10597,6 +10709,10 @@ components: description: Payment instructions for funding the account items: $ref: '#/components/schemas/PaymentInstructions' + privateEnabled: + type: boolean + description: Whether wallet privacy is enabled for the Embedded Wallet. Only present for `EMBEDDED_WALLET` internal accounts. + example: true createdAt: type: string format: date-time @@ -15618,6 +15734,39 @@ components: description: A list of permissions to grant to the token items: $ref: '#/components/schemas/Permission' + InternalAccountUpdateRequest: + title: Internal Account Update Request + description: Request body for `PATCH /internal-accounts/{id}`. The `privateEnabled` value is required on both steps of the signed-retry flow. On step 1 Grid binds it into `payloadToSign`; on step 2 the client echoes the same value back and Grid updates the wallet privacy setting for the internal account's Embedded Wallet. + type: object + required: + - privateEnabled + properties: + privateEnabled: + type: boolean + description: Whether wallet privacy should be enabled for the Embedded Wallet. + example: true + SignedRequestChallenge: + title: Signed Request Challenge + type: object + required: + - payloadToSign + - requestId + - expiresAt + description: Common base for two-step signed-retry challenge responses on Embedded Wallet endpoints (credential registration or revocation, session refresh or revocation, wallet export, and similar). Holds the signing fields shared across every challenge shape; each variant composes this base via `allOf` and adds its own resource `id` (and `type`, when applicable) with variant-specific description and example. + properties: + payloadToSign: + type: string + description: Canonical payload for the retry authorization stamp. Build an API-key stamp over this exact value with the session API keypair, then send the full base64url-encoded stamp in `Grid-Wallet-Signature` on the retry that completes the original request. + example: Y2hhbGxlbmdlLXBheWxvYWQtdG8tc2lnbg== + requestId: + type: string + description: Unique identifier for this request. Must be echoed in the `Request-Id` header on the signed retry so the server can correlate the retry with the issued challenge. + example: 7c4a8d09-ca37-4e3e-9e0d-8c2b3e9a1f21 + expiresAt: + type: string + format: date-time + description: Timestamp after which this challenge is no longer valid. The signed retry must be submitted before this time. + example: '2026-04-08T15:35:00Z' InternalAccountExportRequest: title: Internal Account Export Request description: Request body for `POST /internal-accounts/{id}/export`. The `clientPublicKey` is required on both steps of the signed-retry flow. On step 1 Grid binds it into `payloadToSign` so the subsequent stamp in `Grid-Wallet-Signature` commits to the target pubkey; on step 2 the client echoes the same `clientPublicKey` back and Grid uses it to encrypt the wallet credentials returned in the `200` response. @@ -15647,28 +15796,6 @@ components: The value is a JSON string of the form `{"version": "v1.0.0", "data": "", "dataSignature": "", "enclaveQuorumPublic": ""}`. `data` hex-decodes to JSON `{"encappedPublic": "", "ciphertext": "", "organizationId": ""}`, where `encappedPublic` is the uncompressed SEC1 ephemeral public key. `dataSignature` is an ECDSA-P256-SHA256 signature over the `data` bytes produced by the issuer key in `enclaveQuorumPublic`; verify before decrypting. In sandbox, `dataSignature` and `enclaveQuorumPublic` are empty strings. Clients should bypass attestation verification when calling against sandbox. example: '{"version":"v1.0.0","data":"7b22656e6361707065645075626c6963223a22303433...","dataSignature":"3045022100c9...","enclaveQuorumPublic":"04a1b2c3..."}' - SignedRequestChallenge: - title: Signed Request Challenge - type: object - required: - - payloadToSign - - requestId - - expiresAt - description: Common base for two-step signed-retry challenge responses on Embedded Wallet endpoints (credential registration or revocation, session refresh or revocation, wallet export, and similar). Holds the signing fields shared across every challenge shape; each variant composes this base via `allOf` and adds its own resource `id` (and `type`, when applicable) with variant-specific description and example. - properties: - payloadToSign: - type: string - description: Canonical payload for the retry authorization stamp. Build an API-key stamp over this exact value with the session API keypair, then send the full base64url-encoded stamp in `Grid-Wallet-Signature` on the retry that completes the original request. - example: Y2hhbGxlbmdlLXBheWxvYWQtdG8tc2lnbg== - requestId: - type: string - description: Unique identifier for this request. Must be echoed in the `Request-Id` header on the signed retry so the server can correlate the retry with the issued challenge. - example: 7c4a8d09-ca37-4e3e-9e0d-8c2b3e9a1f21 - expiresAt: - type: string - format: date-time - description: Timestamp after which this challenge is no longer valid. The signed retry must be submitted before this time. - example: '2026-04-08T15:35:00Z' AuthMethodType: type: string enum: diff --git a/mintlify/snippets/global-accounts/authentication.mdx b/mintlify/snippets/global-accounts/authentication.mdx index 8d193043..0f2bd3ee 100644 --- a/mintlify/snippets/global-accounts/authentication.mdx +++ b/mintlify/snippets/global-accounts/authentication.mdx @@ -540,7 +540,7 @@ The response is not paginated — each account holds a small, bounded number of ### The signed-retry pattern -Adding an additional credential, revoking a credential, revoking a session, and exporting a wallet all share the same shape: +Adding an additional credential, revoking a credential, revoking a session, exporting a wallet, and updating wallet privacy all share the same shape: ```mermaid sequenceDiagram diff --git a/mintlify/snippets/global-accounts/client-keys.mdx b/mintlify/snippets/global-accounts/client-keys.mdx index 95ea7f5e..8104fa41 100644 --- a/mintlify/snippets/global-accounts/client-keys.mdx +++ b/mintlify/snippets/global-accounts/client-keys.mdx @@ -224,7 +224,7 @@ Grid returns `payloadToSign` strings from several endpoints: - `POST /quotes` (when the source is a Global Account) — the quote's `paymentInstructions[].accountOrWalletInfo.payloadToSign`. - `POST /auth/credentials` (adding an additional credential) — 202 response body. -- `DELETE /auth/credentials/{id}`, `DELETE /auth/sessions/{id}`, `POST /internal-accounts/{id}/export` — all 202 response bodies. +- `DELETE /auth/credentials/{id}`, `DELETE /auth/sessions/{id}`, `POST /internal-accounts/{id}/export`, `PATCH /internal-accounts/{id}` — all 202 response bodies. Sign the payload **byte-for-byte as returned** (do not re-parse, re-serialize, or trim whitespace). The signature is ECDSA over SHA-256 using the session signing key, DER-encoded, then base64-encoded. Pass it as the `Grid-Wallet-Signature` header on the retry (and, for endpoints that use it, the `Request-Id` header echoed back from the 202). diff --git a/mintlify/snippets/global-accounts/concepts.mdx b/mintlify/snippets/global-accounts/concepts.mdx index 14a489ba..77c8ccab 100644 --- a/mintlify/snippets/global-accounts/concepts.mdx +++ b/mintlify/snippets/global-accounts/concepts.mdx @@ -30,12 +30,12 @@ The client **never** talks to Grid directly. Every request flows client → inte ## Auth credentials, client keys, and session signing keys -Three distinct pieces of crypto collaborate to authorize actions on the Global Account (withdrawals, credential changes, session revocations, and wallet exports): +Three distinct pieces of crypto collaborate to authorize actions on the Global Account (withdrawals, credential changes, session revocations, wallet exports, and wallet privacy updates): | Piece | Where it lives | How long it lives | What it proves | |---|---|---|---| | **Auth credential** — passkey, OIDC token, or email OTP | Registered on the account; the passkey itself lives on the authenticator, OIDC on your IdP, OTP in the user's inbox | Until the customer revokes it | *"I am the human who owns this account."* Used to authenticate the user at the start of each session. | | **Client key pair** (P-256) | Generated on the client device for each verification request; private key stays in device-local secure storage | One verification request | Binds a given session signing key delivery to the exact device that asked for it — Grid encrypts the session to this public key, so only this device can decrypt. | -| **Session signing key** (P-256) | Issued by Grid, sealed to the client public key, decrypted and held on the device for the session's lifetime | 15 minutes (default) | *"This specific account action was approved on an authenticated device."* Signs the `payloadToSign` Grid returns on quotes, credential changes, session revocations, and wallet exports. | +| **Session signing key** (P-256) | Issued by Grid, sealed to the client public key, decrypted and held on the device for the session's lifetime | 15 minutes (default) | *"This specific account action was approved on an authenticated device."* Signs the `payloadToSign` Grid returns on quotes, credential changes, session revocations, wallet exports, and wallet privacy updates. | -The flow is always the same: verify an auth credential → receive a short-lived session signing key → sign `payloadToSign` bytes on the client → pass the signature as the `Grid-Wallet-Signature` header on the request that actually moves funds or changes account state. This applies to withdrawals, adding or removing credentials, revoking sessions, and exporting the wallet seed. +The flow is always the same: verify an auth credential → receive a short-lived session signing key → sign `payloadToSign` bytes on the client → pass the signature as the `Grid-Wallet-Signature` header on the request that actually moves funds or changes account state. This applies to withdrawals, adding or removing credentials, revoking sessions, exporting the wallet seed, and updating wallet privacy. diff --git a/mintlify/snippets/sandbox-global-account-magic.mdx b/mintlify/snippets/sandbox-global-account-magic.mdx index 2fca679f..5f36332d 100644 --- a/mintlify/snippets/sandbox-global-account-magic.mdx +++ b/mintlify/snippets/sandbox-global-account-magic.mdx @@ -83,6 +83,7 @@ Pass `sandbox-valid-signature` as the `Grid-Wallet-Signature` HTTP header on any - `DELETE /auth/credentials/{id}` (revoke credential) - `DELETE /auth/sessions/{id}` (revoke session) - `POST /internal-accounts/{id}/export` (export wallet) +- `PATCH /internal-accounts/{id}` (update wallet privacy) - `POST /quotes/{quoteId}/execute` (when source is an embedded wallet) ```bash diff --git a/openapi.yaml b/openapi.yaml index 9235ec34..a0fb3a3d 100644 --- a/openapi.yaml +++ b/openapi.yaml @@ -3673,6 +3673,118 @@ paths: application/json: schema: $ref: '#/components/schemas/Error500' + /internal-accounts/{id}: + parameters: + - name: id + in: path + description: The id of the internal account to update. + required: true + schema: + type: string + example: InternalAccount:019542f5-b3e7-1d02-0000-000000000002 + patch: + summary: Update internal account + description: | + Update mutable fields on an internal account. Today this supports updating the wallet privacy setting for an Embedded Wallet internal account. + + Updating `privateEnabled` is a two-step signed-retry flow: + + 1. Call `PATCH /internal-accounts/{id}` with the request body `{ "privateEnabled": true }` and no signature headers. Grid returns `202` with `payloadToSign`, `requestId`, and `expiresAt`. + + 2. Use the session API keypair of a verified authentication credential on the same internal account to build an API-key stamp over `payloadToSign`, then retry with that full stamp as the `Grid-Wallet-Signature` header and the `requestId` echoed back as the `Request-Id` header. The retry body must carry the same `privateEnabled` value submitted in step 1. The signed retry returns `200` with the updated internal account. + operationId: updateInternalAccount + tags: + - Internal Accounts + security: + - BasicAuth: [] + parameters: + - name: Grid-Wallet-Signature + in: header + required: false + description: Full API-key stamp built over the prior `payloadToSign` with the session API keypair of a verified authentication credential on the target internal account. Required on the signed retry; ignored on the initial call. + schema: + type: string + example: eyJwdWJsaWNLZXkiOiIwMmExYjIuLi4iLCJzaWduYXR1cmUiOiIzMDQ1MDIyMTAwLi4uIiwic2NoZW1lIjoiUDI1Nl9FQ0RTQV9TSEEyNTYifQ + - name: Request-Id + in: header + required: false + description: The `requestId` returned in a prior `202` response, echoed back on the signed retry so the server can correlate it with the issued challenge. Required on the signed retry; must be paired with `Grid-Wallet-Signature`. + schema: + type: string + example: Request:019542f5-b3e7-1d02-0000-000000000010 + requestBody: + required: true + content: + application/json: + schema: + $ref: '#/components/schemas/InternalAccountUpdateRequest' + examples: + updateWalletPrivacy: + summary: Update wallet privacy request (both steps) + value: + privateEnabled: true + responses: + '200': + description: Signed retry accepted. Returns the updated internal account. + content: + application/json: + schema: + $ref: '#/components/schemas/InternalAccount' + examples: + enabled: + summary: Wallet privacy enabled + value: + id: InternalAccount:019542f5-b3e7-1d02-0000-000000000002 + customerId: Customer:019542f5-b3e7-1d02-0000-000000000001 + type: EMBEDDED_WALLET + balance: + amount: 12550 + currency: + code: USD + name: United States Dollar + symbol: $ + decimals: 2 + fundingPaymentInstructions: [] + privateEnabled: true + createdAt: '2026-04-08T15:30:00Z' + updatedAt: '2026-04-08T15:35:02Z' + '202': + description: Challenge issued. The response contains `payloadToSign` (which binds the submitted `privateEnabled` value) plus a `requestId`. Build an API-key stamp over `payloadToSign` with the session API keypair and echo `requestId` on the retry. + content: + application/json: + schema: + $ref: '#/components/schemas/SignedRequestChallenge' + examples: + challenge: + summary: Internal account update challenge + value: + payloadToSign: Y2hhbGxlbmdlLXBheWxvYWQtdG8tc2lnbg== + requestId: Request:019542f5-b3e7-1d02-0000-000000000010 + expiresAt: '2026-04-08T15:35:00Z' + '400': + description: Bad request + content: + application/json: + schema: + $ref: '#/components/schemas/Error400' + '401': + description: Unauthorized. Returned when the provided `Grid-Wallet-Signature` is missing, malformed, or does not match a pending internal account update challenge, when the `Request-Id` does not match an unexpired pending challenge, or when the retry's `privateEnabled` value does not match the one bound into `payloadToSign` on the initial call. + content: + application/json: + schema: + $ref: '#/components/schemas/Error401' + '404': + description: Internal account not found + content: + application/json: + schema: + $ref: '#/components/schemas/Error404' + '500': + description: Internal service error + content: + application/json: + schema: + $ref: '#/components/schemas/Error500' /internal-accounts/{id}/export: post: summary: Export internal account wallet credentials @@ -10597,6 +10709,10 @@ components: description: Payment instructions for funding the account items: $ref: '#/components/schemas/PaymentInstructions' + privateEnabled: + type: boolean + description: Whether wallet privacy is enabled for the Embedded Wallet. Only present for `EMBEDDED_WALLET` internal accounts. + example: true createdAt: type: string format: date-time @@ -15618,6 +15734,39 @@ components: description: A list of permissions to grant to the token items: $ref: '#/components/schemas/Permission' + InternalAccountUpdateRequest: + title: Internal Account Update Request + description: Request body for `PATCH /internal-accounts/{id}`. The `privateEnabled` value is required on both steps of the signed-retry flow. On step 1 Grid binds it into `payloadToSign`; on step 2 the client echoes the same value back and Grid updates the wallet privacy setting for the internal account's Embedded Wallet. + type: object + required: + - privateEnabled + properties: + privateEnabled: + type: boolean + description: Whether wallet privacy should be enabled for the Embedded Wallet. + example: true + SignedRequestChallenge: + title: Signed Request Challenge + type: object + required: + - payloadToSign + - requestId + - expiresAt + description: Common base for two-step signed-retry challenge responses on Embedded Wallet endpoints (credential registration or revocation, session refresh or revocation, wallet export, and similar). Holds the signing fields shared across every challenge shape; each variant composes this base via `allOf` and adds its own resource `id` (and `type`, when applicable) with variant-specific description and example. + properties: + payloadToSign: + type: string + description: Canonical payload for the retry authorization stamp. Build an API-key stamp over this exact value with the session API keypair, then send the full base64url-encoded stamp in `Grid-Wallet-Signature` on the retry that completes the original request. + example: Y2hhbGxlbmdlLXBheWxvYWQtdG8tc2lnbg== + requestId: + type: string + description: Unique identifier for this request. Must be echoed in the `Request-Id` header on the signed retry so the server can correlate the retry with the issued challenge. + example: 7c4a8d09-ca37-4e3e-9e0d-8c2b3e9a1f21 + expiresAt: + type: string + format: date-time + description: Timestamp after which this challenge is no longer valid. The signed retry must be submitted before this time. + example: '2026-04-08T15:35:00Z' InternalAccountExportRequest: title: Internal Account Export Request description: Request body for `POST /internal-accounts/{id}/export`. The `clientPublicKey` is required on both steps of the signed-retry flow. On step 1 Grid binds it into `payloadToSign` so the subsequent stamp in `Grid-Wallet-Signature` commits to the target pubkey; on step 2 the client echoes the same `clientPublicKey` back and Grid uses it to encrypt the wallet credentials returned in the `200` response. @@ -15647,28 +15796,6 @@ components: The value is a JSON string of the form `{"version": "v1.0.0", "data": "", "dataSignature": "", "enclaveQuorumPublic": ""}`. `data` hex-decodes to JSON `{"encappedPublic": "", "ciphertext": "", "organizationId": ""}`, where `encappedPublic` is the uncompressed SEC1 ephemeral public key. `dataSignature` is an ECDSA-P256-SHA256 signature over the `data` bytes produced by the issuer key in `enclaveQuorumPublic`; verify before decrypting. In sandbox, `dataSignature` and `enclaveQuorumPublic` are empty strings. Clients should bypass attestation verification when calling against sandbox. example: '{"version":"v1.0.0","data":"7b22656e6361707065645075626c6963223a22303433...","dataSignature":"3045022100c9...","enclaveQuorumPublic":"04a1b2c3..."}' - SignedRequestChallenge: - title: Signed Request Challenge - type: object - required: - - payloadToSign - - requestId - - expiresAt - description: Common base for two-step signed-retry challenge responses on Embedded Wallet endpoints (credential registration or revocation, session refresh or revocation, wallet export, and similar). Holds the signing fields shared across every challenge shape; each variant composes this base via `allOf` and adds its own resource `id` (and `type`, when applicable) with variant-specific description and example. - properties: - payloadToSign: - type: string - description: Canonical payload for the retry authorization stamp. Build an API-key stamp over this exact value with the session API keypair, then send the full base64url-encoded stamp in `Grid-Wallet-Signature` on the retry that completes the original request. - example: Y2hhbGxlbmdlLXBheWxvYWQtdG8tc2lnbg== - requestId: - type: string - description: Unique identifier for this request. Must be echoed in the `Request-Id` header on the signed retry so the server can correlate the retry with the issued challenge. - example: 7c4a8d09-ca37-4e3e-9e0d-8c2b3e9a1f21 - expiresAt: - type: string - format: date-time - description: Timestamp after which this challenge is no longer valid. The signed retry must be submitted before this time. - example: '2026-04-08T15:35:00Z' AuthMethodType: type: string enum: diff --git a/openapi/components/schemas/customers/InternalAccount.yaml b/openapi/components/schemas/customers/InternalAccount.yaml index 406136f3..ba7e2e89 100644 --- a/openapi/components/schemas/customers/InternalAccount.yaml +++ b/openapi/components/schemas/customers/InternalAccount.yaml @@ -24,6 +24,12 @@ properties: description: Payment instructions for funding the account items: $ref: ../common/PaymentInstructions.yaml + privateEnabled: + type: boolean + description: >- + Whether wallet privacy is enabled for the Embedded Wallet. Only present + for `EMBEDDED_WALLET` internal accounts. + example: true createdAt: type: string format: date-time @@ -34,4 +40,3 @@ properties: format: date-time description: Timestamp when the internal account was last updated example: 2025-10-03T12:30:00Z - diff --git a/openapi/components/schemas/internal_accounts/InternalAccountUpdateRequest.yaml b/openapi/components/schemas/internal_accounts/InternalAccountUpdateRequest.yaml new file mode 100644 index 00000000..b10262eb --- /dev/null +++ b/openapi/components/schemas/internal_accounts/InternalAccountUpdateRequest.yaml @@ -0,0 +1,15 @@ +title: Internal Account Update Request +description: >- + Request body for `PATCH /internal-accounts/{id}`. The `privateEnabled` + value is required on both steps of the signed-retry flow. On step 1 Grid + binds it into `payloadToSign`; on step 2 the client echoes the same value + back and Grid updates the wallet privacy setting for the internal account's + Embedded Wallet. +type: object +required: + - privateEnabled +properties: + privateEnabled: + type: boolean + description: Whether wallet privacy should be enabled for the Embedded Wallet. + example: true diff --git a/openapi/openapi.yaml b/openapi/openapi.yaml index a04180a3..19b26f8d 100644 --- a/openapi/openapi.yaml +++ b/openapi/openapi.yaml @@ -199,6 +199,8 @@ paths: $ref: paths/tokens/tokens.yaml /tokens/{tokenId}: $ref: paths/tokens/tokens_{tokenId}.yaml + /internal-accounts/{id}: + $ref: paths/internal_accounts/internal_accounts_{id}.yaml /internal-accounts/{id}/export: $ref: paths/internal_accounts/internal_accounts_{id}_export.yaml /auth/credentials: diff --git a/openapi/paths/internal_accounts/internal_accounts_{id}.yaml b/openapi/paths/internal_accounts/internal_accounts_{id}.yaml new file mode 100644 index 00000000..57fd399f --- /dev/null +++ b/openapi/paths/internal_accounts/internal_accounts_{id}.yaml @@ -0,0 +1,141 @@ +parameters: + - name: id + in: path + description: The id of the internal account to update. + required: true + schema: + type: string + example: InternalAccount:019542f5-b3e7-1d02-0000-000000000002 + +patch: + summary: Update internal account + description: > + Update mutable fields on an internal account. Today this supports updating + the wallet privacy setting for an Embedded Wallet internal account. + + + Updating `privateEnabled` is a two-step signed-retry flow: + + + 1. Call `PATCH /internal-accounts/{id}` with the request body + `{ "privateEnabled": true }` and no signature headers. Grid returns + `202` with `payloadToSign`, `requestId`, and `expiresAt`. + + + 2. Use the session API keypair of a verified authentication credential + on the same internal account to build an API-key stamp over + `payloadToSign`, then retry with that full stamp as the + `Grid-Wallet-Signature` header and the `requestId` echoed back as + the `Request-Id` header. The retry body must carry the same + `privateEnabled` value submitted in step 1. The signed retry returns + `200` with the updated internal account. + operationId: updateInternalAccount + tags: + - Internal Accounts + security: + - BasicAuth: [] + parameters: + - name: Grid-Wallet-Signature + in: header + required: false + description: >- + Full API-key stamp built over the prior `payloadToSign` with + the session API keypair of a verified authentication credential + on the target internal account. Required on the signed retry; + ignored on the initial call. + schema: + type: string + example: eyJwdWJsaWNLZXkiOiIwMmExYjIuLi4iLCJzaWduYXR1cmUiOiIzMDQ1MDIyMTAwLi4uIiwic2NoZW1lIjoiUDI1Nl9FQ0RTQV9TSEEyNTYifQ + - name: Request-Id + in: header + required: false + description: >- + The `requestId` returned in a prior `202` response, echoed back + on the signed retry so the server can correlate it with the + issued challenge. Required on the signed retry; must be paired + with `Grid-Wallet-Signature`. + schema: + type: string + example: Request:019542f5-b3e7-1d02-0000-000000000010 + requestBody: + required: true + content: + application/json: + schema: + $ref: ../../components/schemas/internal_accounts/InternalAccountUpdateRequest.yaml + examples: + updateWalletPrivacy: + summary: Update wallet privacy request (both steps) + value: + privateEnabled: true + responses: + '200': + description: Signed retry accepted. Returns the updated internal account. + content: + application/json: + schema: + $ref: ../../components/schemas/customers/InternalAccount.yaml + examples: + enabled: + summary: Wallet privacy enabled + value: + id: InternalAccount:019542f5-b3e7-1d02-0000-000000000002 + customerId: Customer:019542f5-b3e7-1d02-0000-000000000001 + type: EMBEDDED_WALLET + balance: + amount: 12550 + currency: + code: USD + name: United States Dollar + symbol: $ + decimals: 2 + fundingPaymentInstructions: [] + privateEnabled: true + createdAt: '2026-04-08T15:30:00Z' + updatedAt: '2026-04-08T15:35:02Z' + '202': + description: >- + Challenge issued. The response contains `payloadToSign` (which + binds the submitted `privateEnabled` value) plus a `requestId`. + Build an API-key stamp over `payloadToSign` with the session + API keypair and echo `requestId` on the retry. + content: + application/json: + schema: + $ref: ../../components/schemas/common/SignedRequestChallenge.yaml + examples: + challenge: + summary: Internal account update challenge + value: + payloadToSign: Y2hhbGxlbmdlLXBheWxvYWQtdG8tc2lnbg== + requestId: Request:019542f5-b3e7-1d02-0000-000000000010 + expiresAt: '2026-04-08T15:35:00Z' + '400': + description: Bad request + content: + application/json: + schema: + $ref: ../../components/schemas/errors/Error400.yaml + '401': + description: >- + Unauthorized. Returned when the provided `Grid-Wallet-Signature` + is missing, malformed, or does not match a pending internal account + update challenge, when the `Request-Id` does not match an unexpired + pending challenge, or when the retry's `privateEnabled` value does + not match the one bound into `payloadToSign` on the initial call. + content: + application/json: + schema: + $ref: ../../components/schemas/errors/Error401.yaml + '404': + description: Internal account not found + content: + application/json: + schema: + $ref: ../../components/schemas/errors/Error404.yaml + '500': + description: Internal service error + content: + application/json: + schema: + $ref: ../../components/schemas/errors/Error500.yaml From 06f82ad277b93d9aeb87d9d17e3b8683fa04c2c1 Mon Sep 17 00:00:00 2001 From: Dhruv Pareek Date: Mon, 11 May 2026 15:47:56 -0700 Subject: [PATCH 2/2] Make internal account update request partial --- mintlify/openapi.yaml | 12 +++++------- openapi.yaml | 12 +++++------- .../InternalAccountUpdateRequest.yaml | 11 ++++------- .../internal_accounts_{id}.yaml | 18 +++++++++--------- 4 files changed, 23 insertions(+), 30 deletions(-) diff --git a/mintlify/openapi.yaml b/mintlify/openapi.yaml index a0fb3a3d..a9b0d7c8 100644 --- a/mintlify/openapi.yaml +++ b/mintlify/openapi.yaml @@ -3687,11 +3687,11 @@ paths: description: | Update mutable fields on an internal account. Today this supports updating the wallet privacy setting for an Embedded Wallet internal account. - Updating `privateEnabled` is a two-step signed-retry flow: + Updating wallet privacy is a two-step signed-retry flow: 1. Call `PATCH /internal-accounts/{id}` with the request body `{ "privateEnabled": true }` and no signature headers. Grid returns `202` with `payloadToSign`, `requestId`, and `expiresAt`. - 2. Use the session API keypair of a verified authentication credential on the same internal account to build an API-key stamp over `payloadToSign`, then retry with that full stamp as the `Grid-Wallet-Signature` header and the `requestId` echoed back as the `Request-Id` header. The retry body must carry the same `privateEnabled` value submitted in step 1. The signed retry returns `200` with the updated internal account. + 2. Use the session API keypair of a verified authentication credential on the same internal account to build an API-key stamp over `payloadToSign`, then retry with that full stamp as the `Grid-Wallet-Signature` header and the `requestId` echoed back as the `Request-Id` header. The retry body must carry the same update fields submitted in step 1. The signed retry returns `200` with the updated internal account. operationId: updateInternalAccount tags: - Internal Accounts @@ -3749,7 +3749,7 @@ paths: createdAt: '2026-04-08T15:30:00Z' updatedAt: '2026-04-08T15:35:02Z' '202': - description: Challenge issued. The response contains `payloadToSign` (which binds the submitted `privateEnabled` value) plus a `requestId`. Build an API-key stamp over `payloadToSign` with the session API keypair and echo `requestId` on the retry. + description: Challenge issued. The response contains `payloadToSign` (which binds the submitted update fields) plus a `requestId`. Build an API-key stamp over `payloadToSign` with the session API keypair and echo `requestId` on the retry. content: application/json: schema: @@ -3768,7 +3768,7 @@ paths: schema: $ref: '#/components/schemas/Error400' '401': - description: Unauthorized. Returned when the provided `Grid-Wallet-Signature` is missing, malformed, or does not match a pending internal account update challenge, when the `Request-Id` does not match an unexpired pending challenge, or when the retry's `privateEnabled` value does not match the one bound into `payloadToSign` on the initial call. + description: Unauthorized. Returned when the provided `Grid-Wallet-Signature` is missing, malformed, or does not match a pending internal account update challenge, when the `Request-Id` does not match an unexpired pending challenge, or when the retry body does not match the update fields bound into `payloadToSign` on the initial call. content: application/json: schema: @@ -15736,10 +15736,8 @@ components: $ref: '#/components/schemas/Permission' InternalAccountUpdateRequest: title: Internal Account Update Request - description: Request body for `PATCH /internal-accounts/{id}`. The `privateEnabled` value is required on both steps of the signed-retry flow. On step 1 Grid binds it into `payloadToSign`; on step 2 the client echoes the same value back and Grid updates the wallet privacy setting for the internal account's Embedded Wallet. + description: Partial request body for `PATCH /internal-accounts/{id}`. At least one update field must be provided. On step 1 of the signed-retry flow Grid binds the submitted update fields into `payloadToSign`; on step 2 the client echoes the same fields back and Grid applies the update to the internal account. type: object - required: - - privateEnabled properties: privateEnabled: type: boolean diff --git a/openapi.yaml b/openapi.yaml index a0fb3a3d..a9b0d7c8 100644 --- a/openapi.yaml +++ b/openapi.yaml @@ -3687,11 +3687,11 @@ paths: description: | Update mutable fields on an internal account. Today this supports updating the wallet privacy setting for an Embedded Wallet internal account. - Updating `privateEnabled` is a two-step signed-retry flow: + Updating wallet privacy is a two-step signed-retry flow: 1. Call `PATCH /internal-accounts/{id}` with the request body `{ "privateEnabled": true }` and no signature headers. Grid returns `202` with `payloadToSign`, `requestId`, and `expiresAt`. - 2. Use the session API keypair of a verified authentication credential on the same internal account to build an API-key stamp over `payloadToSign`, then retry with that full stamp as the `Grid-Wallet-Signature` header and the `requestId` echoed back as the `Request-Id` header. The retry body must carry the same `privateEnabled` value submitted in step 1. The signed retry returns `200` with the updated internal account. + 2. Use the session API keypair of a verified authentication credential on the same internal account to build an API-key stamp over `payloadToSign`, then retry with that full stamp as the `Grid-Wallet-Signature` header and the `requestId` echoed back as the `Request-Id` header. The retry body must carry the same update fields submitted in step 1. The signed retry returns `200` with the updated internal account. operationId: updateInternalAccount tags: - Internal Accounts @@ -3749,7 +3749,7 @@ paths: createdAt: '2026-04-08T15:30:00Z' updatedAt: '2026-04-08T15:35:02Z' '202': - description: Challenge issued. The response contains `payloadToSign` (which binds the submitted `privateEnabled` value) plus a `requestId`. Build an API-key stamp over `payloadToSign` with the session API keypair and echo `requestId` on the retry. + description: Challenge issued. The response contains `payloadToSign` (which binds the submitted update fields) plus a `requestId`. Build an API-key stamp over `payloadToSign` with the session API keypair and echo `requestId` on the retry. content: application/json: schema: @@ -3768,7 +3768,7 @@ paths: schema: $ref: '#/components/schemas/Error400' '401': - description: Unauthorized. Returned when the provided `Grid-Wallet-Signature` is missing, malformed, or does not match a pending internal account update challenge, when the `Request-Id` does not match an unexpired pending challenge, or when the retry's `privateEnabled` value does not match the one bound into `payloadToSign` on the initial call. + description: Unauthorized. Returned when the provided `Grid-Wallet-Signature` is missing, malformed, or does not match a pending internal account update challenge, when the `Request-Id` does not match an unexpired pending challenge, or when the retry body does not match the update fields bound into `payloadToSign` on the initial call. content: application/json: schema: @@ -15736,10 +15736,8 @@ components: $ref: '#/components/schemas/Permission' InternalAccountUpdateRequest: title: Internal Account Update Request - description: Request body for `PATCH /internal-accounts/{id}`. The `privateEnabled` value is required on both steps of the signed-retry flow. On step 1 Grid binds it into `payloadToSign`; on step 2 the client echoes the same value back and Grid updates the wallet privacy setting for the internal account's Embedded Wallet. + description: Partial request body for `PATCH /internal-accounts/{id}`. At least one update field must be provided. On step 1 of the signed-retry flow Grid binds the submitted update fields into `payloadToSign`; on step 2 the client echoes the same fields back and Grid applies the update to the internal account. type: object - required: - - privateEnabled properties: privateEnabled: type: boolean diff --git a/openapi/components/schemas/internal_accounts/InternalAccountUpdateRequest.yaml b/openapi/components/schemas/internal_accounts/InternalAccountUpdateRequest.yaml index b10262eb..c9fd590e 100644 --- a/openapi/components/schemas/internal_accounts/InternalAccountUpdateRequest.yaml +++ b/openapi/components/schemas/internal_accounts/InternalAccountUpdateRequest.yaml @@ -1,13 +1,10 @@ title: Internal Account Update Request description: >- - Request body for `PATCH /internal-accounts/{id}`. The `privateEnabled` - value is required on both steps of the signed-retry flow. On step 1 Grid - binds it into `payloadToSign`; on step 2 the client echoes the same value - back and Grid updates the wallet privacy setting for the internal account's - Embedded Wallet. + Partial request body for `PATCH /internal-accounts/{id}`. At least one + update field must be provided. On step 1 of the signed-retry flow Grid binds + the submitted update fields into `payloadToSign`; on step 2 the client echoes + the same fields back and Grid applies the update to the internal account. type: object -required: - - privateEnabled properties: privateEnabled: type: boolean diff --git a/openapi/paths/internal_accounts/internal_accounts_{id}.yaml b/openapi/paths/internal_accounts/internal_accounts_{id}.yaml index 57fd399f..ea398fe1 100644 --- a/openapi/paths/internal_accounts/internal_accounts_{id}.yaml +++ b/openapi/paths/internal_accounts/internal_accounts_{id}.yaml @@ -14,7 +14,7 @@ patch: the wallet privacy setting for an Embedded Wallet internal account. - Updating `privateEnabled` is a two-step signed-retry flow: + Updating wallet privacy is a two-step signed-retry flow: 1. Call `PATCH /internal-accounts/{id}` with the request body @@ -26,9 +26,9 @@ patch: on the same internal account to build an API-key stamp over `payloadToSign`, then retry with that full stamp as the `Grid-Wallet-Signature` header and the `requestId` echoed back as - the `Request-Id` header. The retry body must carry the same - `privateEnabled` value submitted in step 1. The signed retry returns - `200` with the updated internal account. + the `Request-Id` header. The retry body must carry the same update + fields submitted in step 1. The signed retry returns `200` with the + updated internal account. operationId: updateInternalAccount tags: - Internal Accounts @@ -96,9 +96,9 @@ patch: '202': description: >- Challenge issued. The response contains `payloadToSign` (which - binds the submitted `privateEnabled` value) plus a `requestId`. - Build an API-key stamp over `payloadToSign` with the session - API keypair and echo `requestId` on the retry. + binds the submitted update fields) plus a `requestId`. Build an + API-key stamp over `payloadToSign` with the session API keypair + and echo `requestId` on the retry. content: application/json: schema: @@ -121,8 +121,8 @@ patch: Unauthorized. Returned when the provided `Grid-Wallet-Signature` is missing, malformed, or does not match a pending internal account update challenge, when the `Request-Id` does not match an unexpired - pending challenge, or when the retry's `privateEnabled` value does - not match the one bound into `payloadToSign` on the initial call. + pending challenge, or when the retry body does not match the update + fields bound into `payloadToSign` on the initial call. content: application/json: schema: