> ## Documentation Index
> Fetch the complete documentation index at: https://ramps-external-account-account-info-updated.mintlify.site/llms.txt
> Use this file to discover all available pages before exploring further.

# Changelog

> New features, improvements, and updates to the Grid API.

Follow along with the latest additions and improvements to the Grid API. For upcoming
changes and roadmap, [contact our team](https://www.lightspark.com/contact).

<Update label="September 2026">
  ## `EXTERNAL_ACCOUNT.ACCOUNT_INFO_UPDATED` webhook

  Grid now tells you when it corrects an external account's details. When the
  receiving bank returns an ACH notification of change (NOC), Grid applies the
  corrected routing number, account number or bank account type and sends
  `EXTERNAL_ACCOUNT.ACCOUNT_INFO_UPDATED`. The payload is the full external account
  with the corrected `accountInfo`. The account keeps its `id`, and later payments use
  the corrected details. Deliveries can arrive out of order, so fetch the account
  before you replace any copy of its details you store.

  ## Sandbox test suffixes work on Solana addresses

  A sandbox external account's last three characters pick the test outcome, and every
  failure suffix starts with `00`. Base58 has no `0`, so no valid Solana address could end
  in one. The transfer and quote destination suffixes `002`–`008` now have `11X` twins: an
  address ending in `112` behaves like `002`, and `117` like `007`. The `00X` forms still
  work. See
  [Sandbox testing](/api-reference/sandbox-testing).

  ## `customerId` is required when creating a customer external account

  `POST /customers/external-accounts` has always rejected a request that omits
  `customerId`, and the spec now says so. Its request body is its own schema,
  `CustomerExternalAccountCreateRequest`, in which `customerId` is required. Use
  `POST /platform/external-accounts` to create an external account owned by the
  platform itself.

  ## Same-day ACH is its own payment rail

  `ACH_SAME_DAY` joins `ACH` in `PaymentRail`. Pin it on a quote's destination to request
  same-business-day settlement for a USD payout.

  * Grid never selects `ACH_SAME_DAY` for you. It is only used when you name it, so
    existing integrations that omit `paymentRail` are unaffected.
  * The two rails are priced separately. A `RailFeeConfig` with `rail: ACH_SAME_DAY`
    prices same-day sends; your existing `ACH` config keeps pricing standard sends.
  * `ACH_SAME_DAY` is capped at $1,000,000 per entry by the NACHA same-day limit, rising
    to $10,000,000 on 2027-09-17. A payout above the limit is rejected rather than slowed;
    send it over `ACH` instead.
  * `ACH` will settle on the standard next-business-day schedule. Until a date we
    announce in advance, `ACH` continues to settle same-business-day on production
    platforms; it already settles next-business-day on sandbox platforms. To guarantee
    same-day settlement after that date, request `ACH_SAME_DAY`.

  See [Assessing fees](/payouts-and-b2b/payment-flow/assessing-fees).

  ## Add cards to Apple Pay, Google Pay, and Samsung Pay from your app

  New `POST /cards/{id}/tokenize` exchanges the values a wallet SDK produces
  for the encrypted provisioning payload that completes an in-app "Add to
  Wallet" tap. The payload is encrypted to the wallet provider's keys, so
  card data never crosses your servers. `Card.cardCapabilities` gains
  `supportsDigitalWalletTokenization`. Manual entry into a wallet continues
  to work for every card with no integration. See
  [Digital wallet tokenization](/cards/card-management/digital-wallet-tokenization).

  ## Card refunds, declines, and voids report distinct outcomes

  Three card-statement corrections to `CardTransaction`:

  * **Refunds are their own dated rows.** A merchant `RETURN` against a purchase now opens
    its own `CardTransaction` (`direction: CREDIT`, credited value in `settledAmount`)
    linked back to the purchase via the new `originalTransactionId` field, instead of
    folding into the purchase's `refundedAmount`. The purchase stays `SETTLED`; the refund
    fires its own `CARD_TRANSACTION.*` webhooks on the refund row. To reverse a sandbox
    return, pass the refund row's id to `POST /sandbox/cards/{id}/simulate/return_reversal`.
  * **`DECLINED` status with `cardDeclinedReason`.** An authorization declined before any
    money moved now reports `DECLINED` (and fires `CARD_TRANSACTION.DECLINED`) instead of
    `EXCEPTION`, which is reserved for settlement anomalies. The new `cardDeclinedReason`
    field explains why Grid declined the authorization: `CARD_NOT_ACTIVE` (frozen or closed
    card), `SPEND_LIMIT_EXCEEDED`, `INSUFFICIENT_FUNDS` (sandbox only—production approves
    underfunded auths and resolves them as `EXCEPTION`), `NO_ELIGIBLE_FUNDING_SOURCE` (e.g.,
    Embedded Wallet with no delegated key), `BLOCKED` (merchant category or transaction
    blocked), `UNSUPPORTED_NETWORK`, or `OTHER`. Declines must be excluded from cardholder
    statements.
  * **`VOIDED` status.** An approved authorization that is fully reversed or expires
    before any clearing posts now reports `VOIDED` (and fires
    `CARD_TRANSACTION.VOIDED`) instead of `SETTLED`.
  * Card rows now carry the same full currency metadata (`decimals`, `name`, `symbol`) as
    payment rows, and `merchant` gains `city` and `state` when the network reports them.
  * `GET /transactions` accepts a `cardId` filter (`Card:` LSID or UUID) for listing one
    card of a multi-card cardholder, and rejects `status` combined with `type=CARD` with a
    400—`status` applies to payment transactions only.

  ## Record consent for each agreement by type

  `agreementConsents` supersedes `endUserTermsConsent`: a list with one entry per agreement,
  so a customer's acceptance of each document is recorded and auditable separately.

  * Each entry carries a `type` alongside the existing `acceptedAt`, `ipAddress`,
    `termsVersion`, and `acceptanceMethod` fields. Send at most one entry per type.
  * `GET /customers/agreements` lists every supported agreement with its `type`, current
    `version`, and hosted `url`. Send an entry's `version` as `termsVersion` when recording
    that agreement's acceptance. `GET /customers/end-user-terms` is deprecated but unchanged
    — it still returns the single `{ version, url }` object, so existing callers keep working.
  * Customer responses return `agreementConsents`, holding the most recent acceptance per
    accepted type, and an empty list until the first acceptance.
  * On update, supplying consents records additional acceptances; acceptances already on
    file for other types are left untouched.

  `endUserTermsConsent` is **deprecated, not removed** — it still works on both the request
  and response, and is equivalent to a single `LIGHTSPARK_END_USER_TERMS` entry. Acceptance
  you have already recorded is migrated for you and reported under that type; you don't need
  to re-collect it. Sending both fields in one request is rejected.

  Accepting one agreement never implies acceptance of another, so the remaining six types
  must be collected from each customer.

  See [Disclosures](/payouts-and-b2b/onboarding/disclosures).

  ## Card returns report as `SETTLED` with a `refundedAmount`

  `REFUNDED` is removed from `CardTransactionStatus`, and `CARD_TRANSACTION.REFUNDED` from
  the webhook types. A card transaction's status tracks authorization outcomes and
  settlement—a return is reported through `direction` and `refundedAmount`, which together
  already carry it.

  * A settled purchase that the merchant later returns stays `SETTLED`, with the returned
    value in `refundedAmount`. A standalone merchant refund arrives as `direction: CREDIT`
    and `status: SETTLED`, with the credited value in `settledAmount`.
  * Returns no longer get a webhook type of their own—the transaction re-fires
    `CARD_TRANSACTION.SETTLED` with the updated amounts.
  * The sandbox return reversal simulator requires a parent with a posted return (non-zero
    `refundedAmount`) rather than a `REFUNDED` parent, which the previous docs described
    and the server never produced.

  Nothing you have received changes: the status was always derived on read, so existing
  transactions already report `SETTLED`. `TransactionStatus.REFUNDED` on cross-border
  payments is a separate enum and is unaffected.

  See [Reconciliation](/cards/transactions/reconciliation).

  ## Sandbox KYC and KYB follow the production flow

  Unregulated sandbox platforms now resolve a customer's verification from a submitted
  packet rather than at create, so your sandbox integration rehearses the same collect,
  submit, and resolve loop you run in production.

  <Warning>
    Behavior change. On an unregulated sandbox, a name suffix alone no longer sets the
    result at create. New customers stay `UNVERIFIED` until you call `POST /verifications`.
    Customers created before this change keep their status, and regulated sandbox platforms
    are unaffected.
  </Warning>

  * Upload documents with `POST /documents`, register beneficial owners for a business, then
    submit with `POST /verifications`. The suffix on `fullName`, `registrationNumber`, or a
    beneficial owner's `lastName` still decides the outcome—it applies once the submission
    is in rather than at create.
  * An incomplete submission returns `verificationStatus: RESOLVE_ERRORS` naming every
    missing field and document, so you can fix and resubmit. A business whose
    `registrationNumber` ends in `002` now reports those errors first rather than rejecting
    outright.
  * Renaming a customer no longer overwrites a settled result—`APPROVED` and `REJECTED` stay
    put. Create a new customer when you want to exercise a different outcome.
  * To keep the previous fast path, open **Configuration** in your sandbox dashboard and turn
    on **Skip verification paperwork**. A terminal suffix then resolves with no documents:
    individuals at create, businesses at their first submission. `001` and `003` still require
    a complete packet. Turn it off when you want to test the full flow again.

  Walk through the fix-and-resubmit loop in [sandbox testing](/api-reference/sandbox-testing).
</Update>

<Update label="August 2026">
  ## `bankAccountType` is required on USD external accounts

  Creating an external account with `accountType: USD_ACCOUNT` now requires
  `bankAccountType`. Send `CHECKING` or `SAVINGS` on `POST /customers/external-accounts`,
  `POST /platform/external-accounts`, and `POST /agents/me/external-accounts`.

  * Grid uses `bankAccountType` to set the ACH transaction code. Accounts created without
    it were sent as checking, and the receiving bank returned a notification of change to
    correct savings accounts.
  * External accounts created before this change are unaffected.

  See [External accounts](/ramps/accounts/external-accounts) for a full request body.

  ## `/transfer-in` and `/transfer-out` are deprecated

  Same-currency transfers now go through the quote endpoint, so one integration covers
  same-currency and cross-currency alike.

  * Use `POST /quotes` with `immediatelyExecute: true` to create and execute a
    same-currency transfer in a single request.
  * `amount` becomes `lockedCurrencyAmount` with `lockedCurrencySide: "SENDING"`; source
    and destination gain `sourceType: "ACCOUNT"` and `destinationType: "ACCOUNT"`.
    `remittanceInformation`, `purposeOfPayment`, and the destination `paymentRail` carry
    over unchanged.
  * The response is a `Quote` rather than a `Transaction`—read `transactionId` from it to
    track the resulting transaction.
  * `POST /transfer-in` and `POST /transfer-out` continue to work with unchanged request
    and response shapes.

  See [Send a payment](/payouts-and-b2b/payment-flow/send-payment#send-a-payment)
  for the updated flow.

  ## Assess your own fees on every transaction

  Charge your customers a platform fee and keep the margin—Grid collects it for you and
  reports it separately from network and FX costs.

  * Set a standing fee with `feeConfigs` on `PATCH /config`—a variable rate in basis
    points, a fixed amount, or both, applied to cross-currency transactions.
  * Override it on a single transaction with `platformFeeOverride` on `POST /quotes`, for
    promos, VIP pricing, or negotiated rates. No standing config required.
  * Both the standing config and the per-transaction override require a USD source
    currency today.
  * Reconcile with `platformFeesIncluded` on quotes and `platformFees` on outgoing
    transactions—your cut, broken out of the total.

  See [Fees](/platform-overview/core-concepts/quote-system#fees) for exactly how the
  variable fee is assessed on each locked side.

  ## Record end-user terms acceptance

  Unregulated platforms must record that each customer accepted Grid's end-user terms
  before their account opens.

  * `GET /customers/end-user-terms` returns the current terms URL and version.
  * Pass `endUserTermsConsent` on customer create or update with the timestamp, IP address,
    terms version, and acceptance method.
  * Quotes fail with `END_USER_TERMS_NOT_ACCEPTED` until acceptance is on file.

  See [Disclosures](/payouts-and-b2b/onboarding/disclosures).

  ## Run KYC from your own onboarding form

  Collect verification data in your own UI and submit it programmatically instead of
  handing customers to a hosted flow.

  * Enhanced due diligence—source of funds and wealth, purpose of account, expected
    transaction count and volume, income and net worth ranges, PEP status—is now a set of
    optional fields on the individual customer itself. Send them on `POST /customers` or
    add them later with `PATCH /customers/{customerId}`; there is no separate EDD resource.
  * `POST /verifications` returns `RESOLVE_ERRORS` with one entry per problem, each naming
    the exact field or accepted document types still needed, so you can fix and resubmit
    without guessing.
  * Request validation errors return every invalid field at once in
    `Error400.details.errors[]`, each with a machine-readable constraint you can render as
    field-level UX—no more resubmitting to discover the next error.
  * Individual customers and beneficial owners now share one identification vocabulary:
    `idType`, `identifier`, and `countryOfIssuance`.

  Rehearse the fix-and-resubmit loop in [sandbox testing](/api-reference/sandbox-testing).

  ## Groundwork for EU support: SCA and Travel Rule

  The API surface for the two controls EU regulation requires is now defined, so you can
  design against it ahead of EU corridors opening.

  * **Strong Customer Authentication**—`POST /sca/login/complete` returns
    `sessionExpiresAt`, so you can prompt a re-login before the 180-day session lapses
    rather than discovering it as a failed payment. Quote authorization documents
    `SCA_SESSION_REQUIRED` (409) and `ACCOUNT_LOCKED` (423), and `SCA_NOT_COMPLETED`
    explains a transaction that failed on an expired challenge.
  * **Travel Rule ownership verification**—a challenge and verify flow for proving a
    customer owns a self-custody wallet. `POST …/external-accounts/{id}/challenge` starts
    verification by wallet signature or hosted liveness check, and `…/verify` completes a
    signature synchronously. Accounts sit at `PENDING_OWNERSHIP_VERIFICATION`—still usable
    below regulatory thresholds—and move to `ACTIVE` on success or `UNVERIFIED` on a failed
    attempt. A single webhook, `EXTERNAL_ACCOUNT.STATUS_UPDATED`, covers the whole
    lifecycle.

  EU availability will be announced separately.

  ## Issue your own stablecoin

  Register a stablecoin with `/stablecoins`, then mint and burn directly against it with
  `/stablecoins/{stablecoinId}/mints` and `/stablecoins/{stablecoinId}/burns`.
  `/stablecoins/{stablecoinId}/operations` tracks each issuance through settlement.

  ## More ways to move money

  * **USDT on Ethereum and Plasma**, alongside Tron.
  * **Bitcoin L1** deposit addresses as a payment instruction on quotes.
  * USD accounts can describe a full wire beneficiary—bank name, checking or savings,
    intermediary bank and routing number, and bank-to-bank instructions.
  * Businesses outside the US can register as a publicly listed company, trust, private
    foundation, or charity.

  See [Currencies and rails](/platform-overview/core-concepts/currencies-and-rails).

  ## Track crypto settlement to and from external wallets

  A transaction that settles on-chain to or from an external crypto wallet now carries the
  settled transfer inline: `onChainTransaction` on the transaction's source or destination
  reports the transfer's `transactionHash` and `network` once the crypto transfer settles.

  <Warning>
    Deprecation. `reconciliationInstructions.transactionHash` is deprecated for wallet
    transfers—read `onChainTransaction` instead, which also tells you the network. The field
    keeps reporting the inter-VASP settlement leg of a UMA payment, which has no
    `onChainTransaction` equivalent, and will not be removed before that leg has a
    replacement.
  </Warning>

  ## Know why a payment failed

  Failure reasons now name outcomes you act on rather than internal processing steps.

  <Warning>
    Breaking change. `EXECUTION_FAILED_POST_DEBIT` and `SETTLEMENT_FAILED` are removed and
    collapse into `QUOTE_EXECUTION_FAILED`, whose description now covers the whole path to
    settlement and states that a debited amount is refunded automatically. `TIMEOUT` and
    `MANUAL_REFUND` are removed because that outcome already surfaces on the refund object.
  </Warning>

  * New payout failure reasons: `PAYOUT_RETURNED`, `LIMIT_EXCEEDED`,
    `ACCOUNT_CANNOT_RECEIVE`, `ACCOUNT_INVALID`, and `COMPLIANCE_REJECTED`.
  * `pendingReason` on any transaction tells you when it is held for compliance review or
    waiting on customer action.
  * New limit codes: `TRANSACTION_SIZE_LIMIT_EXCEEDED` (400) and
    `DAILY_VOLUME_LIMIT_EXCEEDED` (429).

  See [Transaction lifecycle](/platform-overview/core-concepts/transaction-lifecycle#failure-handling).
</Update>

<Update label="July 2026">
  ## Card issuance in sandbox

  You can now issue cards directly in the sandbox environment. Test the full card
  lifecycle—[cardholder setup](/cards/onboarding/cardholder-setup),
  [issuing cards](/cards/card-management/issuing-cards),
  [funding sources](/cards/card-management/funding-sources), and
  [freezing and closing](/cards/card-management/freezing-and-closing)—end to end
  before going live, without touching production.

  See [Cards sandbox testing](/cards/platform-tools/sandbox-testing) to get started.

  ## Cards: real-time webhooks and full event simulation

  React to card activity as it happens, and rehearse every event before going live.

  * `CARD_TRANSACTION.*` fires on every state transition—authorized, partially settled,
    settled, refunded, and exception—carrying the full card transaction.
  * All ten sandbox simulate endpoints, covering balance inquiries, credit and financial
    authorizations, authorization advices, returns, and return reversals.
  * Brand the tokenization verification codes your customers receive with
    `cardTokenization2faConfig` on platform config.

  See [Card webhooks](/cards/platform-tools/webhooks).

  ## Refund visibility on incoming payments

  `INCOMING_PAYMENT.REFUND_PENDING`, `INCOMING_PAYMENT.REFUND_COMPLETED`, and
  `INCOMING_PAYMENT.REFUND_FAILED` now fire the same way outgoing refunds already did, so
  a returned deposit no longer surfaces only as a failed transaction.

  See [Refund object](/platform-overview/core-concepts/transaction-lifecycle#refund-object).

  ## Expanded country coverage

  Grid now supports additional countries, including **China**, broadening the corridors
  available for global payments. Review the currencies and rails available in each region
  in [Currencies and rails](/platform-overview/core-concepts/currencies-and-rails).
</Update>


This documentation is built and hosted on [Mintlify](https://mintlify.com), a developer documentation platform.