Skip to content

[grid] add customer phoneNumber API surface#626

Merged
DhruvPareek merged 3 commits into
mainfrom
dp/grid-customer-phone-number-api
Jun 29, 2026
Merged

[grid] add customer phoneNumber API surface#626
DhruvPareek merged 3 commits into
mainfrom
dp/grid-customer-phone-number-api

Conversation

@DhruvPareek

@DhruvPareek DhruvPareek commented Jun 26, 2026

Copy link
Copy Markdown
Contributor

Summary

  • Adds optional phoneNumber to customer create, update, and response schemas.
  • Documents phoneNumber as strict E.164 (+ plus country code and subscriber number).
  • Updates PATCH /customers/{customerId} docs to cover SMS OTP signed retry and to reject combined email + phoneNumber contact updates.
  • Bumps Grid API version to 2026-06-26.

Endpoint structure after this change

Create customer

POST /customers
{
  "customerType": "INDIVIDUAL",
  "platformCustomerId": "ind-9f84e0c2",
  "region": "US",
  "currencies": ["USD", "USDC"],
  "email": "jane.smith@example.com",
  "phoneNumber": "+14155551234",
  "fullName": "Jane Smith",
  "birthDate": "1990-01-15",
  "nationality": "US"
}

Response includes the same top-level contact fields:

{
  "id": "Customer:...",
  "customerType": "INDIVIDUAL",
  "email": "jane.smith@example.com",
  "phoneNumber": "+14155551234",
  "umaAddress": "$jane.smith@example.umadomain",
  "createdAt": "2026-06-26T12:00:00Z",
  "updatedAt": "2026-06-26T12:00:00Z"
}

Synchronous phone update

PATCH /customers/{customerId}
{
  "customerType": "INDIVIDUAL",
  "phoneNumber": "+14155559876"
}

If no tied SMS_OTP credentials need Turnkey sync, the endpoint returns 200 with the updated customer and phoneNumber.

Signed SMS OTP phone update

Initial call returns 202:

{
  "payloadToSign": "{\"organizationId\":\"org_...\",\"parameters\":{\"userId\":\"user_...\",\"userPhoneNumber\":\"+14155559876\"},\"timestampMs\":\"1775681700000\",\"type\":\"ACTIVITY_TYPE_UPDATE_USER_PHONE_NUMBER\"}",
  "requestId": "Request:...",
  "expiresAt": "2026-04-08T15:35:00Z"
}

The retry sends the same request body plus Grid-Wallet-Signature and Request-Id; success returns 200 with the updated customer.

Rejected combined contact update

PATCH /customers/{customerId}
{
  "customerType": "INDIVIDUAL",
  "email": "john.smith@example.com",
  "phoneNumber": "+14155559876"
}

This shape is documented as rejected; clients should send separate PATCH calls.

Validation

  • make build
  • make lint

@vercel

vercel Bot commented Jun 26, 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 Jun 29, 2026 6:43pm
grid-wallet-demo Ignored Ignored Preview Jun 29, 2026 6:43pm

Request Review

Copy link
Copy Markdown
Contributor Author

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

@github-actions

github-actions Bot commented Jun 26, 2026

Copy link
Copy Markdown
Contributor

✱ 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 phone_number field to customer models

go

feat(api): add phoneNumber field to customer requests/responses and webhooks

kotlin

feat(api): add phoneNumber field to customer models and create/update requests

openapi

feat(api): add phoneNumber field to customer create/update requests and responses

php

feat(api): add phoneNumber field to customer models and create/update requests

python

feat(api): add phone_number field to customers

ruby

feat(api): add phone_number field to customer create/update requests and model

typescript

feat(api): add phoneNumber field to customers create/update/response
⚠️ 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@1fb2f7473afee0099ee352a82c7f3340191965f4
grid-typescript studio · code

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

npm install https://pkg.stainless.com/s/grid-typescript/7c9470b6f7cc4cd65cea539a35d7ee18e19be5f6/dist.tar.gz
⚠️ 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/367cf27f1ff80674bb2444242946fe9ff25274fd/grid-0.0.1-py3-none-any.whl
⚠️ grid-csharp 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 ❗

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-06-29 20:05:40 UTC

