Skip to content
440 changes: 365 additions & 75 deletions mintlify/openapi.yaml

Large diffs are not rendered by default.

440 changes: 365 additions & 75 deletions openapi.yaml

Large diffs are not rendered by default.

13 changes: 13 additions & 0 deletions openapi/components/schemas/customers/InternalAccount.yaml
Original file line number Diff line number Diff line change
Expand Up @@ -32,6 +32,19 @@ properties:
description: Payment instructions for funding the account
items:
$ref: ../common/PaymentInstructions.yaml
label:
type: string
maxLength: 255
description: >-
The platform-supplied label recorded when the account was created. Null
for accounts that carry none.
example: invoice-4417
sweepRule:
allOf:
- $ref: SweepRule.yaml
description: >-
The routing rule attached to this account. Null for accounts that carry
no rule, which is every account other than a `RULE_BASED` one.
privateEnabled:
type: boolean
description: >-
Expand Down
Original file line number Diff line number Diff line change
@@ -0,0 +1,46 @@
title: Internal Account Create Request
type: object
description: >-
Request body for `POST /internal-accounts`. Only `RULE_BASED`
accounts can be created directly, and they always carry a `sweepRule`.
additionalProperties: false
required:
- type
- currency
Comment thread
ls-bolt[bot] marked this conversation as resolved.
Comment thread
ls-bolt[bot] marked this conversation as resolved.
- sweepRule

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.

can we make this optional so the endpoint can stay generic? i know we can't actually make other accounts rn, but helps us in the future

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.

Done in f412e9ddcustomerId is out of required, and the description now says omitting it creates a platform-owned account, with a note that doing so is rejected today.

Agreed on the reasoning, and it is worth stating why it matters beyond convenience: making it required now and relaxing it later is a breaking change for anyone who generated a client against the strict schema. Optional-but-currently-rejected is the shape the endpoint keeps, so the contract does not move when platform-owned accounts ship.

Fair warning that this reverses a call I made earlier on this PR — Greptile flagged the same field and I argued the opposite, on the grounds that the handler refuses an absent customerId so an optional field advertises a request that always 400s. That is true today but it is a runtime restriction, not a shape restriction, and the description is the right place for it. Your framing is better.


