Skip to content

feat(customers): contact (email/phone) verification surface#557

Open
jklein24 wants to merge 6 commits into
mainfrom
feat/striga-contact-verification
Open

feat(customers): contact (email/phone) verification surface#557
jklein24 wants to merge 6 commits into
mainfrom
feat/striga-contact-verification

Conversation

@jklein24
Copy link
Copy Markdown
Contributor

@jklein24 jklein24 commented Jun 7, 2026

Summary

Adds the contact verification surface to the Grid customer API: email/phone OTP verification, required only for customers whose payment provider mandates it (e.g. EU customers via Striga). Non-EU customers are unaffected — contactVerification is omitted from their responses and the endpoints return 409 for providers with no such requirement.

This is the API-spec half of AT-5447; the sparkcore implementation is in webdev PRs #28352 (interface + client) and #28353 (impl + handlers).

Spec changes

  • New schemas (components/schemas/customers/): ContactVerificationStatus (PENDING/VERIFIED), ContactVerification ({ email, phone }), ContactVerificationConfirmRequest ({ code }).

  • Customer: optional, readOnly contactVerification object — present only when the provider requires verification.

  • New paths:

    • POST /customers/{customerId}/verify-email + /verify-email/confirm
    • POST /customers/{customerId}/verify-phone + /verify-phone/confirm

    Send endpoints (re)send the OTP; /confirm submits { code }. They return 409 when the customer's provider doesn't require contact verification.

Docs

  • Added a region-scoped step to the unregulated hosted-KYC flow (snippets/kyc/kyc-unregulated.mdx) documenting the verify-email/verify-phone send+confirm endpoints and the contactVerification status object, noting that generating a KYC link before both channels are VERIFIED returns 409.

Notes

  • Bundled spec (openapi.yaml, mintlify/openapi.yaml) regenerated; redocly lint passes.
  • Once this merges and the SDK regenerates, the webdev side drops its temporary types (raw-dict request parsing → ContactVerificationConfirmRequest; response-dict injection → contactVerification on the Customer model).

🤖 Generated with Claude Code

jklein24 added 2 commits June 6, 2026 01:42
Adds a ContactVerification object on the Customer (present only when the
customer's payment provider requires email+phone verification, e.g. EU/Striga)
and four endpoints to drive it:

- POST /customers/{customerId}/verify-email          (send/resend)
- POST /customers/{customerId}/verify-email/confirm  (submit code)
- POST /customers/{customerId}/verify-phone          (send/resend)
- POST /customers/{customerId}/verify-phone/confirm  (submit code)

Non-EU customers are unaffected: contactVerification is omitted from their
responses and the endpoints return 409 for providers with no such requirement.

Starting point for AT-4657 (Striga EU). See design:
docs/plans/2026-06-06-striga-contact-verification-design.md (webdev).
…flow

For regions that require it (e.g. EU), the customer must verify email and
phone before a KYC link can be generated. Adds a region-scoped step to the
unregulated hosted-KYC flow describing the verify-email/verify-phone
send+confirm endpoints and the contactVerification status object.
@mintlify
Copy link
Copy Markdown
Contributor

mintlify Bot commented Jun 7, 2026

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

Project Status Preview Updated (UTC)
Grid 🟢 Ready View Preview Jun 7, 2026, 12:22 AM

@vercel
Copy link
Copy Markdown

vercel Bot commented Jun 7, 2026

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

1 Skipped Deployment
Project Deployment Actions Updated (UTC)
grid-flow-builder Ignored Ignored Preview Jun 7, 2026 6:13am

Request Review

@github-actions
Copy link
Copy Markdown
Contributor

github-actions Bot commented Jun 7, 2026

✱ Stainless preview builds for grid

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

cli

chore(internal): regenerate SDK with no functional changes

csharp

feat(api): add contact_verification field to customer models

go

feat(api): add contact verification field to customer and webhook responses

kotlin

feat(api): add contactVerification field to Customer/BusinessCustomer/IndividualCustomer

openapi

feat(api): add contact verification endpoints and types to customers

php

feat(api): add contactVerification field to Customer/BusinessCustomer/IndividualCustomer

python

feat(api): add contact_verification field and ContactVerification type to Customer

ruby

feat(api): add contact_verification field to Customer model

typescript

feat(api): add contactVerification field to customer responses

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

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)
💡 Endpoint/NotConfigured: Skipped endpoint because it's not in your Stainless config: `post /customers/{customerId}/verify-email`
💡 Endpoint/NotConfigured: Skipped endpoint because it's not in your Stainless config: `post /customers/{customerId}/verify-email/confirm`
💡 Endpoint/NotConfigured: Skipped endpoint because it's not in your Stainless config: `post /customers/{customerId}/verify-phone`
💡 Endpoint/NotConfigured: Skipped endpoint because it's not in your Stainless config: `post /customers/{customerId}/verify-phone/confirm`
grid-cli 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)
💡 Endpoint/NotConfigured: Skipped endpoint because it's not in your Stainless config: `post /customers/{customerId}/verify-email`
💡 Endpoint/NotConfigured: Skipped endpoint because it's not in your Stainless config: `post /customers/{customerId}/verify-email/confirm`
💡 Endpoint/NotConfigured: Skipped endpoint because it's not in your Stainless config: `post /customers/{customerId}/verify-phone`
💡 Endpoint/NotConfigured: Skipped endpoint because it's not in your Stainless config: `post /customers/{customerId}/verify-phone/confirm`
grid-csharp 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)
💡 Endpoint/NotConfigured: Skipped endpoint because it's not in your Stainless config: `post /customers/{customerId}/verify-email`
💡 Endpoint/NotConfigured: Skipped endpoint because it's not in your Stainless config: `post /customers/{customerId}/verify-email/confirm`
💡 Endpoint/NotConfigured: Skipped endpoint because it's not in your Stainless config: `post /customers/{customerId}/verify-phone`
💡 Endpoint/NotConfigured: Skipped endpoint because it's not in your Stainless config: `post /customers/{customerId}/verify-phone/confirm`
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)
💡 Endpoint/NotConfigured: Skipped endpoint because it's not in your Stainless config: `post /customers/{customerId}/verify-email`
💡 Endpoint/NotConfigured: Skipped endpoint because it's not in your Stainless config: `post /customers/{customerId}/verify-email/confirm`
💡 Endpoint/NotConfigured: Skipped endpoint because it's not in your Stainless config: `post /customers/{customerId}/verify-phone`
💡 Endpoint/NotConfigured: Skipped endpoint because it's not in your Stainless config: `post /customers/{customerId}/verify-phone/confirm`
grid-python 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 ❗

