Skip to content

feat(customers): accept typed agreement consents for seven documents - #990

Open
bsiaotickchong wants to merge 1 commit into
mainfrom
09-11-grid-api-agreement-consents
Open

feat(customers): accept typed agreement consents for seven documents#990
bsiaotickchong wants to merge 1 commit into
mainfrom
09-11-grid-api-agreement-consents

Conversation

@bsiaotickchong

Copy link
Copy Markdown
Contributor

Summary

  • Replaces the single endUserTermsConsent field with agreementConsents, a list carrying one entry per agreement, so each document's acceptance is recorded and auditable separately.
  • Adds an AgreementType enum with the seven supported documents, and changes GET /customers/end-user-terms to return a catalog of all seven as { data: [{ type, version, url }] }.
  • Spec + docs only. Backend, migration, and generated-client work follow in separate PRs.

The seven agreement types

Document AgreementType
Lightspark Terms of Service (End User Terms) LIGHTSPARK_END_USER_TERMS
Lightspark E-SIGN consent LIGHTSPARK_E_SIGN_CONSENT
Lightspark Privacy Policy LIGHTSPARK_PRIVACY_POLICY
Lead E-SIGN consent LEAD_E_SIGN_CONSENT
Lead Accountholder Agreement LEAD_ACCOUNTHOLDER_AGREEMENT
Lead Privacy Policy LEAD_PRIVACY_POLICY
Lead Cardholder Agreement LEAD_CARDHOLDER_AGREEMENT

Each consent entry carries type plus the four existing evidence fields (acceptedAt, ipAddress, termsVersion, acceptanceMethod), all required. termsVersion is the version the catalog returns for that type — versions are scoped per type, so a version valid for one agreement is not valid for another.

Design decisions worth a reviewer's attention

The endpoint path is unchanged. GET /customers/end-user-terms keeps its path and gains a list response (operationId getEndUserTermslistAgreementDocuments). Renaming the path would be a second breaking change for no functional gain; happy to rename if you'd prefer to take both at once.

The catalog is not a requirements list. Seven supported types does not mean every customer must accept all seven. Which documents are required, for which products and customers, is a legal decision this PR deliberately does not encode.

info.version is intentionally not bumped. It tracks the published dated version — a live routing prefix, also hardcoded across the docs and the generated client — not each breaking merge. It has not moved since that rule landed in #679, across 36+ breaking-change PRs. The breaking-change label and API-reviewer approval are the real gate; flagging it here so that decision is explicit rather than implied.

Clients pinned to the dated version are not affected. The server change lands this shape on the /grid/rc release-candidate surface only, leaving /grid/2025-10-13 serving the existing contract. Cutting a new dated version is a separate, coordinated release.

retrieve_end_user_terms in .stainless/stainless.yml is left alone. It's a published SDK method name and its path is unchanged, so renaming it is an independent SDK-breaking decision. The three model mappings that pointed at now-deleted schemas are updated.

Migration for existing consumers

  • Acceptance already recorded under the old field maps only to LIGHTSPARK_END_USER_TERMS. Acceptance of one agreement never implies another, so the other six must be collected from each customer.
  • On update, supplying consents records additional acceptances; evidence already on file for other types is untouched. Omitting the field changes nothing.
  • Customer.agreementConsents is read-only and returns the most recent acceptance per accepted type — an empty list until the first acceptance, where the old field was omitted.

Changes: 21 files

  • openapi/components/schemas/customers/Agreement*.yaml — 6 new schemas (type, acceptance method, consent request/response, document, list response)
  • openapi/components/schemas/customers/EndUserTerms*.yaml — 3 superseded schemas removed (EndUserTerms, EndUserTermsAcceptanceMethod, EndUserTermsConsentRequest); EndUserTermsConsent is kept and marked deprecated: true
  • openapi/components/schemas/customers/Customer{,CreateRequest,UpdateRequest}.yaml — field swapped to the list
  • openapi/paths/customers/customers_end-user-terms.yaml — catalog response
  • openapi/webhooks/customer-update.yaml — 2 examples updated to match Customer
  • .stainless/stainless.yml — model mappings repointed
  • mintlify/ — disclosures rewritten, snippet + changelog entry, regenerated bundles

Rebase note

