Skip to content

Grid Programmatic KYC API - #726

Merged
akanter merged 6 commits into
mainfrom
07-22-kyc-singleton-implementation-plan
Jul 27, 2026
Merged

Grid Programmatic KYC API#726
akanter merged 6 commits into
mainfrom
07-22-kyc-singleton-implementation-plan

Conversation

@akanter

@akanter akanter commented Jul 22, 2026

Copy link
Copy Markdown
Contributor

Summary

Phase 1 grid-api slice of the KYC singleton flow — OpenAPI schema additions, the new EDD endpoint, and Mintlify sandbox docs.

OpenAPI schemas

  • taxIdentification added to IndividualCustomerFields.yaml as a single optional object referencing the existing IdentificationType enum (SSN | ITIN | EIN | NON_US_TAX_ID).
  • New TaxIdentification.yaml (idType, identifier, countryOfIssuance).
  • New shared FieldConstraint.yaml under errors/ — regex / format / enum / min-max length. Error400.yaml's details description documents the convention: on submit-endpoint validation failures, details.constraint conforms to FieldConstraint, giving platforms a machine-readable validator hint they can use to pre-validate their onboarding form before re-submitting. Applies to POST /customers, PATCH /customers/{id}, POST /customers/edd, PATCH /customers/edd.
  • New enum schemas: IndividualSourceOfFundsCategory (11 individual-appropriate values), SourceOfWealthCategory, AnnualIncomeRange, NetWorthRange, PepStatus, ExpectedMonthlyTransactionCount, ExpectedMonthlyTransactionVolume. IndividualSourceOfFundsCategory sits alongside the existing (business) SourceOfFundsCategory, keeping the business flow's enum untouched. ExpectedMonthlyTransactionCount/Volume are extracted from previously-inline definitions in BusinessInfo.yaml and shared with the new EDD schema.

New EDD endpoint

Flat linked-resource pattern per openapi/README.md — customerId in the request body on POST, as a query param on GET/PATCH.

  • POST /customers/edd — create or replace a customer's enhanced-due-diligence fields (200, upsert semantics).
  • GET /customers/edd?customerId={id} — retrieve (404 if none recorded).
  • PATCH /customers/edd?customerId={id} — partial update.
  • CustomerEddCreateRequest.yaml — POST body: customerId + 8 optional EDD fields.
  • CustomerEdd.yaml — response and PATCH body: 8 optional fields (sourceOfFundsCategories, sourceOfWealthCategories, purposeOfAccount, expectedMonthlyTransactionCount, expectedMonthlyTransactionVolume, annualIncomeRange, netWorthRange, pepStatus).
  • Registered in openapi/openapi.yaml.

Sandbox docs

  • mintlify/snippets/sandbox-verification.mdx — combined snippet covering all three verification types (business KYB, beneficial-owner KYC, individual-customer KYC).
  • Individual KYC outcome table: fullName suffix 001 → PENDING, 002 → REJECTED, 003 → APPROVED with EDD-required flow, else APPROVED.
  • Full curl example: create individual customer → verify (returns VerificationError with EDD_INFORMATION_MISSING) → POST /customers/edd → retry verification → APPROVED.
  • Import in mintlify/api-reference/sandbox-testing.mdx points at the renamed snippet.

Verification

  • make build succeeds — bundled openapi.yaml and mintlify/openapi.yaml regenerated.
  • make lint passes (exit 0; 14 pre-existing warnings, no new errors introduced by this diff).

Scope

grid-api (OpenAPI spec + Mintlify docs) only. Sparkcore persistence, sandbox switch wiring, and Sumsub integration ship on webdev#31010 (plan up for approval). OFAC / LexisNexis handling is a separate workstream.

Reply with a comment (e.g. LGTM) to approve — emoji reactions don't notify me here.

@vercel

vercel Bot commented Jul 22, 2026

Copy link
Copy Markdown

The latest updates on your projects. Learn more about Vercel for GitHub.

2 Skipped Deployments
Project Deployment Actions Updated (UTC)
grid-flow-builder Ignored Ignored Preview Jul 27, 2026 11:20pm
grid-wallet-demo Ignored Ignored Preview Jul 27, 2026 11:20pm

Request Review

akanter commented Jul 22, 2026

Copy link
Copy Markdown
Contributor Author

This stack of pull requests is managed by Graphite. Learn more about stacking.

@ls-bolt ls-bolt Bot mentioned this pull request Jul 22, 2026
@ls-bolt

ls-bolt Bot commented Jul 22, 2026

Copy link
Copy Markdown
Contributor