pip install https://pkg.stainless.com/s/grid-python/0c539054fcd2e4293df1b3fc1ba64eb29a07a8e7/grid-0.0.1-py3-none-any.whl
New diagnostics (4 note)
💡 Endpoint/NotConfigured: Skipped endpoint because it's not in your Stainless config: `post /customers/{customerId}/verify-email`
💡 Endpoint/NotConfigured: Skipped endpoint because it's not in your Stainless config: `post /customers/{customerId}/verify-email/confirm`
💡 Endpoint/NotConfigured: Skipped endpoint because it's not in your Stainless config: `post /customers/{customerId}/verify-phone`
💡 Endpoint/NotConfigured: Skipped endpoint because it's not in your Stainless config: `post /customers/{customerId}/verify-phone/confirm`
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)
💡 Endpoint/NotConfigured: Skipped endpoint because it's not in your Stainless config: `post /customers/{customerId}/verify-email`
💡 Endpoint/NotConfigured: Skipped endpoint because it's not in your Stainless config: `post /customers/{customerId}/verify-email/confirm`
💡 Endpoint/NotConfigured: Skipped endpoint because it's not in your Stainless config: `post /customers/{customerId}/verify-phone`
💡 Endpoint/NotConfigured: Skipped endpoint because it's not in your Stainless config: `post /customers/{customerId}/verify-phone/confirm`
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 ✅