🤖 charged-neutron-40(#40) | Feedback

properties:
customerId:
type: string
description: >-
The customer the account is created for. Omit it to create a
platform-owned account. The account holder must already have a verified
account in the same currency, which is where funds land when a sweep
cannot be completed. Platform-owned rule-based accounts are not available
yet, so omitting this is currently rejected.
example: Customer:019542f5-b3e7-1d02-0000-000000000001
type:
allOf:
- $ref: ./InternalAccountType.yaml
description: >-
Must be `RULE_BASED`. `INTERNAL_FIAT`, `INTERNAL_CRYPTO`, and
`EMBEDDED_WALLET` accounts are provisioned automatically when a customer
is created or approved, so they cannot be created through this endpoint.
example: RULE_BASED
currency:
type: string
description: >-
Currency code the account is denominated in (ISO 4217). Rule-based
accounts are currently available in `USD` only.
example: USD
label:
type: string
maxLength: 255
description: >-
Your own name for the account, echoed back on reads. Useful for
identifying which payer the account was issued for.
example: invoice-4417
sweepRule:
allOf:
- $ref: ./SweepRuleRequest.yaml
description: >-
The routing rule for the account. Required when `type` is `RULE_BASED`.
6 changes: 6 additions & 0 deletions openapi/components/schemas/customers/InternalAccountType.yaml
Original file line number Diff line number Diff line change
Expand Up @@ -4,6 +4,7 @@ enum:
- INTERNAL_FIAT
- INTERNAL_CRYPTO
- EMBEDDED_WALLET
- RULE_BASED
description: >-
Classification of an internal account.

Expand All @@ -17,3 +18,8 @@ description: >-
- `EMBEDDED_WALLET`: A self-custodial Embedded Wallet provisioned for the
customer. Outbound transfers require a session signature produced by the
customer's device — see the Embedded Wallets guide.

- `RULE_BASED`: An additional account number for an existing account holder,
with a routing rule attached, so incoming payments can be attributed to a
specific payer and swept automatically. Created with
`POST /internal-accounts`.
52 changes: 52 additions & 0 deletions openapi/components/schemas/customers/SweepRule.yaml
Original file line number Diff line number Diff line change
@@ -0,0 +1,52 @@
title: Sweep Rule
type: object
description: >-
The routing rule attached to a rule-based account. Returned on the account
rather than as a resource of its own, because the rule has no lifecycle apart
from the account.
required:
- destination
properties:
destination:
allOf:
- $ref: SweepRuleDestination.yaml
description: Where funds that settle into this account are swept.
minimumAmount:
allOf:
- $ref: ../common/CurrencyAmount.yaml
description: >-
The smallest balance the corridor to the destination can carry,
denominated in this account's own currency. A settled balance below it is
not swept. Zero means no floor applies, which is the case for a

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.

is this denoted in sending currency? would we ever have a minimum for same-currency internal dest because of deposit rail fees?

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.

Yes — the account's own currency, which is the sending side. gen_effective_sweep_bounds reads platform_currency off the source account and takes max(platform_currency.min_amount, grid_switch.min_amount); for a cross-currency destination it then raises that floor by the provider's own bound. So every value is in the source currency, never the destination's.

On the same-currency internal case: no, and it is unconditional rather than incidental. That branch returns minimum=0, maximum=None before any config is read, because the movement is a book transfer between subaccounts of one deposit account — no rail, no fee, no FX.

Deposit rail fees do not reach this either, and the reason is a bit subtle: the sweep reads the account's settled balance, which is already net of whatever the inbound rail cost. The floor exists to stop a balance the outbound corridor would reject, so it is priced on the send side only. An inbound fee shrinks the balance being compared, but never the threshold.

That is what makes the guarantee hold — a same-currency internal destination always has an exit, so a rule-based account pointed at one can never strand funds regardless of amount.


🤖 charged-neutron-40(#40) | Feedback

same-currency internal destination: that movement is a book transfer with
no rail, fee, or conversion to justify one. This is current configuration
rather than a moving estimate — it changes only when the platform's or
Grid's configuration changes, so there is nothing to re-poll.
maximumAmount:
allOf:
- $ref: ../common/CurrencyAmount.yaml
description: >-
The largest balance the corridor to the destination can carry. Null means
no ceiling applies. A settled balance above it is not swept.
purposeOfPayment:
allOf:

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.

curious why we need all of these allOf

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.

It is a workaround for an OpenAPI rule rather than anything semantic: a sibling key next to $ref is ignored, so writing

destination:
  $ref: SweepRuleDestination.yaml
  description: Where funds that settle into this account are swept.

silently drops the description — the reader sees only whatever SweepRuleDestination.yaml says about itself. Wrapping in allOf makes the description a property of the wrapper rather than a sibling of the $ref, so it survives.

It is the house pattern rather than something I introduced — AgentAction.yaml, the auth challenge/credential schemas, and others use the same shape, 262 occurrences across the bundle. Where a referenced schema's own description is sufficient I use a bare $ref instead.

Worth noting this is a 3.0-era constraint and we are on 3.1, where $ref siblings are permitted; tooling support is the reason to stay consistent with the rest of the spec rather than mix styles. If you would like the whole spec moved to bare $ref + sibling description, that is a reasonable cleanup but a separate one.


🤖 charged-neutron-40(#40) | Feedback

- $ref: ../quotes/PurposeOfPayment.yaml
description: The purpose of payment applied to each sweep.
example: SELF
description:
type: string
description: >-
Free-form description recorded on each sweep. Not delivered to the
recipient.
example: Rent sweep
remittanceInformation:
type: string
description: >-
Free-form information that travels with each sweep to the recipient.
example: Unit 4B March
platformFeeOverride:
allOf:
- $ref: ../quotes/PlatformFeeOverride.yaml
description: >-
Fee terms applied to every sweep this rule drives. Null when the
platform's configured fees apply.
18 changes: 18 additions & 0 deletions openapi/components/schemas/customers/SweepRuleDestination.yaml
Original file line number Diff line number Diff line change
@@ -0,0 +1,18 @@
title: Sweep Rule Destination
type: object
description: >-
Where a rule-based account's credits are swept.
required:
- accountId
properties:
accountId:
type: string
description: The account that receives the swept funds.
example: ExternalAccount:a12dcbd6-dced-4ec4-b756-3c3a9ea3d123
paymentRail:
allOf:
- $ref: ../common/PaymentRail.yaml
description: >-
The rail each sweep is sent over. Null when a rail is selected
automatically per sweep, in which case none is resolved ahead of time.
example: ACH
Original file line number Diff line number Diff line change
@@ -0,0 +1,25 @@
title: Sweep Rule Destination Request
type: object
description: >-
The account a rule-based account sweeps its incoming payments to.
additionalProperties: false
required:
- accountId
properties:
accountId:
type: string
description: >-
Reference to the account that receives the swept funds. May be an
external account or another internal account, but never a `RULE_BASED`
internal account — that account's own rule would sweep the funds on
again. The destination may be denominated in a different currency, in
which case the sweep is converted at the prevailing rate.
example: ExternalAccount:e85dcbd6-dced-4ec4-b756-3c3a9ea3d965
paymentRail:
description: >-
The payment rail to use when sweeping to an external account. Must be
one of the rails supported by the destination account. If omitted, a rail
is selected automatically for each sweep. Not accepted when the
destination is an internal account, which settles without a payment rail.
allOf:
- $ref: ../common/PaymentRail.yaml
40 changes: 40 additions & 0 deletions openapi/components/schemas/customers/SweepRuleRequest.yaml
Original file line number Diff line number Diff line change
@@ -0,0 +1,40 @@
title: Sweep Rule Request
type: object
description: >-
The routing rule attached to a rule-based account. Every payment that settles
into the account is swept to the rule's destination, carrying the payment
metadata configured here.
additionalProperties: false
required:
- destination
properties:
destination:
allOf:
- $ref: ./SweepRuleDestinationRequest.yaml
description: Where funds that settle into this account are swept.
purposeOfPayment:
allOf:
- $ref: ../quotes/PurposeOfPayment.yaml
description: >-
The purpose of payment applied to each sweep. Required by some
destination geographies.
example: SELF
description:
type: string
maxLength: 255
description: >-
Free-form description recorded on each sweep. Not delivered to the
recipient; use `remittanceInformation` for that.
example: Rent sweep
remittanceInformation:
type: string
maxLength: 1024
description: >-
Free-form information that travels with each sweep to the recipient.
The field this populates depends on the payment rail: for ACH it
populates the Addenda record, for FedNow and RTP it populates the
remittanceInformation field, and for wires it populates the OBI
(Originator to Beneficiary Information) / beneficiary information. Only
printable ASCII characters are accepted, because the underlying rails
carry nothing else.
example: Unit 4B March
16 changes: 16 additions & 0 deletions openapi/components/schemas/transactions/IncomingTransaction.yaml
Original file line number Diff line number Diff line change
Expand Up @@ -29,3 +29,19 @@ allOf:
failureReason:
$ref: ./IncomingTransactionFailureReason.yaml
description: If the transaction failed, this field provides the reason for failure.
sweepFailureReason:
allOf:
- $ref: ./SweepFailureReason.yaml
description: >-
Set when this payment settled into a rule-based account but was not
swept to the destination of the account's rule. The deposit itself
succeeded, so the transaction still completes; this says why the
destination was not paid. Omitted for any other account.
sweepCanonicalAccountId:
type: string
description: >-
The account holder's own account in the same currency the funds were
moved to when a sweep could not be completed — the customer's, or the
platform's for a platform-owned rule-based account. A rule-based
account never holds a balance. Present whenever
`sweepFailureReason` is.
17 changes: 17 additions & 0 deletions openapi/components/schemas/transactions/SweepFailureReason.yaml
Original file line number Diff line number Diff line change
@@ -0,0 +1,17 @@
type: string
enum:
- BELOW_MINIMUM
- ABOVE_MAXIMUM
- NO_ELIGIBLE_RAIL
- QUOTE_FAILED
- EXECUTION_FAILED
description: |
Why the payment could not be swept to the rule's destination.

| Reason | Description |
|--------|-------------|
| `BELOW_MINIMUM` | The amount is below the smallest amount the corridor to the destination can carry. |
| `ABOVE_MAXIMUM` | The amount is above the largest amount the corridor to the destination can carry. |
| `NO_ELIGIBLE_RAIL` | No payment rail available to the destination could carry the sweep. |
| `QUOTE_FAILED` | Grid could not price the sweep to the destination. |
| `EXECUTION_FAILED` | The sweep was priced but the resulting payment did not go through. |
2 changes: 2 additions & 0 deletions openapi/openapi.yaml

Some generated files are not rendered by default. Learn more about how customized files appear on GitHub.

5 changes: 3 additions & 2 deletions openapi/paths/customers/customers_internal_accounts.yaml
Original file line number Diff line number Diff line change
Expand Up @@ -28,8 +28,9 @@ get:
in: query
description: >-
Filter by internal account type. Use `EMBEDDED_WALLET` to find the
self-custodial wallet provisioned for a customer, or `INTERNAL_FIAT` /
`INTERNAL_CRYPTO` for the platform-managed holding accounts.
self-custodial wallet provisioned for a customer, `INTERNAL_FIAT` /
`INTERNAL_CRYPTO` for the platform-managed holding accounts, or
`RULE_BASED` for the additional account numbers issued for a customer.
required: false
schema:
$ref: ../../components/schemas/customers/InternalAccountType.yaml
Expand Down
Loading
Loading