⚡ On it — stripping the LexisNexis content from the plan (that was just answering your investigation question, not implementation scope). Also adding sandbox support to the design doc (#700) since customers need to be able to drive the KYC flow through sandbox.

@ls-bolt

ls-bolt Bot commented Jul 22, 2026

Copy link
Copy Markdown
Contributor

Got it — leaving this plan PR alone for now. Jumping over to grid-api#700 to add the sandbox section to the design doc first. Once the sandbox design is settled there, I'll come back and update this plan (both dropping the LexisNexis content per your earlier note, and incorporating sandbox into the implementation phasing).

@ls-bolt

ls-bolt Bot commented Jul 22, 2026

Copy link
Copy Markdown
Contributor

Aligned on the strategy. Reworking Phase 1 to be: full API surface (schemas + new endpoints) + full sandbox behavior, no real production wire-up yet. That lets customers integrate against sandbox immediately while we progressively fill in the real Sumsub / LexisNexis / doc pass-through behind the same API. Production wire-up becomes Phase 2+.

Also dropping the LexisNexis content per your earlier note. Standby.

@ls-bolt

ls-bolt Bot commented Jul 22, 2026

Copy link
Copy Markdown
Contributor

⚡ Starting Phase 1 implementation now. Since design doc #700 has the current-state design (with sandbox first-class and OFAC treated as separate work), I'll:

  1. Trim the plan doc here — strip the LexisNexis / OFAC content (per your note that OFAC is a separate thing), align phasing with what's in plan-kyc-api-design #700's Implementation phasing section.
  2. Land the Phase 1 grid-api items in this PR: taxIdentification[] on IndividualCustomerFields, optional EDD extras, VerificationError code + constraint, new GET /verification/requirements endpoint + response schema, submitForVerification flag on POST /customers / PATCH /customers/{id}, and rename sandbox-kyb-verification.mdxsandbox-verification.mdx with the new individual-customer KYC subsection.
  3. Regenerate the bundled openapi.yaml via make build and run make lint.
  4. Sparkcore backend work is a separate PR in webdev/sparkcore — will handle after this lands.

@ls-bolt
ls-bolt Bot force-pushed the 07-22-kyc-singleton-implementation-plan branch from 8f5c9ef to d64ecbf Compare July 22, 2026 23:13
@github-actions github-actions Bot added the breaking-change Introduces a breaking change to the OpenAPI spec label Jul 22, 2026
@ls-bolt

ls-bolt Bot commented Jul 22, 2026

Copy link
Copy Markdown
Contributor

First Phase 1 slice pushed (R2). make lint passes; bundled openapi.yaml regenerates cleanly.

In this push:

  • Plan doc rewrite — stripped all LexisNexis / OFAC content, tightened to Phase 1 grid-api scope, pointing to design PR plan-kyc-api-design #700 for full context.
  • §1 taxIdentification[] on IndividualCustomerFields — new TaxIdentification.yaml schema (idType + identifier + countryOfIssuance), array on the individual customer.
  • §1a Optional EDD extras on IndividualCustomerFields — added sourceOfFundsCategories (reuses existing), sourceOfWealthCategories (new enum), purposeOfAccount (reuses existing), expectedMonthlyTransactionCount / Volume (extracted to shared enum schemas), annualIncomeRange (new), netWorthRange (new), pepStatus (new).
  • §2 VerificationError code + constraint — added optional fields; FieldConstraint.yaml extracted as shared schema (will be reused by /verification/requirements in the next slice).
  • §5 submitForVerification on POST /customers — new optional boolean on the create-request; new CustomerCreateResponse.yaml wraps { customer, verification? }; the 201 response schema on POST /customers now uses the wrapped shape.

Still to come in follow-up commits on this PR:

  • §5 submitForVerification on PATCH /customers/{customerId} (mirrors POST — add flag to CustomerUpdateRequest, wrap the 200 response).
  • §3 GET /verification/requirements endpoint spec + VerificationRequirements.yaml response schema + supporting FieldRequirement, DocumentRequirement, AddressFormat schemas + registration in root openapi.yaml.
  • Mintlify — rename sandbox-kyb-verification.mdxsandbox-verification.mdx, add "Individual customer verification" subsection with the fullName-suffix table, update imports across api-reference/sandbox-testing.mdx and per-product pages.
  • One small design decision surfaced: the existing SourceOfFundsCategory enum is business-focused (OPERATING_REVENUE, INVESTMENT_INCOME, VENTURE_CAPITAL, etc.) — the individual example I put in the schema had to fall back to PERSONAL_SAVINGS. Worth deciding whether we expand the existing enum with individual-appropriate values (SALARY, FREELANCE_INCOME, etc.) or add a parallel IndividualSourceOfFundsCategory. Not urgent — schema shape is fine as-is — but flagging for later.

Sparkcore backend PR will follow separately (different repo).

@github-actions

github-actions Bot commented Jul 22, 2026

Copy link
Copy Markdown
Contributor

✱ Stainless preview builds for grid

This PR will update the grid SDKs with the following commit messages.

cli

feat: plan-kyc-singleton-implementation

go

feat(api): add EDD fields to customers, verification error constraints, CustomerNewResponse

kotlin

feat: plan-kyc-singleton-implementation

openapi

feat: plan-kyc-singleton-implementation

php

feat: plan-kyc-singleton-implementation

python

feat(api): add CustomerCreateResponse model, customer verification/tax/PEP fields

ruby

feat: plan-kyc-singleton-implementation

typescript

feat: plan-kyc-singleton-implementation
⚠️ grid-openapi studio · code

Your SDK build had at least one "warning" diagnostic.
generate ⚠️

grid-ruby studio · code

Your SDK build had at least one "note" diagnostic.
generate ✅build ✅lint ✅test ✅

⚠️ grid-go studio · code

Your SDK build had a failure in the lint CI job, which is a regression from the base state.
generate ✅build ✅lint ❗test ❗

go get github.com/stainless-sdks/grid-go@a211828cf961f8dbaff91db5a35a5add3101ec45
⚠️ grid-kotlin studio · code

Your SDK build had a failure in the test CI job, which is a regression from the base state.
generate ⚠️build ✅lint ✅test ❗

⚠️ grid-python studio · code

Your SDK build had a failure in the lint CI job, which is a regression from the base state.
generate ✅build ✅lint ❗test ❗

pip install https://pkg.stainless.com/s/grid-python/81a59ecf661023eccf385fe5dbf62f7ae995a803/grid-0.0.1-py3-none-any.whl
grid-typescript studio · conflict

Your SDK build had at least one note diagnostic.

grid-php studio · code

Your SDK build had at least one "note" diagnostic.
generate ✅lint ✅test ✅

⚠️ grid-cli studio · code

Your SDK build had a failure in the build CI job, which is a regression from the base state.
generate ⚠️build ❗lint ❗test ❗


This comment is auto-generated by GitHub Actions and is automatically kept up to date as you push.
If you push custom code to the preview branch, re-run this workflow to update the comment.
Last updated: 2026-07-28 00:03:45 UTC

@mintlify

mintlify Bot commented Jul 22, 2026

Copy link
Copy Markdown
Contributor

Preview deployment for your docs. Learn more about Mintlify Previews.

Project Status Preview Updated (UTC)
Grid 🟢 Ready View Preview Jul 22, 2026, 11:14 PM

@ls-bolt
ls-bolt Bot force-pushed the 07-22-kyc-singleton-implementation-plan branch from d64ecbf to 67a3a37 Compare July 22, 2026 23:15
@ls-bolt
ls-bolt Bot force-pushed the 07-22-kyc-singleton-implementation-plan branch from 67a3a37 to 0783bc0 Compare July 23, 2026 01:20
@ls-bolt

ls-bolt Bot commented Jul 23, 2026

Copy link
Copy Markdown
Contributor

R4 pushed — aligned with the 6 design changes in #700 R13. make lint clean.

Reverted from R3:

  • Removed submitForVerification from CustomerCreateRequest.yaml; POST /customers 201 response reverted to CustomerOneOf (deleted CustomerCreateResponse.yaml).
  • Removed code from VerificationError.yaml (kept constraint — the existing VerificationErrorType enum already covers programmatic categorization).
  • Removed all EDD-extras fields from IndividualCustomerFields.yaml; they moved to the new EDD endpoint below.
  • Changed taxIdentification on IndividualCustomerFields.yaml from array to single object (dropped the multi-jurisdictional-taxpayer array).

Added:

  • New POST /customers/{customerId}/edd, GET, PATCH endpoint (openapi/paths/customers/customers_{customerId}_edd.yaml).
  • New CustomerEdd.yaml request/response schema — 8 EDD fields referencing existing / new enum schemas (SourceOfWealthCategory, AnnualIncomeRange, NetWorthRange, PepStatus, SourceOfFundsCategory, PurposeOfAccount, ExpectedMonthlyTransactionCount/Volume).
  • Registered /customers/{customerId}/edd in openapi/openapi.yaml.
  • Extended mintlify/snippets/sandbox-verification.mdx — new 003 suffix row on the individual-customer KYC outcome table + a full curl example flow showing create → verify (MISSING_FIELD on edd.*) → POST /customers/{id}/edd → retry.
  • Plan doc (docs/plans/2026-07-22-kyc-singleton-implementation.md) rewritten to match the new scope.

Kept from R3:

  • Enum schemas (SourceOfWealthCategory.yaml, AnnualIncomeRange.yaml, NetWorthRange.yaml, PepStatus.yaml, ExpectedMonthlyTransactionCount.yaml, ExpectedMonthlyTransactionVolume.yaml) — now referenced by CustomerEdd.yaml instead of IndividualCustomerFields.yaml.
  • TaxIdentification.yaml — now referenced as a single object rather than array element.
  • FieldConstraint.yaml — still referenced by VerificationError.constraint.
  • sandbox-verification.mdx rename (from sandbox-kyb-verification.mdx) + <SandboxVerification /> import update in mintlify/api-reference/sandbox-testing.mdx.

One design nit flagged in the plan doc's Risks section: existing SourceOfFundsCategory enum is business-focused (OPERATING_REVENUE, INVESTMENT_INCOME, VENTURE_CAPITAL, etc.). Reusing it for individual EDD leaves only PERSONAL_SAVINGS and OTHER clearly applicable. Worth deciding whether to expand it with individual-appropriate values (SALARY, FREELANCE_INCOME) or add a parallel IndividualSourceOfFundsCategory enum. Not blocking Phase 1 — schema is functionally fine — but flagging as a design decision.

Comment thread docs/plans/2026-07-22-kyc-singleton-implementation.md Outdated
Comment thread openapi/paths/customers/customers_{customerId}_edd.yaml Outdated
@akanter akanter added the bolt label Jul 23, 2026 — with Graphite App
@ls-bolt

ls-bolt Bot commented Jul 24, 2026

Copy link
Copy Markdown
Contributor

Revision 11

Addressed @pengying's two inline comments:

  • CustomerEdd.yaml:52 — added createdAt + updatedAt (both readOnly: true, ISO-8601), matching the Customer.yaml timestamp convention. Added updatedAt as well since EDD is PATCH-able.
  • IndividualCustomerFields.yaml:26 — reverted taxIdentification from single $ref to array. My 07-23 R14 note on #700 said we could revisit if real demand showed up; dual citizens / multi-jurisdictional taxpayers are common enough to make the array the safer default.

Also rolled into this commit (was pending in the working tree from a prior local session that lost the dedicated commit to a rebase, so I let gt modify pick it up):

  • errors/FieldConstraint.yaml — trimmed the rationale prose per @akanter's earlier feedback ("description says WHAT the field is, not WHY it exists"). Net: 45 lines → 35 lines.

Rebundled openapi.yaml via make build; make lint exits 0 (only pre-existing informational warnings on other schemas).

@ls-bolt
ls-bolt Bot force-pushed the 07-22-kyc-singleton-implementation-plan branch from f0a2dd8 to 1575a8b Compare July 24, 2026 19:56
@ls-bolt

ls-bolt Bot commented Jul 24, 2026

Copy link
Copy Markdown
Contributor

Revision 12

Flattened taxIdentification per @akanter's design landing on the offline discussion. Reverted R11's array change and eliminated the TaxIdentification wrapper entirely — the three fields now live at top-level on IndividualCustomerFields with a taxId prefix:

  • taxIdType$ref: ./IdentificationType.yaml
  • taxIdentifier — the tax-identification number (SSN / ITIN / foreign tax ID)
  • taxIdCountryOfIssuance — ISO 3166-1 alpha-2 country code, required when taxIdType is NON_US_TAX_ID

Follow-on cleanup:

  • Deleted openapi/components/schemas/customers/TaxIdentification.yaml (no longer referenced)
  • Updated Error400.details.field example taxIdentification.identifiertaxIdentifier
  • Rebundled openapi.yaml + mintlify/openapi.yaml

Rationale (matches Stripe's Person.id_number / id_number_type shape): 90% case for Grid customers is one primary tax ID; wrapping in a sub-object was dead weight for that common path. If real dual-citizen / multi-jurisdictional demand shows up post-launch, we can add additionalTaxIdentifications: [] alongside the primary flat fields — additive, no breaking change.

Downstream impact on webdev#31108 (PR B)

Sparkcore-side already has flat columns on the ent (tax_id_type, tax_identifier, tax_id_country_of_issuance) from the earlier column-rename, so the DB shape is already correct. The Pydantic-side TaxIdentification sub-object schema will need to flatten to match the API surface. Small handler-wiring change too. Will explore + push as a small PR B revision — flagging separately here in case anyone wants a different approach.

@ls-bolt

ls-bolt Bot commented Jul 24, 2026

Copy link
Copy Markdown
Contributor

Revision 13 — pathParam nesting per @akanter's landing

Switched /customers/edd?customerId={id} (queryParam) to /customers/{customerId}/edd (pathParam nesting), per your call rolling with the RESTful pathParam approach that @shreyav and @jklein24 leaned toward.

Changes:

Composes naturally to /transactions/{transactionId}/edd if we ever ship transaction-EDD (per your "we'll have a matching /transactions/{id}/edd" note).

Sparkcore-side follow-up (webdev #31107): switching the handler routes from /customers/edd (queryParam) to /customers/<customer_id>/edd (pathParam) coming next — will restack #31110 and #31111 onto the new PR A tip.

make build + make lint: clean. Both commits pushed.

akanter and others added 6 commits July 27, 2026 23:19
Addresses @jklein24's review questions on grid-api#726:
- top-level: explicit that FieldConstraint drives both client-side
  pre-validation and direct form-UI rendering (HTML5 input type,
  inputmode, autocomplete, maxlength attributes).
- format: usage guidance — prefer HTML5 input type names when the
  field maps to one; use semantic slugs otherwise; treat unknown
  values as opaque and fall back to accompanying pattern/length
  constraints. Example changed to `email` (HTML5 canonical).
- pattern: notes that length is surfaced as separate minLength/
  maxLength fields so consumers can wire native controls without
  regex evaluation.
- minLength / maxLength: rationale for being distinct fields
  (native <input maxlength> wire-up) documented.

No schema shape change; description-only.

Co-Authored-By: jklein24 <jklein24@users.noreply.github.com>
…xIdentifier/taxIdCountryOfIssuance (@akanter direction, Stripe-style)
…d (pathParam nesting per @akanter direction)

Restful pathParam nesting matches shreyav's and jklein24's inclination:

- Renamed openapi/paths/customers/customers_edd.yaml → customers_{customerId}_edd.yaml
- Added path-level customerId path parameter (inherited by GET/POST/PATCH)
- Removed per-operation customerId query parameter
- Deleted CustomerEddCreateRequest.yaml (customerId no longer in body; CustomerEdd.yaml is now the POST request body directly)
- openapi/openapi.yaml: path key /customers/edd -> /customers/{customerId}/edd, grouped with other /customers/{customerId}/... subresources

Composes naturally to /transactions/{id}/edd if we ever ship transaction-EDD.
…tomers/{id}/edd endpoint)

@akanter's landed direction after design discussion + 8-vendor competitive research (BaaS
precedent unanimous: Stripe/Bridge/Unit all put EDD on the customer resource).

- Delete openapi/paths/customers/customers_{customerId}_edd.yaml (was R13's endpoint file)
- Remove /customers/{customerId}/edd path key from openapi/openapi.yaml
- IndividualCustomerFields.yaml: restructure to allOf composition with CustomerEdd.yaml,
  so the 11 EDD fields (SoF, SoW, PEP, income, net worth, purpose, txn volume/count, plus
  3 *OtherDescription companions) become optional top-level attributes on the customer
- CustomerEdd.yaml: drop createdAt/updatedAt (no longer a resource; Customer's own
  timestamps suffice); update description to reflect the new pattern
- Error400.yaml: drop /customers/{id}/edd from submit-endpoints example list

API surface != DB schema: sparkcore-side still keeps EntUmaaasCustomerEdd as a separate
1:1-nullable table to avoid NULL bloat on IndividualUserInfo; only customers who submit
EDD get a row. See webdev#31107 PR A rework for the sparkcore-side change.
@ls-bolt
ls-bolt Bot force-pushed the 07-22-kyc-singleton-implementation-plan branch from a7aeaff to af730cd Compare July 27, 2026 23:20
@github-actions github-actions Bot added the breaking-change Introduces a breaking change to the OpenAPI spec label Jul 27, 2026
@github-actions

Copy link
Copy Markdown
Contributor

⚠️ Breaking OpenAPI changes detected

This PR introduces breaking changes to openapi.yaml:

API Changelog 2025-10-13 vs. 2025-10-13

API Changes

GET /customers

  • ⚠️ the data/items/oneOf[subschema #1: Individual Customer]/allOf[#/components/schemas/IndividualCustomerFields]/ response's property type/format changed from object/to/`` for status 200
  • ⚠️ removed the required property data/items/oneOf[subschema #1: Individual Customer]/allOf[#/components/schemas/IndividualCustomerFields]/customerType from the response with the 200 status
  • ⚠️ removed the optional property data/items/oneOf[subschema #1: Individual Customer]/allOf[#/components/schemas/IndividualCustomerFields]/address from the response with the 200 status
  • ⚠️ removed the optional property data/items/oneOf[subschema #1: Individual Customer]/allOf[#/components/schemas/IndividualCustomerFields]/birthDate from the response with the 200 status
  • ⚠️ removed the optional property data/items/oneOf[subschema #1: Individual Customer]/allOf[#/components/schemas/IndividualCustomerFields]/fullName from the response with the 200 status
  • ⚠️ removed the optional property data/items/oneOf[subschema #1: Individual Customer]/allOf[#/components/schemas/IndividualCustomerFields]/kycStatus from the response with the 200 status
  • ⚠️ removed the optional property data/items/oneOf[subschema #1: Individual Customer]/allOf[#/components/schemas/IndividualCustomerFields]/nationality from the response with the 200 status

POST /customers

  • ⚠️ added #/components/schemas/CustomerEdd, subschema #1 to the oneOf[subschema #1: Individual Customer Create Request]/allOf[#/components/schemas/IndividualCustomerFields]/ request property allOf list
  • ⚠️ the oneOf[subschema #1: Individual Customer Create Request]/allOf[#/components/schemas/IndividualCustomerFields]/ request property type/format changed from object/to/``
  • ⚠️ the oneOf[subschema #1: Individual Customer]/allOf[#/components/schemas/IndividualCustomerFields]/ response's property type/format changed from object/to/`` for status 201
  • ⚠️ removed the required property oneOf[subschema #1: Individual Customer]/allOf[#/components/schemas/IndividualCustomerFields]/customerType from the response with the 201 status
  • ⚠️ removed the request property oneOf[subschema #1: Individual Customer Create Request]/allOf[#/components/schemas/IndividualCustomerFields]/address
  • ⚠️ removed the request property oneOf[subschema #1: Individual Customer Create Request]/allOf[#/components/schemas/IndividualCustomerFields]/birthDate
  • ⚠️ removed the request property oneOf[subschema #1: Individual Customer Create Request]/allOf[#/components/schemas/IndividualCustomerFields]/customerType
  • ⚠️ removed the request property oneOf[subschema #1: Individual Customer Create Request]/allOf[#/components/schemas/IndividualCustomerFields]/fullName
  • ⚠️ removed the request property oneOf[subschema #1: Individual Customer Create Request]/allOf[#/components/schemas/IndividualCustomerFields]/kycStatus
  • ⚠️ removed the request property oneOf[subschema #1: Individual Customer Create Request]/allOf[#/components/schemas/IndividualCustomerFields]/nationality
  • ⚠️ removed the optional property oneOf[subschema #1: Individual Customer]/allOf[#/components/schemas/IndividualCustomerFields]/address from the response with the 201 status
  • ⚠️ removed the optional property oneOf[subschema #1: Individual Customer]/allOf[#/components/schemas/IndividualCustomerFields]/birthDate from the response with the 201 status
  • ⚠️ removed the optional property oneOf[subschema #1: Individual Customer]/allOf[#/components/schemas/IndividualCustomerFields]/fullName from the response with the 201 status
  • ⚠️ removed the optional property oneOf[subschema #1: Individual Customer]/allOf[#/components/schemas/IndividualCustomerFields]/kycStatus from the response with the 201 status
  • ⚠️ removed the optional property oneOf[subschema #1: Individual Customer]/allOf[#/components/schemas/IndividualCustomerFields]/nationality from the response with the 201 status

DELETE /customers/{customerId}

  • ⚠️ the oneOf[subschema #1: Individual Customer]/allOf[#/components/schemas/IndividualCustomerFields]/ response's property type/format changed from object/to/`` for status 200
  • ⚠️ removed the required property oneOf[subschema #1: Individual Customer]/allOf[#/components/schemas/IndividualCustomerFields]/customerType from the response with the 200 status
  • ⚠️ removed the optional property oneOf[subschema #1: Individual Customer]/allOf[#/components/schemas/IndividualCustomerFields]/address from the response with the 200 status
  • ⚠️ removed the optional property oneOf[subschema #1: Individual Customer]/allOf[#/components/schemas/IndividualCustomerFields]/birthDate from the response with the 200 status
  • ⚠️ removed the optional property oneOf[subschema #1: Individual Customer]/allOf[#/components/schemas/IndividualCustomerFields]/fullName from the response with the 200 status
  • ⚠️ removed the optional property oneOf[subschema #1: Individual Customer]/allOf[#/components/schemas/IndividualCustomerFields]/kycStatus from the response with the 200 status
  • ⚠️ removed the optional property oneOf[subschema #1: Individual Customer]/allOf[#/components/schemas/IndividualCustomerFields]/nationality from the response with the 200 status

GET /customers/{customerId}

  • ⚠️ the oneOf[subschema #1: Individual Customer]/allOf[#/components/schemas/IndividualCustomerFields]/ response's property type/format changed from object/to/`` for status 200
  • ⚠️ removed the required property oneOf[subschema #1: Individual Customer]/allOf[#/components/schemas/IndividualCustomerFields]/customerType from the response with the 200 status
  • ⚠️ removed the optional property oneOf[subschema #1: Individual Customer]/allOf[#/components/schemas/IndividualCustomerFields]/address from the response with the 200 status
  • ⚠️ removed the optional property oneOf[subschema #1: Individual Customer]/allOf[#/components/schemas/IndividualCustomerFields]/birthDate from the response with the 200 status
  • ⚠️ removed the optional property oneOf[subschema #1: Individual Customer]/allOf[#/components/schemas/IndividualCustomerFields]/fullName from the response with the 200 status
  • ⚠️ removed the optional property oneOf[subschema #1: Individual Customer]/allOf[#/components/schemas/IndividualCustomerFields]/kycStatus from the response with the 200 status
  • ⚠️ removed the optional property oneOf[subschema #1: Individual Customer]/allOf[#/components/schemas/IndividualCustomerFields]/nationality from the response with the 200 status

PATCH /customers/{customerId}

  • ⚠️ added #/components/schemas/CustomerEdd, subschema #1 to the oneOf[subschema #1: Individual Customer Update Request]/allOf[#/components/schemas/IndividualCustomerFields]/ request property allOf list
  • ⚠️ the oneOf[subschema #1: Individual Customer Update Request]/allOf[#/components/schemas/IndividualCustomerFields]/ request property type/format changed from object/to/``
  • ⚠️ the oneOf[subschema #1: Individual Customer]/allOf[#/components/schemas/IndividualCustomerFields]/ response's property type/format changed from object/to/`` for status 200
  • ⚠️ removed the required property oneOf[subschema #1: Individual Customer]/allOf[#/components/schemas/IndividualCustomerFields]/customerType from the response with the 200 status
  • ⚠️ removed the request property oneOf[subschema #1: Individual Customer Update Request]/allOf[#/components/schemas/IndividualCustomerFields]/address
  • ⚠️ removed the request property oneOf[subschema #1: Individual Customer Update Request]/allOf[#/components/schemas/IndividualCustomerFields]/birthDate
  • ⚠️ removed the request property oneOf[subschema #1: Individual Customer Update Request]/allOf[#/components/schemas/IndividualCustomerFields]/customerType
  • ⚠️ removed the request property oneOf[subschema #1: Individual Customer Update Request]/allOf[#/components/schemas/IndividualCustomerFields]/fullName
  • ⚠️ removed the request property oneOf[subschema #1: Individual Customer Update Request]/allOf[#/components/schemas/IndividualCustomerFields]/kycStatus
  • ⚠️ removed the request property oneOf[subschema #1: Individual Customer Update Request]/allOf[#/components/schemas/IndividualCustomerFields]/nationality
  • ⚠️ removed the optional property oneOf[subschema #1: Individual Customer]/allOf[#/components/schemas/IndividualCustomerFields]/address from the response with the 200 status
  • ⚠️ removed the optional property oneOf[subschema #1: Individual Customer]/allOf[#/components/schemas/IndividualCustomerFields]/birthDate from the response with the 200 status
  • ⚠️ removed the optional property oneOf[subschema #1: Individual Customer]/allOf[#/components/schemas/IndividualCustomerFields]/fullName from the response with the 200 status
  • ⚠️ removed the optional property oneOf[subschema #1: Individual Customer]/allOf[#/components/schemas/IndividualCustomerFields]/kycStatus from the response with the 200 status
  • ⚠️ removed the optional property oneOf[subschema #1: Individual Customer]/allOf[#/components/schemas/IndividualCustomerFields]/nationality from the response with the 200 status

POST webhook:customer-update

  • ⚠️ added #/components/schemas/CustomerEdd, subschema #1 to the allOf[subschema #2]/data/oneOf[subschema #1: Individual Customer]/allOf[#/components/schemas/IndividualCustomerFields]/ request property allOf list
  • ⚠️ the allOf[subschema #2]/data/oneOf[subschema #1: Individual Customer]/allOf[#/components/schemas/IndividualCustomerFields]/ request property type/format changed from object/to/``
  • ⚠️ removed the request property allOf[subschema #2]/data/oneOf[subschema #1: Individual Customer]/allOf[#/components/schemas/IndividualCustomerFields]/address
  • ⚠️ removed the request property allOf[subschema #2]/data/oneOf[subschema #1: Individual Customer]/allOf[#/components/schemas/IndividualCustomerFields]/birthDate
  • ⚠️ removed the request property allOf[subschema #2]/data/oneOf[subschema #1: Individual Customer]/allOf[#/components/schemas/IndividualCustomerFields]/customerType
  • ⚠️ removed the request property allOf[subschema #2]/data/oneOf[subschema #1: Individual Customer]/allOf[#/components/schemas/IndividualCustomerFields]/fullName
  • ⚠️ removed the request property allOf[subschema #2]/data/oneOf[subschema #1: Individual Customer]/allOf[#/components/schemas/IndividualCustomerFields]/kycStatus
  • ⚠️ removed the request property allOf[subschema #2]/data/oneOf[subschema #1: Individual Customer]/allOf[#/components/schemas/IndividualCustomerFields]/nationality

Detected by oasdiff. This PR will need approval from an API reviewer before merge.

@ls-bolt

ls-bolt Bot commented Jul 27, 2026

Copy link
Copy Markdown
Contributor

re github-actions[bot] breaking-change warning

Those warnings are false positives from the R14 restructure — the detector doesn't unwrap allOf composition when comparing schemas across revisions.

What actually changed:

  • IndividualCustomerFields structure switched from type: object at the top level to allOf: [<inline object>, <$ref to CustomerEdd>] — this is how the 11 EDD fields become optional patchable attributes on the customer without duplicating the field definitions.
  • The detector sees the top-level type disappear (moved into the first allOf sub-schema) and the required: [customerType] array move (also into the first sub-schema) and flags both as breaking.

They're not breaking — the merged schema (allOf semantics) still has type: object and still requires customerType. Any client that consumes the resolved schema (JSON Schema tools, generated SDKs) sees the same required-field contract.

Alternative: could inline the 11 EDD field definitions directly into IndividualCustomerFields.yaml (dropping the allOf composition) to silence the detector — but that duplicates the field defs across IndividualCustomerFields and future TransactionEdd schemas when they land. Happy to switch to the inlined form if you'd rather satisfy the detector than keep the DRY composition.

Rebase status: PR is now on latest main (af730cd5, clean rebase). Alembic single-head verified on the webdev PR stack. All 4 webdev PRs (#31107, #31108, #31110, #31111) MERGEABLE.

@akanter
akanter requested review from jklein24 and pengying July 27, 2026 23:37

Copy link
Copy Markdown
Contributor

You need a patch on the path /customers

@ls-bolt

ls-bolt Bot commented Jul 27, 2026

Copy link
Copy Markdown
Contributor

PATCH /customers/{customerId} already accepts EDD fields — its body composes IndividualCustomerFields (R14 restructured it as allOf with CustomerEdd.yaml), so the 11 EDD fields (SoF, SoW, PEP, income range, net worth, purpose, txn count/volume, plus 3 *OtherDescription companions) are patchable through the existing endpoint. Concretely: PATCH body → IndividualCustomerUpdateRequestallOf(CustomerUpdateRequest, IndividualCustomerFields)allOf(<basic fields>, CustomerEdd).

Let me know if you meant something different — e.g., a queryParam-style PATCH /customers?customerId={id} following your earlier queryParam preference (that'd be a separate migration touching every customer-scoped endpoint if we commit to it).

@akanter
akanter merged commit 9da2c08 into main Jul 27, 2026
10 checks passed
@akanter
akanter deleted the 07-22-kyc-singleton-implementation-plan branch July 27, 2026 23:57
jklein24 pushed a commit that referenced this pull request Jul 30, 2026
## Summary
- Fix sandbox-verification.mdx EDD example to use correct endpoint
(`PATCH /customers/{customerId}` instead of non-existent `POST
/customers/edd`)
- Replace invalid `PERSONAL_SPEND` enum value with valid `P2P_TRANSFERS`
from PurposeOfAccount schema
- Add required `customerType: INDIVIDUAL` to PATCH request body

This sync addresses documentation drift from the Grid Programmatic KYC
API (#726) changes.

## Test plan
- [x] Verified `P2P_TRANSFERS` is a valid PurposeOfAccount enum value in
`openapi/components/schemas/customers/PurposeOfAccount.yaml`
- [x] Verified EDD fields are patched via `PATCH
/customers/{customerId}` per `CustomerEdd.yaml` description and
`IndividualCustomerUpdateRequest.yaml` schema
- [x] Ran `make lint` - no new errors

🤖 Generated with [Claude Code](https://claude.com/claude-code)

Co-authored-by: claude[bot] <41898282+claude[bot]@users.noreply.github.com>
Co-authored-by: Claude Opus 4.5 <noreply@anthropic.com>
Sign up for free to join this conversation on GitHub. Already have an account? Sign in to comment

Labels

bolt breaking-change Introduces a breaking change to the OpenAPI spec

Projects

None yet

Development

Successfully merging this pull request may close these issues.

4 participants