npm install https://pkg.stainless.com/s/grid-typescript/78077750ec13f43e5ff76d2fb17c2b54fafe40de/dist.tar.gz
New diagnostics (4 note)
💡 Endpoint/NotConfigured: Skipped endpoint because it's not in your Stainless config: `post /customers/{customerId}/verify-email`
💡 Endpoint/NotConfigured: Skipped endpoint because it's not in your Stainless config: `post /customers/{customerId}/verify-email/confirm`
💡 Endpoint/NotConfigured: Skipped endpoint because it's not in your Stainless config: `post /customers/{customerId}/verify-phone`
💡 Endpoint/NotConfigured: Skipped endpoint because it's not in your Stainless config: `post /customers/{customerId}/verify-phone/confirm`
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@03593ef0d60dc1e083634422dce7d6dd514d2c77
New diagnostics (4 note)
💡 Endpoint/NotConfigured: Skipped endpoint because it's not in your Stainless config: `post /customers/{customerId}/verify-email`
💡 Endpoint/NotConfigured: Skipped endpoint because it's not in your Stainless config: `post /customers/{customerId}/verify-email/confirm`
💡 Endpoint/NotConfigured: Skipped endpoint because it's not in your Stainless config: `post /customers/{customerId}/verify-phone`
💡 Endpoint/NotConfigured: Skipped endpoint because it's not in your Stainless config: `post /customers/{customerId}/verify-phone/confirm`
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)
💡 Endpoint/NotConfigured: Skipped endpoint because it's not in your Stainless config: `post /customers/{customerId}/verify-email`
💡 Endpoint/NotConfigured: Skipped endpoint because it's not in your Stainless config: `post /customers/{customerId}/verify-email/confirm`
💡 Endpoint/NotConfigured: Skipped endpoint because it's not in your Stainless config: `post /customers/{customerId}/verify-phone`
💡 Endpoint/NotConfigured: Skipped endpoint because it's not in your Stainless config: `post /customers/{customerId}/verify-phone/confirm`

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-06-07 06:19:03 UTC

@greptile-apps
Copy link
Copy Markdown
Contributor

greptile-apps Bot commented Jun 7, 2026

Greptile Summary

This PR adds the contact verification surface to the Grid customer API, enabling EU customers (e.g. Striga) to verify their email and phone via OTP before proceeding to KYC. Non-EU customers are unaffected — the contactVerification field is omitted from their Customer objects and the new endpoints return 409 for providers with no verification requirement.

  • New schemas (ContactVerificationStatus, ContactVerification, ContactVerificationConfirmRequest) and four new endpoints (verify-email, verify-email/confirm, verify-phone, verify-phone/confirm) are added with consistent auth, error codes, and sandbox behavior.
  • Customer schema gains an optional readOnly contactVerification field; kyc-link gains a 409 response documenting the pre-KYC gate.
  • kyc-unregulated.mdx is updated with a new "Verify contact channels" step that correctly scopes the requirement to providers that mandate it.

Confidence Score: 5/5

Safe to merge — the change is additive spec-only work that does not affect existing customers or endpoints.

All new paths follow existing API patterns (auth, error codes, sandbox notes); the Customer schema extension is optional and readOnly; previously flagged issues have been addressed. The two remaining comments are documentation phrasing nits that don't affect runtime behavior.

The confirm-endpoint descriptions and the kyc-link 409 description have minor wording that assumes both email and phone channels are always required, but this won't cause a functional regression.

Important Files Changed

Filename Overview
openapi/components/schemas/customers/ContactVerification.yaml New schema for email/phone verification state; OAS 3.1 $ref+sibling description is valid; schema correctly models optional per-channel presence.
openapi/components/schemas/customers/ContactVerificationStatus.yaml New PENDING/VERIFIED enum for a single verification channel; straightforward and correct.
openapi/components/schemas/customers/ContactVerificationConfirmRequest.yaml New request body with required code string; clean and minimal.
openapi/components/schemas/customers/Customer.yaml Adds optional readOnly contactVerification field via allOf pattern; correctly matches the established pattern for object-ref properties.
openapi/paths/customers/customers_{customerId}_verify-email.yaml New send-email endpoint; 204/401/404/409/500 responses all present; sandbox note included.
openapi/paths/customers/customers_{customerId}_verify-email_confirm.yaml New confirm-email endpoint; returns full Customer on 200; description says 'Both email and phone must be VERIFIED' which is inaccurate for single-channel providers per the ContactVerification schema.
openapi/paths/customers/customers_{customerId}_verify-phone.yaml New send-phone endpoint; mirrors verify-email structure; responses complete.
openapi/paths/customers/customers_{customerId}_verify-phone_confirm.yaml New confirm-phone endpoint; same 'Both email and phone must be VERIFIED' inaccuracy as the email confirm endpoint.
openapi/paths/customers/customers_{customerId}_kyc-link.yaml Adds 409 response for unverified contact channels; 409 description hardcodes 'email and phone' rather than 'all required channels,' inconsistent with the ContactVerification schema's single-channel support.
mintlify/snippets/kyc/kyc-unregulated.mdx Adds contact-verification step to the hosted and embedded KYC flows; correctly phrases the requirement as 'every present channel' rather than hardcoding both channels.

Sequence Diagram