@jklein24 jklein24 left a comment

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.

@DhruvPareek I actually need this same change for Striga. Is this ready for review?

Copy link
Copy Markdown
Contributor Author

this PR is ready, it's tied to this stack of pr's (all of the draft pr's are related to the phone number stuff), but that stack isn't ready yet

Copy link
Copy Markdown
Contributor Author

i can clean it all up tomorrow morning and send it over for review though if that works?

@DhruvPareek DhruvPareek marked this pull request as ready for review June 29, 2026 04:24

Copy link
Copy Markdown
Contributor Author

or if you're stuff isn't related to my webdev stack dw about it

@greptile-apps

greptile-apps Bot commented Jun 29, 2026

Copy link
Copy Markdown
Contributor

Greptile Summary

This PR adds phoneNumber (strict E.164) to the customer create, update, and response schemas, and extends the PATCH /customers/{customerId} documentation to cover the signed-retry flow for SMS OTP credential updates, paralleling the existing email flow.

  • Schema additions: phoneNumber with pattern ^\\+[1-9]\\d{1,14}$ added to Customer, CustomerCreateRequest, and CustomerUpdateRequest; consistent wording across all three.
  • PATCH documentation: Description, header docs, request/response examples, and all error codes (200, 202, 400, 401, 409, 424) updated to cover the phone-number signed-retry path and the rejection of combined email+phoneNumber requests.
  • Schema gap: CustomerUpdateRequest allows email and phoneNumber to coexist in the same body even though the API rejects that combination; a not: required: [email, phoneNumber] constraint is missing.

Confidence Score: 4/5

Safe to merge with the schema gap noted; the server will still reject invalid requests, but client tooling won't warn before they're sent.

The CustomerUpdateRequest schema defines email and phoneNumber as independent optional properties with no mutual-exclusivity constraint, directly contradicting the documented rejection of combined requests. SDK generators and OpenAPI validators will accept the combined body as schema-valid, so clients relying on schema validation will be surprised by a server-side 400. Fixing this requires adding a not constraint to the schema and propagating it to the two bundled YAML files.

openapi/components/schemas/customers/CustomerUpdateRequest.yaml and its copies in openapi.yaml and mintlify/openapi.yaml need the mutual-exclusivity constraint.

Important Files Changed

Filename Overview
openapi/components/schemas/customers/CustomerUpdateRequest.yaml Adds phoneNumber field with E.164 regex, but missing a not constraint to enforce the documented email+phoneNumber mutual-exclusivity rule.
openapi/components/schemas/customers/Customer.yaml Adds phoneNumber to response schema with correct E.164 pattern and consistent description wording.
openapi/components/schemas/customers/CustomerCreateRequest.yaml Adds phoneNumber to create request schema; no mutual-exclusivity restriction needed here (create legitimately accepts both email and phone).
openapi/paths/customers/customers_{customerId}.yaml Comprehensively updates PATCH description, header docs, examples, and all response codes for phone; 400 description omits the combined-contact rejection case.
openapi/paths/customers/customers.yaml Adds phoneNumber to all three POST request body examples; changes are additive and correct.
openapi.yaml Bundled file mirroring all component and path changes; inherits the same CustomerUpdateRequest schema gap.
mintlify/openapi.yaml Mintlify copy of the bundled spec; identical changes and the same inherited schema gap.

Sequence Diagram

%%{init: {'theme': 'neutral'}}%%
sequenceDiagram
    participant Client
    participant Grid API

    Note over Client,Grid API: Phone update – no SMS_OTP credentials tied
    Client->>Grid API: PATCH /customers/{id} {phoneNumber}
    Grid API-->>Client: 200 Updated customer (phoneNumber set)

    Note over Client,Grid API: Phone update – SMS_OTP credentials tied (signed-retry)
    Client->>Grid API: PATCH /customers/{id} {phoneNumber} (no sig headers)
    Grid API-->>Client: 202 {payloadToSign, requestId, expiresAt}
    Client->>Client: Sign payloadToSign with session API keypair
    Client->>Grid API: PATCH /customers/{id} {phoneNumber} + Grid-Wallet-Signature + Request-Id
    Grid API-->>Client: 200 Updated customer (phoneNumber + SMS_OTP creds updated)

    Note over Client,Grid API: Rejected – combined contact update
    Client->>Grid API: PATCH /customers/{id} {email, phoneNumber}
    Grid API-->>Client: 400 Bad request
Loading
%%{init: {'theme': 'base', 'themeVariables': {"darkMode": true, "background": "#0d1117", "primaryColor": "#21262d", "primaryTextColor": "#e6edf3", "primaryBorderColor": "#8b949e", "lineColor": "#8b949e", "textColor": "#e6edf3", "edgeLabelBackground": "#161b22", "actorBkg": "#21262d", "actorBorder": "#8b949e", "actorTextColor": "#e6edf3", "actorLineColor": "#8b949e", "signalColor": "#8b949e", "signalTextColor": "#e6edf3", "noteBkgColor": "#373320", "noteBorderColor": "#d4a72c", "noteTextColor": "#f0e6c0", "labelBoxBkgColor": "#21262d", "labelBoxBorderColor": "#8b949e", "labelTextColor": "#e6edf3", "loopTextColor": "#e6edf3", "activationBkgColor": "#30363d", "activationBorderColor": "#8b949e"}}}%%
sequenceDiagram
    participant Client
    participant Grid API

    Note over Client,Grid API: Phone update – no SMS_OTP credentials tied
    Client->>Grid API: PATCH /customers/{id} {phoneNumber}
    Grid API-->>Client: 200 Updated customer (phoneNumber set)

    Note over Client,Grid API: Phone update – SMS_OTP credentials tied (signed-retry)
    Client->>Grid API: PATCH /customers/{id} {phoneNumber} (no sig headers)
    Grid API-->>Client: 202 {payloadToSign, requestId, expiresAt}
    Client->>Client: Sign payloadToSign with session API keypair
    Client->>Grid API: PATCH /customers/{id} {phoneNumber} + Grid-Wallet-Signature + Request-Id
    Grid API-->>Client: 200 Updated customer (phoneNumber + SMS_OTP creds updated)

    Note over Client,Grid API: Rejected – combined contact update
    Client->>Grid API: PATCH /customers/{id} {email, phoneNumber}
    Grid API-->>Client: 400 Bad request
Loading
Prompt To Fix All With AI
Fix the following 1 code review issue. Work through them one at a time, proposing concise fixes.

---

### Issue 1 of 1
openapi/components/schemas/customers/CustomerUpdateRequest.yaml:37-45
**Schema permits what the API rejects**

The prose in `customers_{customerId}.yaml` explicitly states "A request that includes both fields is rejected," but the `CustomerUpdateRequest` schema defines `email` and `phoneNumber` as independent optional properties with no mutual-exclusivity constraint. Any OpenAPI validator or SDK generator treats the schema as the source of truth, so a client that sends `{"customerType":"INDIVIDUAL","email":"a@b.com","phoneNumber":"+14155559876"}` will pass schema validation, then receive a `400` from the server with no schema-level explanation.

Adding a `not` constraint directly in `CustomerUpdateRequest.yaml` would encode the restriction machine-readably:

```yaml
not:
  required:
    - email
    - phoneNumber
```

Without this, the schema actively misrepresents the API's behavior to every consumer that relies on it for validation or code generation.

Reviews (4): Last reviewed commit: "Drop API version bump" | Re-trigger Greptile

Comment thread openapi/paths/customers/customers_{customerId}.yaml
Comment thread openapi/components/schemas/customers/Customer.yaml Outdated
@jklein24

Copy link
Copy Markdown
Contributor

or if you're stuff isn't related to my webdev stack dw about it

Tomorrow is cool. I just need the phone number in the API for customer creation. Totally unrelated to your webdev work though.

Why do you need an API version bump for this btw? Would be good to avoid that if possible

Copy link
Copy Markdown
Contributor Author

oh i prolly have the wrong understanding of how the api works but i thought if you regen grid-api in webdev you need to bump the api version too?

Copy link
Copy Markdown
Contributor Author

lemme also fix the greptile comments on this pr, but then we should be cool to merge it rn

Copy link
Copy Markdown
Contributor Author

oh were you talking about version: "2026-06-26" in this PR in openapi/openapi.yaml? if so i reverted it back. I thought you were talking about the version bump in the webdev PR that i linked to above

DhruvPareek and others added 3 commits June 29, 2026 11:41
- Document phone-number uniqueness (SMS_OTP) in the PATCH 409 response
- Use "strict E.164 format" wording in Customer response schema for consistency

Co-Authored-By: Claude Opus 4.8 (1M context) <noreply@anthropic.com>
This change is purely additive (optional phoneNumber field), so it does
not warrant an info.version bump. Revert info.version to 2026-06-25 to
match main; the PR now carries no version change.

Co-Authored-By: Claude Opus 4.8 (1M context) <noreply@anthropic.com>
@DhruvPareek DhruvPareek force-pushed the dp/grid-customer-phone-number-api branch from e0b4849 to 0d9ac4e Compare June 29, 2026 18:43
Comment on lines +37 to +45
phoneNumber:
type: string
pattern: '^\+[1-9]\d{1,14}$'
description: >-
Phone number for the customer in strict E.164 format. For customers with
tied Embedded Wallet internal accounts, changing this value also updates
every tied `SMS_OTP` credential across all tied Embedded Wallets. Send
phone number and email updates as separate PATCH calls.
example: '+14155551234'

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.

P1 Schema permits what the API rejects

The prose in customers_{customerId}.yaml explicitly states "A request that includes both fields is rejected," but the CustomerUpdateRequest schema defines email and phoneNumber as independent optional properties with no mutual-exclusivity constraint. Any OpenAPI validator or SDK generator treats the schema as the source of truth, so a client that sends {"customerType":"INDIVIDUAL","email":"a@b.com","phoneNumber":"+14155559876"} will pass schema validation, then receive a 400 from the server with no schema-level explanation.

Adding a not constraint directly in CustomerUpdateRequest.yaml would encode the restriction machine-readably:

not:
  required:
    - email
    - phoneNumber

Without this, the schema actively misrepresents the API's behavior to every consumer that relies on it for validation or code generation.

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

Comment:
**Schema permits what the API rejects**

The prose in `customers_{customerId}.yaml` explicitly states "A request that includes both fields is rejected," but the `CustomerUpdateRequest` schema defines `email` and `phoneNumber` as independent optional properties with no mutual-exclusivity constraint. Any OpenAPI validator or SDK generator treats the schema as the source of truth, so a client that sends `{"customerType":"INDIVIDUAL","email":"a@b.com","phoneNumber":"+14155559876"}` will pass schema validation, then receive a `400` from the server with no schema-level explanation.

Adding a `not` constraint directly in `CustomerUpdateRequest.yaml` would encode the restriction machine-readably:

```yaml
not:
  required:
    - email
    - phoneNumber
```

Without this, the schema actively misrepresents the API's behavior to every consumer that relies on it for validation or code generation.

How can I resolve this? If you propose a fix, please make it concise.

@jklein24

Copy link
Copy Markdown
Contributor

oh were you talking about version: "2026-06-26" in this PR in openapi/openapi.yaml? if so i reverted it back. I thought you were talking about the version bump in the webdev PR that i linked to above

Yeah I was just talking about the schema version in this PR. I don't think we need to bump it.

@DhruvPareek DhruvPareek merged commit c9d7766 into main Jun 29, 2026
11 checks passed
@DhruvPareek DhruvPareek deleted the dp/grid-customer-phone-number-api branch June 29, 2026 19:57
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.

2 participants