Rebased onto main (6 commits, through #934 "bind one funding source per card"). Two conflicts, both resolved keeping each side:

  • Customer.yaml — main still carried only endUserTermsConsent; this branch adds the deprecation marker plus agreementConsents. Took this branch's version, which contains both.
  • mintlify/changelog.mdx — both sides added a new top entry. Kept both, main's card-refunds entry above this one.

Verified after the rebase that main's card work survived intact (e.g. CardTransaction.originalTransactionId is present in the rebuilt bundle) alongside the seven-value enum and the deprecated field.

Test plan

This repo has no test suite; it is spec + docs. Verified:

  • make build bundles cleanly; both generated bundles regenerated and committed.
  • make lint exits 0 with 2 warnings / 14 ignored — byte-identical to the main baseline, confirmed by re-running lint on a stashed tree. No new lint problems.
  • Parsed the bundle and asserted: the enum holds exactly 7 unique values; all 5 consent fields are required; all three customer schemas expose agreementConsents and no longer expose endUserTermsConsent; the 200 response resolves to data[] of AgreementDocument with {type, version, url} all required; no EndUserTerms* schema remains; info.version unchanged.
  • An automated review pass caught the stale .stainless model mappings, which are fixed here. Its remaining finding is the version-bump question addressed above.

Public

Consent is now recorded per agreement type, and the agreements endpoint lists every supported document with its current version and URL.

Original PR: #954

@ls-bolt ls-bolt Bot added bolt breaking-change Introduces a breaking change to the OpenAPI spec labels Sep 14, 2026
@vercel

vercel Bot commented Sep 14, 2026

Copy link
Copy Markdown

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

Project Deployment Actions Updated
grid-cards-demo Error Error Sep 14, 2026 6:12pm UTC
2 Skipped Deployments
Project Deployment Actions Updated
grid-flow-builder Ignored Ignored Preview Sep 14, 2026 6:12pm UTC
grid-wallet-demo Ignored Ignored Preview Sep 14, 2026 6:12pm UTC

Request Review

Copy link
Copy Markdown

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

@github-actions

github-actions Bot commented Sep 14, 2026

Copy link
Copy Markdown
Contributor

⚠️ Breaking OpenAPI changes detected

oasdiff reports 700 error / 18 warning changes to openapi.yaml.
This PR will need approval from an API reviewer before merge.

Errors (700)

  • GET /agents — removed the required property reason from the response with the 400 status [response-required-property-removed].
  • GET /agents — removed the required property reason from the response with the 401 status [response-required-property-removed].
  • GET /agents — removed the required property reason from the response with the 500 status [response-required-property-removed].
  • POST /agents — removed the required property reason from the response with the 400 status [response-required-property-removed].
  • POST /agents — removed the required property reason from the response with the 401 status [response-required-property-removed].
  • POST /agents — removed the required property reason from the response with the 500 status [response-required-property-removed].
  • GET /agents/approvals — removed the required property reason from the response with the 400 status [response-required-property-removed].
  • GET /agents/approvals — removed the required property reason from the response with the 401 status [response-required-property-removed].
  • GET /agents/approvals — removed the required property reason from the response with the 500 status [response-required-property-removed].
  • POST /agents/device-codes/{code}/redeem — removed the required property reason from the response with the 400 status [response-required-property-removed].
  • POST /agents/device-codes/{code}/redeem — removed the required property reason from the response with the 404 status [response-required-property-removed].
  • POST /agents/device-codes/{code}/redeem — removed the required property reason from the response with the 500 status [response-required-property-removed].
  • GET /agents/device-codes/{code}/status — removed the required property reason from the response with the 401 status [response-required-property-removed].
  • GET /agents/device-codes/{code}/status — removed the required property reason from the response with the 404 status [response-required-property-removed].
  • GET /agents/device-codes/{code}/status — removed the required property reason from the response with the 500 status [response-required-property-removed].
  • GET /agents/me — removed the required property reason from the response with the 401 status [response-required-property-removed].
  • GET /agents/me — removed the required property reason from the response with the 500 status [response-required-property-removed].
  • GET /agents/me/actions — removed the required property reason from the response with the 401 status [response-required-property-removed].
  • GET /agents/me/actions — removed the required property reason from the response with the 500 status [response-required-property-removed].
  • GET /agents/me/actions/{actionId} — removed the required property reason from the response with the 401 status [response-required-property-removed].
  • GET /agents/me/actions/{actionId} — removed the required property reason from the response with the 404 status [response-required-property-removed].
  • GET /agents/me/actions/{actionId} — removed the required property reason from the response with the 500 status [response-required-property-removed].
  • GET /agents/me/external-accounts — removed the required property reason from the response with the 400 status [response-required-property-removed].
  • GET /agents/me/external-accounts — removed the required property reason from the response with the 401 status [response-required-property-removed].
  • GET /agents/me/external-accounts — removed the required property reason from the response with the 500 status [response-required-property-removed].
  • POST /agents/me/external-accounts — removed the required property reason from the response with the 400 status [response-required-property-removed].
  • POST /agents/me/external-accounts — removed the required property reason from the response with the 401 status [response-required-property-removed].
  • POST /agents/me/external-accounts — removed the required property reason from the response with the 409 status [response-required-property-removed].
  • POST /agents/me/external-accounts — removed the required property reason from the response with the 500 status [response-required-property-removed].
  • DELETE /agents/me/external-accounts/{externalAccountId} — removed the required property reason from the response with the 401 status [response-required-property-removed].
  • DELETE /agents/me/external-accounts/{externalAccountId} — removed the required property reason from the response with the 404 status [response-required-property-removed].
  • DELETE /agents/me/external-accounts/{externalAccountId} — removed the required property reason from the response with the 500 status [response-required-property-removed].
  • GET /agents/me/external-accounts/{externalAccountId} — removed the required property reason from the response with the 401 status [response-required-property-removed].
  • GET /agents/me/external-accounts/{externalAccountId} — removed the required property reason from the response with the 404 status [response-required-property-removed].
  • GET /agents/me/external-accounts/{externalAccountId} — removed the required property reason from the response with the 500 status [response-required-property-removed].
  • GET /agents/me/internal-accounts — removed the required property reason from the response with the 401 status [response-required-property-removed].
  • GET /agents/me/internal-accounts — removed the required property reason from the response with the 500 status [response-required-property-removed].
  • POST /agents/me/quotes — removed the required property reason from the response with the 400 status [response-required-property-removed].
  • POST /agents/me/quotes — removed the required property reason from the response with the 401 status [response-required-property-removed].
  • POST /agents/me/quotes — removed the required property reason from the response with the 403 status [response-required-property-removed].
  • POST /agents/me/quotes — removed the required property reason from the response with the 412 status [response-required-property-removed].
  • POST /agents/me/quotes — removed the required property reason from the response with the 424 status [response-required-property-removed].
  • POST /agents/me/quotes — removed the required property reason from the response with the 500 status [response-required-property-removed].
  • GET /agents/me/quotes/{quoteId} — removed the required property reason from the response with the 401 status [response-required-property-removed].
  • GET /agents/me/quotes/{quoteId} — removed the required property reason from the response with the 404 status [response-required-property-removed].
  • GET /agents/me/quotes/{quoteId} — removed the required property reason from the response with the 500 status [response-required-property-removed].
  • POST /agents/me/quotes/{quoteId}/execute — removed the required property reason from the response with the 400 status [response-required-property-removed].
  • POST /agents/me/quotes/{quoteId}/execute — removed the required property reason from the response with the 401 status [response-required-property-removed].
  • POST /agents/me/quotes/{quoteId}/execute — removed the required property reason from the response with the 403 status [response-required-property-removed].
  • POST /agents/me/quotes/{quoteId}/execute — removed the required property reason from the response with the 404 status [response-required-property-removed].
  • POST /agents/me/quotes/{quoteId}/execute — removed the required property reason from the response with the 409 status [response-required-property-removed].
  • POST /agents/me/quotes/{quoteId}/execute — removed the required property reason from the response with the 500 status [response-required-property-removed].
  • GET /agents/me/transactions — removed the required property reason from the response with the 400 status [response-required-property-removed].
  • GET /agents/me/transactions — removed the required property reason from the response with the 401 status [response-required-property-removed].
  • GET /agents/me/transactions — removed the required property reason from the response with the 500 status [response-required-property-removed].
  • GET /agents/me/transactions/{transactionId} — removed the required property reason from the response with the 401 status [response-required-property-removed].
  • GET /agents/me/transactions/{transactionId} — removed the required property reason from the response with the 404 status [response-required-property-removed].
  • GET /agents/me/transactions/{transactionId} — removed the required property reason from the response with the 500 status [response-required-property-removed].
  • DELETE /agents/{agentId} — removed the required property reason from the response with the 401 status [response-required-property-removed].
  • DELETE /agents/{agentId} — removed the required property reason from the response with the 404 status [response-required-property-removed].
  • DELETE /agents/{agentId} — removed the required property reason from the response with the 500 status [response-required-property-removed].
  • GET /agents/{agentId} — removed the required property reason from the response with the 401 status [response-required-property-removed].
  • GET /agents/{agentId} — removed the required property reason from the response with the 404 status [response-required-property-removed].
  • GET /agents/{agentId} — removed the required property reason from the response with the 500 status [response-required-property-removed].
  • PATCH /agents/{agentId} — removed the required property reason from the response with the 400 status [response-required-property-removed].
  • PATCH /agents/{agentId} — removed the required property reason from the response with the 401 status [response-required-property-removed].
  • PATCH /agents/{agentId} — removed the required property reason from the response with the 404 status [response-required-property-removed].
  • PATCH /agents/{agentId} — removed the required property reason from the response with the 500 status [response-required-property-removed].
  • POST /agents/{agentId}/actions/{actionId}/approve — removed the required property reason from the response with the 400 status [response-required-property-removed].
  • POST /agents/{agentId}/actions/{actionId}/approve — removed the required property reason from the response with the 401 status [response-required-property-removed].
  • POST /agents/{agentId}/actions/{actionId}/approve — removed the required property reason from the response with the 404 status [response-required-property-removed].
  • POST /agents/{agentId}/actions/{actionId}/approve — removed the required property reason from the response with the 409 status [response-required-property-removed].
  • POST /agents/{agentId}/actions/{actionId}/approve — removed the required property reason from the response with the 500 status [response-required-property-removed].
  • POST /agents/{agentId}/actions/{actionId}/reject — removed the required property reason from the response with the 400 status [response-required-property-removed].
  • POST /agents/{agentId}/actions/{actionId}/reject — removed the required property reason from the response with the 401 status [response-required-property-removed].
  • POST /agents/{agentId}/actions/{actionId}/reject — removed the required property reason from the response with the 404 status [response-required-property-removed].
  • POST /agents/{agentId}/actions/{actionId}/reject — removed the required property reason from the response with the 409 status [response-required-property-removed].
  • POST /agents/{agentId}/actions/{actionId}/reject — removed the required property reason from the response with the 500 status [response-required-property-removed].
  • POST /agents/{agentId}/device-codes — removed the required property reason from the response with the 400 status [response-required-property-removed].
  • POST /agents/{agentId}/device-codes — removed the required property reason from the response with the 401 status [response-required-property-removed].
  • POST /agents/{agentId}/device-codes — removed the required property reason from the response with the 404 status [response-required-property-removed].
  • POST /agents/{agentId}/device-codes — removed the required property reason from the response with the 409 status [response-required-property-removed].
  • POST /agents/{agentId}/device-codes — removed the required property reason from the response with the 500 status [response-required-property-removed].
  • PATCH /agents/{agentId}/policy — removed the required property reason from the response with the 400 status [response-required-property-removed].
  • PATCH /agents/{agentId}/policy — removed the required property reason from the response with the 401 status [response-required-property-removed].
  • PATCH /agents/{agentId}/policy — removed the required property reason from the response with the 404 status [response-required-property-removed].
  • PATCH /agents/{agentId}/policy — removed the required property reason from the response with the 500 status [response-required-property-removed].
  • GET /auth/credentials — removed the required property reason from the response with the 400 status [response-required-property-removed].
  • GET /auth/credentials — removed the required property reason from the response with the 401 status [response-required-property-removed].
  • GET /auth/credentials — removed the required property reason from the response with the 500 status [response-required-property-removed].
  • POST /auth/credentials — removed the required property reason from the response with the 400 status [response-required-property-removed].
  • POST /auth/credentials — removed the required property reason from the response with the 401 status [response-required-property-removed].
  • POST /auth/credentials — removed the required property reason from the response with the 404 status [response-required-property-removed].
  • POST /auth/credentials — removed the required property reason from the response with the 500 status [response-required-property-removed].
  • DELETE /auth/credentials/{id} — removed the required property reason from the response with the 400 status [response-required-property-removed].
  • DELETE /auth/credentials/{id} — removed the required property reason from the response with the 401 status [response-required-property-removed].
  • DELETE /auth/credentials/{id} — removed the required property reason from the response with the 404 status [response-required-property-removed].
  • DELETE /auth/credentials/{id} — removed the required property reason from the response with the 500 status [response-required-property-removed].
  • POST /auth/credentials/{id}/challenge — removed the required property reason from the response with the 400 status [response-required-property-removed].
  • POST /auth/credentials/{id}/challenge — removed the required property reason from the response with the 401 status [response-required-property-removed].
  • …and 600 more errors.

Warnings (18)

Show sample
  • POST /customers — removed the request property oneOf[subschema #1: Individual Customer Create Request]/allOf[#/components/schemas/CustomerCreateRequest]/endUserTermsConsent/acceptanceMethod [request-property-removed].
  • POST /customers — removed the request property oneOf[subschema #1: Individual Customer Create Request]/allOf[#/components/schemas/CustomerCreateRequest]/endUserTermsConsent/acceptedAt [request-property-removed].
  • POST /customers — removed the request property oneOf[subschema #1: Individual Customer Create Request]/allOf[#/components/schemas/CustomerCreateRequest]/endUserTermsConsent/ipAddress [request-property-removed].
  • POST /customers — removed the request property oneOf[subschema #1: Individual Customer Create Request]/allOf[#/components/schemas/CustomerCreateRequest]/endUserTermsConsent/termsVersion [request-property-removed].
  • POST /customers — removed the request property oneOf[subschema #2: Business Customer Create Request]/allOf[#/components/schemas/CustomerCreateRequest]/endUserTermsConsent/acceptanceMethod [request-property-removed].
  • POST /customers — removed the request property oneOf[subschema #2: Business Customer Create Request]/allOf[#/components/schemas/CustomerCreateRequest]/endUserTermsConsent/acceptedAt [request-property-removed].
  • POST /customers — removed the request property oneOf[subschema #2: Business Customer Create Request]/allOf[#/components/schemas/CustomerCreateRequest]/endUserTermsConsent/ipAddress [request-property-removed].
  • POST /customers — removed the request property oneOf[subschema #2: Business Customer Create Request]/allOf[#/components/schemas/CustomerCreateRequest]/endUserTermsConsent/termsVersion [request-property-removed].
  • PATCH /customers/{customerId} — removed the request property oneOf[subschema #1: Individual Customer Update Request]/allOf[subschema #1: Customer Update Request]/endUserTermsConsent/acceptanceMethod [request-property-removed].
  • PATCH /customers/{customerId} — removed the request property oneOf[subschema #1: Individual Customer Update Request]/allOf[subschema #1: Customer Update Request]/endUserTermsConsent/acceptedAt [request-property-removed].
  • PATCH /customers/{customerId} — removed the request property oneOf[subschema #1: Individual Customer Update Request]/allOf[subschema #1: Customer Update Request]/endUserTermsConsent/ipAddress [request-property-removed].
  • PATCH /customers/{customerId} — removed the request property oneOf[subschema #1: Individual Customer Update Request]/allOf[subschema #1: Customer Update Request]/endUserTermsConsent/termsVersion [request-property-removed].
  • PATCH /customers/{customerId} — removed the request property oneOf[subschema #2: Business Customer Update Request]/allOf[subschema #1: Customer Update Request]/endUserTermsConsent/acceptanceMethod [request-property-removed].
  • PATCH /customers/{customerId} — removed the request property oneOf[subschema #2: Business Customer Update Request]/allOf[subschema #1: Customer Update Request]/endUserTermsConsent/acceptedAt [request-property-removed].
  • PATCH /customers/{customerId} — removed the request property oneOf[subschema #2: Business Customer Update Request]/allOf[subschema #1: Customer Update Request]/endUserTermsConsent/ipAddress [request-property-removed].
  • PATCH /customers/{customerId} — removed the request property oneOf[subschema #2: Business Customer Update Request]/allOf[subschema #1: Customer Update Request]/endUserTermsConsent/termsVersion [request-property-removed].
  • POST webhook:customer-update — removed #/components/schemas/EndUserTermsConsentRequest from the allOf[subschema #2]/data/oneOf[subschema #1: Individual Customer]/allOf[#/components/schemas/Customer]/endUserTermsConsent request property allOf list [request-property-all-of-removed].
  • POST webhook:customer-update — removed #/components/schemas/EndUserTermsConsentRequest from the allOf[subschema #2]/data/oneOf[subschema #2: Business Customer]/allOf[#/components/schemas/Customer]/endUserTermsConsent request property allOf list [request-property-all-of-removed].

Detected by oasdiff. Full report: job summary or the oasdiff-report artifact.

@github-actions

github-actions Bot commented Sep 14, 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(api): add list-agreements method, agreementConsents field to customers

go

feat(api): add agreements endpoint/fields to customers, deprecate endUserTermsConsent

kotlin

feat(api): add listAgreements endpoint, agreementConsents field, deprecate retrieveEndUserTerms

openapi

feat(api): add agreements endpoint/fields, deprecate end-user-terms, update customers types

php

feat(api): add agreements endpoint/types, agreementConsents field, deprecate endUserTermsConsent

python

feat(api): add list_agreements, agreement_consents to customers, deprecate end_user_terms

ruby

feat(api): add agreements endpoint, remove EndUserTerms models, deprecate retrieve_end_user_terms

typescript

feat(api): add listAgreements method and agreementConsents field to customers

Edit this comment to update them. They will appear in their respective SDK's changelogs.

grid-typescript studio · code · diff

Your SDK build had at least one new note diagnostic, which is a regression from the base state.
generate ❗build ⏭️lint ⏭️test ⏭️

New diagnostics (4 note)
💡 Schema/DeprecatedWithoutMessage: Confirm schema is deprecated and add missing deprecation details if needed.
💡 Schema/DeprecatedWithoutMessage: Confirm schema is deprecated and add missing deprecation details if needed.
💡 Schema/DeprecatedWithoutMessage: Confirm schema is deprecated and add missing deprecation details if needed.
💡 Schema/DeprecatedWithoutMessage: Confirm schema is deprecated and add missing deprecation details if needed.
grid-openapi studio · code · diff

Your SDK build had at least one new note diagnostic, which is a regression from the base state.
generate ❗

New diagnostics (4 note)
💡 Schema/DeprecatedWithoutMessage: Confirm schema is deprecated and add missing deprecation details if needed.
💡 Schema/DeprecatedWithoutMessage: Confirm schema is deprecated and add missing deprecation details if needed.
💡 Schema/DeprecatedWithoutMessage: Confirm schema is deprecated and add missing deprecation details if needed.
💡 Schema/DeprecatedWithoutMessage: Confirm schema is deprecated and add missing deprecation details if needed.
grid-ruby studio · code · diff

Your SDK build had at least one new note diagnostic, which is a regression from the base state.
generate ❗build ⏭️lint ⏭️test ⏭️

New diagnostics (4 note)
💡 Schema/DeprecatedWithoutMessage: Confirm schema is deprecated and add missing deprecation details if needed.
💡 Schema/DeprecatedWithoutMessage: Confirm schema is deprecated and add missing deprecation details if needed.
💡 Schema/DeprecatedWithoutMessage: Confirm schema is deprecated and add missing deprecation details if needed.
💡 Schema/DeprecatedWithoutMessage: Confirm schema is deprecated and add missing deprecation details if needed.
grid-kotlin studio · code · diff

Your SDK build had at least one new note diagnostic, which is a regression from the base state.
generate ❗build ⏭️lint ⏭️test ⏭️

New diagnostics (4 note)
💡 Schema/DeprecatedWithoutMessage: Confirm schema is deprecated and add missing deprecation details if needed.
💡 Schema/DeprecatedWithoutMessage: Confirm schema is deprecated and add missing deprecation details if needed.
💡 Schema/DeprecatedWithoutMessage: Confirm schema is deprecated and add missing deprecation details if needed.
💡 Schema/DeprecatedWithoutMessage: Confirm schema is deprecated and add missing deprecation details if needed.
grid-go studio · code · diff

Your SDK build had at least one new note diagnostic, which is a regression from the base state.
generate ❗build ⏭️lint ⏭️test ⏭️

go get github.com/stainless-sdks/grid-go@ea4e55108375dedeb1c26651b303caad4a2c0f9b
New diagnostics (4 note)
💡 Schema/DeprecatedWithoutMessage: Confirm schema is deprecated and add missing deprecation details if needed.
💡 Schema/DeprecatedWithoutMessage: Confirm schema is deprecated and add missing deprecation details if needed.
💡 Schema/DeprecatedWithoutMessage: Confirm schema is deprecated and add missing deprecation details if needed.
💡 Schema/DeprecatedWithoutMessage: Confirm schema is deprecated and add missing deprecation details if needed.
grid-python studio · code · diff

Your SDK build had at least one "error" diagnostic, but this did not represent a regression.
generate ❗build ⏭️lint ⏭️test ⏭️

grid-php studio · code · diff

Your SDK build had at least one new note diagnostic, which is a regression from the base state.
generate ❗lint ⏭️test ⏭️

New diagnostics (4 note)
💡 Schema/DeprecatedWithoutMessage: Confirm schema is deprecated and add missing deprecation details if needed.
💡 Schema/DeprecatedWithoutMessage: Confirm schema is deprecated and add missing deprecation details if needed.
💡 Schema/DeprecatedWithoutMessage: Confirm schema is deprecated and add missing deprecation details if needed.
💡 Schema/DeprecatedWithoutMessage: Confirm schema is deprecated and add missing deprecation details if needed.
grid-cli studio · code · diff

Your SDK build had at least one "error" diagnostic, but this did not represent a regression.
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-09-14 18:21:41 UTC

@greptile-apps

greptile-apps Bot commented Sep 14, 2026

Copy link
Copy Markdown
Contributor

RetriggerConfidence Score: 4/5

The PR should not merge until the new contract and examples target the release-candidate surface rather than the unchanged dated API.

Findings

  1. P2 Consent list remains optional
  2. P2 Empty consent arrays allowed
Fix with agent prompt
### Issue 1
openapi/components/schemas/customers/Customer.yaml:26-34
`agreementConsents` is described as an empty list before the first acceptance, but it is absent from `Customer.required`. Responses may therefore omit the field while still satisfying the schema, and generated clients will model it as optional. Consumers cannot rely on consistently receiving `[]` as documented.

Note: If this suggestion doesn't match your team's coding style, reply to this and let me know. I'll remember it for next time!

### Issue 2
openapi/components/schemas/customers/CustomerCreateRequest.yaml:62-72
The request documentation says `agreementConsents` contains “one or more” entries, but the create and update arrays specify only `maxItems`. Schema-generated validation can therefore accept `[]`, which records no consent and leaves the customer unprovisioned, causing later customer-scoped transactions to fail. Both request schemas should require at least one item.

---

For each issue above, determine whether it is valid and should be fixed. If so, fix it directly.

Summary

  • Adds AgreementType, consent, document, and catalog response schemas.
  • Adds GET /customers/agreements while deprecating the existing End User Terms operation.
  • Adds agreementConsents to customer request and response models.
  • Retains deprecated legacy fields for compatibility.
  • Updates customer webhook examples, Stainless configuration, and Mintlify guidance.
  • The versioned server and documentation currently expose the release-candidate contract on the unchanged dated API URL.

Diagram

sequenceDiagram
  participant Client
  participant Catalog as GET /customers/agreements
  participant CustomerAPI as Customer API
  Client->>Catalog: Request supported agreement documents
  Catalog-->>Client: "data[{type, version, url}]"
  Client->>Client: Present applicable documents
  Client->>CustomerAPI: "agreementConsents[{type, termsVersion, evidence}]"
  CustomerAPI-->>Client: Customer with agreementConsents[]
Loading

Reviews (1) · Last reviewed commit: "feat(customers): accept typed agreement ..."

Comment on lines +26 to +34
agreementConsents:
type: array
readOnly: true
description: >-
The customer's recorded acceptance of the End User Terms. Omitted until
The customer's recorded agreement acceptances, one entry per accepted
type holding that type's most recent acceptance. Empty until an
acceptance has been recorded.
items:
$ref: ./AgreementConsent.yaml

Copy link
Copy Markdown
Contributor

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

P2 Consent list remains optional

agreementConsents is described as an empty list before the first acceptance, but it is absent from Customer.required. Responses may therefore omit the field while still satisfying the schema, and generated clients will model it as optional. Consumers cannot rely on consistently receiving [] as documented.

Prompt To Fix With AI
This is a comment left during a code review.
Path: openapi/components/schemas/customers/Customer.yaml
Line: 26-34

Comment:
**Consent list remains optional**

`agreementConsents` is described as an empty list before the first acceptance, but it is absent from `Customer.required`. Responses may therefore omit the field while still satisfying the schema, and generated clients will model it as optional. Consumers cannot rely on consistently receiving `[]` as documented.

---

For each issue above, determine whether it is valid and should be fixed. If so, fix it directly.

Note: If this suggestion doesn't match your team's coding style, reply to this and let me know. I'll remember it for next time!

Comment on lines +62 to +72
agreementConsents:
type: array
maxItems: 7
description: >-
Evidence that the customer accepted one or more Grid agreements, at most
one entry per type. Unregulated platforms must record acceptance of the
Lightspark End User Terms before initiating customer-scoped transactions;
those transactions fail until that consent is recorded. Consents can be
supplied during customer creation or in a later customer update.
items:
$ref: ./AgreementConsentRequest.yaml

Copy link
Copy Markdown
Contributor

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

P2 Empty consent arrays allowed

The request documentation says agreementConsents contains “one or more” entries, but the create and update arrays specify only maxItems. Schema-generated validation can therefore accept [], which records no consent and leaves the customer unprovisioned, causing later customer-scoped transactions to fail. Both request schemas should require at least one item.

Prompt To Fix With AI
This is a comment left during a code review.
Path: openapi/components/schemas/customers/CustomerCreateRequest.yaml
Line: 62-72

Comment:
**Empty consent arrays allowed**

The request documentation says `agreementConsents` contains “one or more” entries, but the create and update arrays specify only `maxItems`. Schema-generated validation can therefore accept `[]`, which records no consent and leaves the customer unprovisioned, causing later customer-scoped transactions to fail. Both request schemas should require at least one item.

---

For each issue above, determine whether it is valid and should be fixed. If so, fix it directly.

@mintlify

mintlify Bot commented Sep 14, 2026

Copy link
Copy Markdown
Contributor

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

Project Status Preview Updated
Grid 🟢 Ready View Preview Sep 14, 2026, 6:13 PM

@ls-bolt

ls-bolt Bot commented Sep 14, 2026

Copy link
Copy Markdown
Contributor

Fixed the wire break, and addressed the review findings.

The break is gone. GET /customers/end-user-terms keeps its original { version, url } body and is now marked deprecated: true; the seven-document catalog moved to a new GET /customers/agreements returning { data: [{ type, version, url }] }. Both routes serve on every API version, so a client reading .version / .url off the root object is unaffected. Previously the old path had been repurposed to return the list — that was the only runtime-level break, and it no longer exists.

Mutation-tested the guarantee: making the deprecated route return the list shape again fails its tests on both versions.

Finding 1 (consent list optional) — valid, fixed. The backend emits agreementConsents unconditionally on both customer converter paths, so the schema was understating the contract. Customer.required now includes it. Worth noting the ripple: making it required surfaced two webhook examples (kycApprovedWebhook, kybRejectedWebhook) that omitted the field and were failing oas3-valid-media-example as errors — both now include it, and lint is back to its 2-warning baseline.

Finding 2 (empty arrays allowed) — declining, this is deliberate. An empty agreementConsents is a valid no-op that records nothing and preserves evidence already on file; a minItems: 1 would reject it. Two things the finding assumes aren't the case here:

  • The field is optional at the top level, so a caller who wants to record nothing simply omits it — minItems wouldn't close any gap, it would just turn one no-op spelling into a 400.
  • "Leaves the customer unprovisioned" isn't a new hazard: provisioning is gated on LIGHTSPARK_END_USER_TERMS specifically, and a customer created without consent has always been able to record it later via PATCH. That path is covered by test_an_empty_consent_list_preserves_recorded_evidence, which asserts an empty list leaves recorded evidence intact.

I did soften the create-request wording, which did say "one or more" — it now describes the list without implying a minimum.

Verified: make build + make lint clean at baseline; on the backend, 604 tests pass with ruff and ty clean. Making the field required also required agreement_consents=[] on 19 Customer constructions in test fixtures (no production call sites).

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.

2 participants