feat(openapi): add rail + settlement-timing fields to OutgoingTransaction - #633
Conversation
…tion Add four optional, nullable fields to the OutgoingTransaction schema so the rail and settlement-timing data that sparkcore already emits on outgoing_payment.* webhooks becomes first-class in the typed API model: - paymentRail: nullable PaymentRail enum, the rail used to settle the txn - railSelectionMode: nullable AUTO/MANUAL enum (new RailSelectionMode schema) - expectedSettlementAt: nullable date-time of expected settlement - settlementTimelineSeconds: nullable integer seconds to settlement All four are additive and backward-compatible: not in `required`, and nullable for instant rails, AUTO mode before a rail is resolved, and non-direct-destination transactions. Bump the spec version to 2026-06-29, rebundle openapi.yaml + mintlify/openapi.yaml, and surface the new fields in the ramps outgoing-payment webhook payload example. Co-Authored-By: Claude Opus 4.8 <noreply@anthropic.com> Claude-Session: https://claude.ai/code/session_01FTtfvC6Qcsh3M62jVaCT1Y
|
The latest updates on your projects. Learn more about Vercel for GitHub. 2 Skipped Deployments
|
|
Preview deployment for your docs. Learn more about Mintlify Previews.
|
✱ Stainless preview builds for gridThis PR will update the cli csharp go kotlin openapi php python ruby typescript ✅ grid-ruby studio · code
|
Greptile SummaryThis PR promotes four settlement-timing and rail-selection fields (
Confidence Score: 4/5Safe to merge — purely additive schema extension with no breaking changes to existing fields or required properties. The change is clean and backward-compatible. Two field descriptions in OutgoingTransaction.yaml use phrasing that could mislead consumers: openapi/components/schemas/transactions/OutgoingTransaction.yaml — the two description suggestions on paymentRail and expectedSettlementAt are worth a quick look before publishing the new API version.
|
| Filename | Overview |
|---|---|
| openapi/components/schemas/transactions/OutgoingTransaction.yaml | Adds four optional nullable fields (paymentRail, railSelectionMode, expectedSettlementAt, settlementTimelineSeconds); structure and nullable idioms follow existing conventions, but two field descriptions have minor accuracy issues |
| openapi/components/schemas/common/RailSelectionMode.yaml | New two-value enum (AUTO/MANUAL) matching the structure of the adjacent PaymentRail.yaml; clean and consistent |
| openapi/openapi.yaml | Source root spec with version bump only (2026-06-25 → 2026-06-29); RailSelectionMode is auto-discovered by bundler via $ref chain |
| openapi.yaml | Generated bundle; diff matches source changes exactly — version bump plus RailSelectionMode schema and four new OutgoingTransaction fields |
| mintlify/openapi.yaml | Second generated bundle (Mintlify copy); identical changes to openapi.yaml, consistent |
| mintlify/ramps/platform-tools/webhooks.mdx | Adds the four new fields to the OUTGOING_PAYMENT webhook example; JSON syntax is valid and example values are plausible for an ACH transaction |
Sequence Diagram
%%{init: {'theme': 'neutral'}}%%
sequenceDiagram
participant Platform
participant GridAPI
participant SparkCore
participant Beneficiary
Platform->>GridAPI: "POST /quotes/{id}/execute"
GridAPI->>SparkCore: Initiate OutgoingTransaction
SparkCore-->>GridAPI: "Transaction created (paymentRail=null, railSelectionMode=AUTO)"
GridAPI-->>Platform: OUTGOING_PAYMENT.PENDING webhook
SparkCore->>SparkCore: Resolve payment rail
SparkCore-->>GridAPI: "Rail resolved (paymentRail=ACH, expectedSettlementAt set)"
GridAPI-->>Platform: OUTGOING_PAYMENT.PROCESSING webhook (paymentRail, railSelectionMode, expectedSettlementAt, settlementTimelineSeconds)
SparkCore->>Beneficiary: Initiate ACH transfer
SparkCore-->>GridAPI: Transaction settled on Grid side
GridAPI-->>Platform: "OUTGOING_PAYMENT.COMPLETED webhook (settledAt=now, expectedSettlementAt=T+1d)"
Beneficiary-->>Beneficiary: ACH clears at T+1d (expectedSettlementAt)
%%{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 Platform
participant GridAPI
participant SparkCore
participant Beneficiary
Platform->>GridAPI: "POST /quotes/{id}/execute"
GridAPI->>SparkCore: Initiate OutgoingTransaction
SparkCore-->>GridAPI: "Transaction created (paymentRail=null, railSelectionMode=AUTO)"
GridAPI-->>Platform: OUTGOING_PAYMENT.PENDING webhook
SparkCore->>SparkCore: Resolve payment rail
SparkCore-->>GridAPI: "Rail resolved (paymentRail=ACH, expectedSettlementAt set)"
GridAPI-->>Platform: OUTGOING_PAYMENT.PROCESSING webhook (paymentRail, railSelectionMode, expectedSettlementAt, settlementTimelineSeconds)
SparkCore->>Beneficiary: Initiate ACH transfer
SparkCore-->>GridAPI: Transaction settled on Grid side
GridAPI-->>Platform: "OUTGOING_PAYMENT.COMPLETED webhook (settledAt=now, expectedSettlementAt=T+1d)"
Beneficiary-->>Beneficiary: ACH clears at T+1d (expectedSettlementAt)
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/components/schemas/transactions/OutgoingTransaction.yaml:76-83
The `paymentRail` description says "Null until a rail is resolved," but the phrase "until" implies the field will eventually become non-null for every transaction. Per the PR description, it is *permanently* null for instant rails and non-direct-destination transactions. Consumers relying on polling for a non-null value would wait forever in those cases.
```suggestion
paymentRail:
anyOf:
- $ref: ../common/PaymentRail.yaml
- type: 'null'
description: >-
The payment rail used to settle this transaction (e.g. ACH, WIRE,
NEFT, FASTER_PAYMENTS). Uses the same values as the PaymentRail sent
on quote requests. Null when no external rail is used (e.g. instant
or intra-network transfers) or before a rail is resolved.
```
### Issue 2 of 2
openapi/components/schemas/transactions/OutgoingTransaction.yaml:92-99
The parenthetical "e.g. instant rails" is listed as an example of when settlement time is *not yet known*, but instant rails settle immediately — so the time is actually known (it's now). This makes the description self-contradictory. A developer reading it might conclude instant-rail transactions can eventually gain a non-null `expectedSettlementAt`, which is incorrect.
```suggestion
expectedSettlementAt:
type:
- string
- 'null'
format: date-time
description: >-
Expected settlement time at the beneficiary. Null for instant rails
(settlement is immediate) and before a rail with deferred settlement
is resolved.
```
Reviews (1): Last reviewed commit: "feat(openapi): add rail + settlement-tim..." | Re-trigger Greptile
| paymentRail: | ||
| anyOf: | ||
| - $ref: ../common/PaymentRail.yaml | ||
| - type: 'null' | ||
| description: >- | ||
| The payment rail used to settle this transaction (e.g. ACH, WIRE, | ||
| NEFT, FASTER_PAYMENTS). Uses the same values as the PaymentRail sent | ||
| on quote requests. Null until a rail is resolved. |
There was a problem hiding this comment.
The
paymentRail description says "Null until a rail is resolved," but the phrase "until" implies the field will eventually become non-null for every transaction. Per the PR description, it is permanently null for instant rails and non-direct-destination transactions. Consumers relying on polling for a non-null value would wait forever in those cases.
| paymentRail: | |
| anyOf: | |
| - $ref: ../common/PaymentRail.yaml | |
| - type: 'null' | |
| description: >- | |
| The payment rail used to settle this transaction (e.g. ACH, WIRE, | |
| NEFT, FASTER_PAYMENTS). Uses the same values as the PaymentRail sent | |
| on quote requests. Null until a rail is resolved. | |
| paymentRail: | |
| anyOf: | |
| - $ref: ../common/PaymentRail.yaml | |
| - type: 'null' | |
| description: >- | |
| The payment rail used to settle this transaction (e.g. ACH, WIRE, | |
| NEFT, FASTER_PAYMENTS). Uses the same values as the PaymentRail sent | |
| on quote requests. Null when no external rail is used (e.g. instant | |
| or intra-network transfers) or before a rail is resolved. |
Prompt To Fix With AI
This is a comment left during a code review.
Path: openapi/components/schemas/transactions/OutgoingTransaction.yaml
Line: 76-83
Comment:
The `paymentRail` description says "Null until a rail is resolved," but the phrase "until" implies the field will eventually become non-null for every transaction. Per the PR description, it is *permanently* null for instant rails and non-direct-destination transactions. Consumers relying on polling for a non-null value would wait forever in those cases.
```suggestion
paymentRail:
anyOf:
- $ref: ../common/PaymentRail.yaml
- type: 'null'
description: >-
The payment rail used to settle this transaction (e.g. ACH, WIRE,
NEFT, FASTER_PAYMENTS). Uses the same values as the PaymentRail sent
on quote requests. Null when no external rail is used (e.g. instant
or intra-network transfers) or before a rail is resolved.
```
How can I resolve this? If you propose a fix, please make it concise.| expectedSettlementAt: | ||
| type: | ||
| - string | ||
| - 'null' | ||
| format: date-time | ||
| description: >- | ||
| Expected settlement time at the beneficiary. Null when not yet known | ||
| (e.g. instant rails, or before a rail is resolved). |
There was a problem hiding this comment.
The parenthetical "e.g. instant rails" is listed as an example of when settlement time is not yet known, but instant rails settle immediately — so the time is actually known (it's now). This makes the description self-contradictory. A developer reading it might conclude instant-rail transactions can eventually gain a non-null
expectedSettlementAt, which is incorrect.
| expectedSettlementAt: | |
| type: | |
| - string | |
| - 'null' | |
| format: date-time | |
| description: >- | |
| Expected settlement time at the beneficiary. Null when not yet known | |
| (e.g. instant rails, or before a rail is resolved). | |
| expectedSettlementAt: | |
| type: | |
| - string | |
| - 'null' | |
| format: date-time | |
| description: >- | |
| Expected settlement time at the beneficiary. Null for instant rails | |
| (settlement is immediate) and before a rail with deferred settlement | |
| is resolved. |
Prompt To Fix With AI
This is a comment left during a code review.
Path: openapi/components/schemas/transactions/OutgoingTransaction.yaml
Line: 92-99
Comment:
The parenthetical "e.g. instant rails" is listed as an example of when settlement time is *not yet known*, but instant rails settle immediately — so the time is actually known (it's now). This makes the description self-contradictory. A developer reading it might conclude instant-rail transactions can eventually gain a non-null `expectedSettlementAt`, which is incorrect.
```suggestion
expectedSettlementAt:
type:
- string
- 'null'
format: date-time
description: >-
Expected settlement time at the beneficiary. Null for instant rails
(settlement is immediate) and before a rail with deferred settlement
is resolved.
```
How can I resolve this? If you propose a fix, please make it concise.Address review feedback on OutgoingTransaction field descriptions: - paymentRail: replace "Null until a rail is resolved" (which implied the field always eventually becomes non-null) with an accurate description of the permanent-null cases — instant/intra-network transfers and non-direct-destination transactions — as well as the pre-resolution case. - expectedSettlementAt: instant rails settle immediately (time is known, not "not yet known"); reword so it's null for instant rails and before a deferred-settlement rail is resolved. Wording only; no schema, type, or required-field changes. Rebundled. Co-Authored-By: Claude Opus 4.8 <noreply@anthropic.com> Claude-Session: https://claude.ai/code/session_01FTtfvC6Qcsh3M62jVaCT1Y
…#671) ## Summary Removes the csharp target from `.stainless/stainless.yml` so the `Build SDKs for pull request / preview` check stops failing on every spec-changing PR. **Why every spec PR is red**: Stainless's csharp build CI is broken upstream for ANY freshly generated code (content-independent, proven by bisect on #650, breakage window ~Jul 3). The preview check gates on a per-language head-vs-base regression, and with csharp in main's config the base build resolves to a cached pre-breakage csharp *success* — so every fresh head failure counts as a regression → red. **Why this fixes it**: with csharp out of the config on main, base builds have no cached csharp success to regress from — matching outcomes on base and head → "not a regression" → green. Proven end-to-end by probe #659: the exact previously-failing change class (doc-comment-only codegen delta) ran against a csharp-free base and `preview` **passed**. Full mechanics: [investigation writeup](#657 (comment)). **No shipping impact**: the csharp target is unpublished (`production_repo: null`, `publish.nuget: false`). Re-add the block when Stainless fixes their csharp build CI upstream. ## Sequencing (for #657 and other open spec PRs) 1. Merge this PR — **its own `preview` will be red** (its base is current main); merge through it (precedent: #633, #637). 2. Merge/rebase main into open spec PRs (e.g. #657) — their next `preview` run goes green. ## Verification - `make lint` passes; the file matches the exact removal previously validated on the #657 branch (commit 069ad9a) and by probe #659. --- 🤖 [thundering-graviton-3](https://zeus.dev.dev.sparkinfra.net/#/arc?id=thundering-graviton)[(#3)](https://zeus.dev.dev.sparkinfra.net/#/instance?id=thundering-graviton-3) | [Feedback](https://zeus.dev.dev.sparkinfra.net/feedback) Original PR: #663 Co-authored-by: Claude Fable 5 <noreply@anthropic.com>
…the Card resource (#657) ## Summary Follow-up #2 from [webdev#29877](lightsparkdev/webdev#29877) (Lithic card PAN-reveal POC), evolved per review: PAN reveal is now an **explicit, dedicated endpoint** rather than a side effect of reading the card. Server change: [webdev#29995](lightsparkdev/webdev#29995). - **New `POST /cards/{id}/reveal`** (`openapi/paths/cards/cards_{id}_reveal-session.yaml` + `CardRevealResponse` schema) — mints a signed, short-lived reveal URL and returns `{ panEmbedUrl, expiresAt }`. Every mint is audit-logged with the requesting actor; unattributable sessions get 403. - **`panEmbedUrl` is removed from the `Card` schema entirely** — get/list/create/update responses and webhook payloads never carry it. A live PAN-reveal credential only exists when a client explicitly asked for one (least-privilege + clean "reveal requested" audit semantics). - **Docs rewritten to the reveal-session flow**: issuing-cards gains the `POST …/reveal` example, quickstart/webhooks/terminology snippets updated, webhook examples no longer show the field, and `GET /cards/{id}` points at the reveal endpoint. - **Rode along from the folded #656**: a small `GtqAccountInfoBase` description clarification. `.stainless/stainless.yml` is byte-identical to main (the csharp experiments were fully reverted; the real csharp fix is #663). ## Verification - `make build` + `make lint` pass (0 errors; remaining warnings pre-exist on main). - Server implementation is TDD'd end-to-end on [webdev#29995](lightsparkdev/webdev#29995) (58 handler/proxy tests + dashboard dialog tests green). ## CI note — red `preview` check is expected until #663 merges The Stainless csharp target's upstream build CI fails on any codegen delta (content-independent); the fix is #663 (removes the unpublished csharp target on main). Until it merges, `preview` stays red on spec-changing PRs, this one included — merge through it (precedent #633, #637). Details: [investigation](#657 (comment)). --- 🤖 [thundering-graviton-5](https://zeus.dev.dev.sparkinfra.net/#/arc?id=thundering-graviton)[(#5)](https://zeus.dev.dev.sparkinfra.net/#/instance?id=thundering-graviton-5) | [Feedback](https://zeus.dev.dev.sparkinfra.net/feedback) --------- Co-authored-by: Claude Fable 5 <noreply@anthropic.com> Co-authored-by: mohamedwane <mohamedwane@users.noreply.github.com> Co-authored-by: benwgold <benwgold@users.noreply.github.com> Co-authored-by: pengying <pengying@users.noreply.github.com>
## Summary - Revert the Grid API OpenAPI `info.version` from `2026-06-29` back to `2025-10-13`, the value it held on the initial schema commit. Edited the source `openapi/openapi.yaml` and rebundled (`make build`), so the generated `openapi.yaml` and `mintlify/openapi.yaml` are updated in lockstep. - Add a Critical Rules bullet to `CLAUDE.md` establishing the versioning convention: **bump `info.version` only when a breaking change ships, at which point it should match a new `servers.url` path**. ## Why The two prior bumps ([#617](#617): `2025-10-13` → `2026-06-25`, and [#633](#633): `2026-06-25` → `2026-06-29`) were both purely additive, non-breaking schema changes. Under the new convention the version should not have moved for those, so it's reset to `2025-10-13` — which matches the current `servers.url` path `https://api.lightspark.com/grid/2025-10-13`. ## Test plan - `make lint` passes (0 errors). - Verified `info.version` reads `2025-10-13` in all three files (`openapi/openapi.yaml`, `openapi.yaml`, `mintlify/openapi.yaml`). Requested by @shreyav Slack thread: https://lightsparkgroup.slack.com/archives/D0AH5T8FMGE/p1783708971530589 --- 🤖 [custodial-circuit](https://zeus.dev.dev.sparkinfra.net/#/arc?id=custodial-circuit)[(#1)](https://zeus.dev.dev.sparkinfra.net/#/instance?id=custodial-circuit) | [Feedback](https://zeus.dev.dev.sparkinfra.net/feedback) Original PR: #678
Summary
Adds four optional, nullable fields to the
OutgoingTransactionschema so the rail and settlement-timing data that sparkcore already emits onoutgoing_payment.*webhooks (today as a raw-JSON post-process, webdev #29090) becomes first-class in the typed API model. This makes the fields show up in generated clients/SDKs and the public webhook docs. The change is purely additive and backward-compatible.Fields added
paymentRailPaymentRailenum refanyOf: [$ref, {type: 'null'}]railSelectionModeRailSelectionModeenum (AUTO/MANUAL)anyOf: [$ref, {type: 'null'}]expectedSettlementAtformat: date-timetype: [string, 'null']settlementTimelineSecondstype: [integer, 'null']All four are not added to
requiredand are nullable — they are null for instant rails, AUTO mode before a rail is resolved, and non-direct-destination transactions.Changes
openapi/components/schemas/transactions/OutgoingTransaction.yaml— the four new properties.openapi/components/schemas/common/RailSelectionMode.yaml— new named enum schema (placed alongsidePaymentRail.yaml, matching the repo's one-enum-per-file convention).openapi/openapi.yaml— version bump2026-06-25→2026-06-29(date-based release convention).openapi.yaml+mintlify/openapi.yaml— regenerated bundles (npm run build:openapi).mintlify/ramps/platform-tools/webhooks.mdx— surfaced the new fields in theOUTGOING_PAYMENTwebhook payload example. Other webhook docs render the model via$refand pick up the fields automatically.Conventions followed
anyOf+type: 'null'for refs (as inCard.yaml), andtype: [..., 'null']lists for scalars (as inAgentSpendingLimits.yaml).paymentRailvalue is the grid-apiPaymentRailname (e.g.FASTER_PAYMENTS), matching the enum exactly — no internal aliases.Verification
redoclyvalidation: ✅ valid (warnings are all pre-existing and unrelated).spectral lint --fail-severity=error: ✅ exit 0, no errors.requiredis unchanged (still onlytype,sentAmount,source) and the regeneratedOutgoingTransactionmodel includes all four fields as optional/nullable. The only non-additive line across the bundles is the version bump.Follow-up
Downstream webdev change is tracked as AT-5666: once a new grid-api package version is published, bump the pinned version, regenerate the client, and move sparkcore's webhook enrichment out of the raw-dict post-process into the typed model.
🤖 Generated with Claude Code
Generated by Claude Code