sequenceDiagram
    participant I as Integrator
    participant G as Grid API

    I->>G: POST /customers (create customer)
    G-->>I: "Customer { contactVerification: { email: PENDING, phone: PENDING } }"

    note over I,G: EU / Striga provider only

    I->>G: "POST /customers/{id}/verify-email"
    G-->>I: 204 (code sent)
    I->>G: "POST /customers/{id}/verify-email/confirm { code }"
    G-->>I: "200 Customer { email: VERIFIED, phone: PENDING }"

    I->>G: "POST /customers/{id}/verify-phone"
    G-->>I: 204 (code sent)
    I->>G: "POST /customers/{id}/verify-phone/confirm { code }"
    G-->>I: "200 Customer { email: VERIFIED, phone: VERIFIED }"

    I->>G: "POST /customers/{id}/kyc-link"
    G-->>I: "200 { kycUrl, expiresAt }"
Loading
Prompt To Fix All With AI
Fix the following 2 code review issues. Work through them one at a time, proposing concise fixes.

---

### Issue 1 of 2
openapi/paths/customers/customers_{customerId}_verify-email_confirm.yaml:14
**"Both email and phone" hardcodes a two-channel assumption**

The `ContactVerification` schema explicitly states that "A provider may require both email and phone, just one of them, or — when the object is absent — neither." If a provider requires only email, the `phone` property won't appear in the object and the customer can proceed to KYC after verifying email alone. The phrase "Both email and phone must be `VERIFIED`" creates a false invariant that contradicts the schema — a reader following this description would incorrectly block KYC for single-channel providers.

The same wording appears in the `verify-phone/confirm` description (line 14). The `kyc-unregulated.mdx` snippet gets this right: "Generating the KYC link before every present channel is `VERIFIED` returns `409`." The confirm-endpoint descriptions should match that phrasing — something like "Every channel present in `contactVerification` must reach `VERIFIED` before the customer can begin KYC."

### Issue 2 of 2
openapi/paths/customers/customers_{customerId}_kyc-link.yaml:62-67
**kyc-link 409 description assumes both channels are always required**

The description says "email and phone are not both `VERIFIED` yet," implying the provider always requires two channels. The `ContactVerification` schema explicitly supports single-channel providers where only `email` or only `phone` may be present. A provider that mandates email-only verification would still fire this 409 if just email isn't verified — but the description leaves an integrator thinking they need to verify phone too.

```suggestion
    '409':
      description: >-
        The customer's payment provider requires contact verification and not all
        required contact channels are `VERIFIED` yet. Complete contact
        verification (see `verify-email` and `verify-phone`) before generating a
        KYC link.
```

Reviews (6): Last reviewed commit: "Make contactVerification channels indepe..." | Re-trigger Greptile

Comment thread openapi/paths/customers/customers_{customerId}_verify-email.yaml
…on endpoints

All four new verify-email/verify-phone (send + confirm) paths now document the
'500' response that every comparable customer endpoint includes, and the confirm
endpoints' '409' descriptions now cover the "already verified" case to match the
send endpoints. Regenerated the bundled openapi.yaml and mintlify/openapi.yaml.

Co-Authored-By: Claude Opus 4.8 (1M context) <noreply@anthropic.com>
… Direct API flow

Adds the Contact Verification tag to the root tags array so it is declared like
every other operation group, and adds a note to the Direct API onboarding (KYC)
flow clarifying that EU customers must verify email/phone before submitting for
verification. Regenerated the bundled openapi.yaml and mintlify/openapi.yaml.

Co-Authored-By: Claude Opus 4.8 (1M context) <noreply@anthropic.com>
The kyc-link path was missing the 409 response that the docs and the contact
verification feature describe: generating a KYC link before both email and phone
are VERIFIED returns 409. Added it so the spec and docs stay in sync and SDK
generators produce a handler for the case. Regenerated the bundled artifacts.

Co-Authored-By: Claude Opus 4.8 (1M context) <noreply@anthropic.com>
A provider may require email verification, phone verification, both, or
neither. Drop the required:[email,phone] constraint so each channel is
reported independently — only the channels a provider actually requires are
present on the object (Striga requires both, but the API no longer assumes
that). Update docs accordingly (each present channel must be VERIFIED;
verify-email/verify-phone 409 for a channel the provider doesn't require).
Sign up for free to join this conversation on GitHub. Already have an account? Sign in to comment

Labels

None yet

Projects

None yet

Development

Successfully merging this pull request may close these issues.

1 participant