# 

What changed in `openapi.yaml`, and what you have to do about it.

Read this before you regenerate an SDK. Every published SDK is generated from this spec on every release, so a change
here reaches your build whether or not you asked for it.

## How versions work during beta

The version lives in `info.version` and looks like `1.0.0-beta.N`.

- **N increases by exactly one on every push that changes the spec.** No skips,
no reuse, no batching. One push, one N.
- **N carries no meaning beyond identity.** It tells you which build you compiled
against. It does not tell you whether the change was safe.
- **Severity lives in this file, not in the number.** Read the categories below.


At general availability the version drops to `1.0.0` and switches to ordinary
semantic versioning, where a major bump means a breaking change.

## What the categories mean

| Category | What happened | What you do |
|  --- | --- | --- |
| **Breaking** | Client code that was correct stops working | Change your code before this version reaches your environment |
| **Deprecated** | Still works, removal is scheduled | Plan the work before the stated date |
| **Added** | New optional surface | Adopt when it is useful to you |
| **Corrected** | The document changed, the API did not | Re-read if you built to the old text |


**Corrected** is the category to watch. Much of this spec was written ahead of the
service, so a correction often means the API never behaved the way the document
claimed. If you built to observed behavior you are fine. If you built to the
document, you may have been broken all along and this change is what tells you.

## How deprecations work

A deprecation is a promise with a date on it.

- Every bullet under **Deprecated** ends with `Removal: YYYY-MM-DD`. An
open-ended deprecation is not permitted, and CI rejects one.
- **The default window is four weeks** from the release that announces it. A
shorter window needs the affected team's agreement; CI warns rather than fails,
so the exception is visible instead of silent.
- When the deprecated value appears on a **response**, the old value keeps being
returned for the whole window. Generated SDKs throw on a value they do not
know, so a client on an older SDK breaks the moment the response changes — not
when the enum grows. The announcement and the flip are separate releases, and
both appear here.


## A note on the entries before beta.23

This file starts at `beta.19`. Everything from `beta.19` through `beta.22` is
**reconstructed from git history**, because no changelog existed at the time and
the version was not bumped per push — `beta.20` alone covers 20 commits across
six days. Treat those four entries as a good-faith summary rather than a record,
and confirm anything you intend to rely on against the spec itself.

`beta.23` onward is written at the time of the change.

## 1.0.0-beta.48 — 2026-10-22

The request examples on `POST /v1/transactions` now send a tax amount. **Nothing changes in a request or a response, and the API did not change.**

### Corrected

- **`tax_amount` in the request examples.** Every sale, authorization and payout example on `POST /v1/transactions` now sends a `tax_amount` that is part of its `amount`: 500 of a $100.00 sale or authorization, 500 of the $120.00 sale with a tip, and 1250 of the $250.00 payout. Each example's description says how much of the charge is tax, and each matching response example echoes the value. The sale read back by `TransactionGetResponseExample`, its entry in `TransactionListResponseExample`, the authorization's entry there, and both capture examples report 500 too, because a capture reports the value from the authorization it captures. The declined sale sends none, and the void and refund report `null`, as every refund and void does.


## 1.0.0-beta.47 — 2026-10-22

Every transaction response example now shows `tax_amount`. **Nothing changes in a request or a response, and the API did not change.**

### Corrected

- **`tax_amount` in the transaction response examples.** The API returns `tax_amount` on every transaction response, as `null` when no tax amount was sent, but only `SaleWithTaxResponseExample` showed it. The other 17 examples, from the create, capture, cancel, get and list operations, now carry `tax_amount: null`, because none of their transactions was sent one. A capture copies its auth's value, and the auth in `AuthWithCardRequestExample` sends none. The request examples are unchanged, because `tax_amount` is optional, and `SaleWithTaxRequestExample` remains the one that sends it.


## 1.0.0-beta.46 — 2026-10-22

A transaction can say how much of its amount is tax, and every transaction response and report reads it back. **Nothing changes for a request that leaves it out.**

### Added

- **`tax_amount` on `POST /v1/transactions`.** Optional, in cents, from 0 to 99,999,999, on a card sale, a card auth, an ACH sale and an ACH payout. It is part of `amount`, not added to it: `amount: 10500` with `tax_amount: 500` is a $105.00 charge of which $5.00 is tax. It is for reporting only, and the gateway does not check it against `amount`. A negative value is refused with `400` and error code `INVALID_REQUEST_DATA`.
- **`tax_amount` on `TransactionResponse`.** A create response echoes the value sent, and a capture reports the value from the auth it captures. A transaction created without one reports `null`, not `0`. That includes every transaction created before 2026-10-22, and every refund and void. `GET /v1/transactions` and `GET /v1/transactions/{transactionId}` return it too, because both return `TransactionResponse`.
- **A sale-with-tax example pair on `POST /v1/transactions`,** `SaleWithTaxRequestExample` and `SaleWithTaxResponseExample`, beside the sale-with-tip pair.


## 1.0.0-beta.45 — 2026-10-22

The batch list reports each batch's status by name, and the document now lists the names. **Nothing changes in a request, and the API did not change.**

### Corrected

- **`BatchElasticModel.status` is `Opened`, `Closed`, `In-Progress` or `Failed`.** `GET /v1/batches` returns these four, capitalized and with a hyphen in `In-Progress`. The schema used to say `open`, `closed` or `settled` and gave `closed` as its example, so code comparing against the old text would never match a real batch. `settled` was never a status: a settled batch reports `Closed`. `GET /v1/batches/status` reports the same statuses as the codes `"2"`, `"5"` and `"10"`, as beta.43 described.


## 1.0.0-beta.44 — 2026-10-22

The batch list's `term` filter now says what it accepts. **Nothing changes in a request or a response, and the API did not change.**

### Corrected

- **`term` on `GET /v1/batches` is described.** It was a bare string with no description. It takes a comma-separated list of pieces: `createdate` comparisons against a quoted ISO 8601 timestamp, one `status=` of `opened`, `closed`, `in_progress` or `failed`, and one IANA time zone name or `UTC`. For example, `createdate>='2026-09-01T00:00:00Z',createdate<='2026-09-30T23:59:59Z',status=closed,America/Chicago`. Any other piece is refused.


### Known limitation

- **A `term` the service refuses returns 500 `SYSTEM_ERROR`, not 400.** Check the pieces against the list above before sending.


## 1.0.0-beta.43 — 2026-10-22

The batch status poll reports each batch's status as a numeric code, and the document now lists the codes. **Nothing changes in a request, and the API did not change.**

### Corrected

- **`BatchStatusDto.status` is `"2"`, `"5"` or `"10"`.** `GET /v1/batches/status` returns `"10"` for every batch in `in_progress_batches`, and `"2"` (closed) or `"5"` (failed) for every batch in `completed_batches`. The schema used to give `"in_progress"` as its example and list no values, so code built to the old text would never match a real status. The codes are strings, not numbers. `GET /v1/batches` reports the same statuses by name, as `Closed`, `Failed` and `In-Progress`, so read the two endpoints' statuses separately.


## 1.0.0-beta.42 — 2026-10-22

Filtering the transaction list by `type=PAYOUT` now works, and the document no longer warns that it does not. **Nothing changes in a request or a response schema.**

### Corrected

- **`GET /v1/transactions?type=PAYOUT` returns only payouts.** The `type` parameter's description said a `PAYOUT` filter was accepted and ignored, returning every transaction. The gateway fixed that, and the fix reached production on 2026-10-16. If your code filtered payouts out on its own after the call, it can stop. `PAYMENT` is not a filter value, and sending it returns 400 `INVALID_REQUEST_DATA`, the same as `VOID`.


## 1.0.0-beta.41 — 2026-10-21

Creating a transaction and canceling one can return `503`, and the document now says so. **Nothing changes in a request**, and no existing response changes shape.

### Corrected

- **`POST /v1/transactions` and `POST /v1/transactions/{id}/cancel` declare a `503` response.** It carries `error_code` `PROCESSOR_UNAVAILABLE` in the usual error body, and means the processor is down or unreachable and did not process the request, so a retry after a backoff is safe. The gateway has returned it for bank account (ACH) transactions since 2026-10-09, when VeriCheck cannot be reached or answers with its own outage. Card transactions will return it too once TSYS reports that it, the issuer or the switch could not process the request. A `502` is different: the processor may have seen the transaction, so reconcile before retrying.


## 1.0.0-beta.40 — 2026-10-16

A transaction charged to a card token returns a two-digit expiry year, and the document now says so. **Nothing changes in a request.** Only `Expiry.year` changes, and it now accepts what the API already returns.

### Known limitation

- **The response to a transaction charged to a card token carries a two-digit `payment_method.expiry.year`.** A sale sent with `payment_method.type` of `TOKEN` for a card expiring in 2030 returns `"30"`. The card sale that issued the token returns `"2030"`, and so does a later read of either transaction. `Expiry.year` now accepts two or four digits, so a response that validated against beta.39 still validates, and one that failed only on this field now passes. Read a two-digit year as 20YY. When every response returns four digits, the schema goes back to four.


## 1.0.0-beta.39 — 2026-10-16

The create response does not report a missing payment token as null, and the document now says so. **Nothing changes on the wire.** Every schema keeps its shape.

### Known limitation

- **A transaction's own response carries a non-token string in `payment_token` when no token was issued.** A declined sale returns `""`, and an approved one whose payment method could not be tokenized returns the sentence `Payment method could not be tokenized.` A read of the same transaction returns `null`, which is what the `PaymentToken` description used to promise for every response. The description now says what each one returns. Until the create response returns `null` too, treat a value that starts with none of `CARD_TOKEN-`, `BANK_TOKEN-` and `PAYMENT_TOKEN-` as no token, and do not store it.


## 1.0.0-beta.38 — 2026-10-16

A new payment token now says what it stands for. **Nothing changes in a request.** Every schema keeps its shape, and a token you already hold keeps working.

### Added

- **A payment token's prefix names its payment method.** Since 2026-10-16, in dev and in production, a token issued for a card starts with `CARD_TOKEN-` and one issued for a bank account starts with `BANK_TOKEN-`. Before then, every token started with `PAYMENT_TOKEN-`, and those tokens are still accepted. So an older token's prefix does not say what it stands for. Send a token back exactly as it was returned. The `PaymentToken` description now says all of this.


### Corrected

- **The token examples use the new prefixes.** The card examples show `CARD_TOKEN-`, and the bank-account token sale and payout examples show `BANK_TOKEN-`.
- **beta.37's SEC code change reached production as well.** Its entry said the gateway refuses an ACH transaction without `standard_entry_class` on dev. It has done so in production too since 2026-10-16, and the entry now says so.


## 1.0.0-beta.37 — 2026-10-16

An ACH transaction now has to say how the customer authorized it. The gateway stopped filling in `WEB` for a missing Standard Entry Class (SEC) code, and this release brings the document into line.

### Breaking

- **`BankAccountInput` requires `standard_entry_class`.** Since 2026-10-16, in dev and in production, the gateway refuses an ACH sale or payout without it, with `400` and error code `REQUIRED_DATA_MISSING`, and `details` naming `payment_method.standard_entry_class`. Until then a missing code ran as `WEB`, and beta.36 still documented that default. Send the code that matches how the customer authorized the debit or credit, which is `WEB` for an online authorization if that is what you relied on.
- **A bank-account token needs `standard_entry_class` too.** A sale or payout that spends a `PaymentTokenInput` standing for a bank account, sent without the code, gets the same `400 REQUIRED_DATA_MISSING`. The schema cannot express "required only for a bank-account token", so `PaymentTokenInput.standard_entry_class` stays optional in the schema, and its description carries the rule.


### Corrected

- **A SEC code on a card token is ignored.** The `PaymentTokenInput.standard_entry_class` description and the `saleWithBankAccountToken` example said a code sent with a card token returns `400 INVALID_REQUEST_DATA`. The gateway now processes the card transaction and ignores the code.
- **The ACH examples no longer describe a default.** The ACH sale and bank-account token sale examples said an omitted code means `WEB`. Both now say the code is required.


## 1.0.0-beta.36 — 2026-10-16

The transaction examples now show what the gateway actually returns, and four Create Transaction use cases gain an example. **Nothing changes on the wire.** Every schema keeps its shape. Only examples, example values and two descriptions moved.

### Corrected

- **A new transaction has no `batch_id`.** The sale, token sale, payout, recurring, capture and void examples showed a batch ID on the response, and several said one was "already assigned". The gateway assigns it when the batch settles, so each of these responses now shows `batch_id: null`. The read-back examples show a batch ID only on the transaction that has settled, with `is_settled: true`.
- **Batch IDs have no `BATCH-` prefix.** The gateway returns a bare ULID, such as `01M15KXKX6ATHV4SNWSKSX5BG3`. Every batch ID example in the document, in the batch schemas too, now uses that form.
- **A read returns `initiator` as null.** The read-back examples showed `CUSTOMER`, which contradicted the `TransactionResponse.initiator` description. They now show `null`.
- **A create response echoes the request's `entry_method`.** The create examples showed `keyed`, which is what a reporting read returns. They now show `ECOMMERCE`, and the read-back examples keep `keyed`.
- **`network_transaction_id` examples look like real ones.** A network returns a 15-digit value such as `000000004271001`, not `MCC1234567890`. Each transaction example now carries its own value, and a capture, void or refund carries the value of the transaction it acts on.
- **The recurring examples form a chain.** The subsequent payment said it billed against `saleWithCard`, which has no `recurring_details` and so cannot start an agreement. It now points at the new initial payment. It also sends `recurring: true`, and its response echoes the `initial_network_transaction_id` the gateway looked up.
- **A refund comes back as `REFUND`.** The refund and void examples both said a refund is reported as `CANCEL`. Only a void is. The values were already right. The descriptions now match them.
- **ACH truncated numbers carry no mask.** The payout response showed `****21` and `****9012`. The gateway returns the last four digits alone, `0021` and `9012`. The `BankAccountOutput.route_number_truncated` example was the first four digits, and is now the last four.
- **Responses include `recurring`.** The gateway always returns it, `false` on a one-off transaction, and every transaction response example now shows it.
- **The `network_transaction_id` description names the right field.** A later charge quotes it back as `recurring_details.initial_network_transaction_id`, not `recurring.initial_network_transaction_id`.
- **Four new Create Transaction examples, each with its response.**
  - `saleRecurringInitial` is the customer-initiated sale that starts a recurring agreement.
  - `saleWithBankAccountToken` charges a token that stands for a bank account, with `standard_entry_class` sent on the request.
  - `saleWithCardMoto` uses the `MOTO` entry method.
  - `saleWithTip` shows a tip that is part of `amount`, not added to it.


## 1.0.0-beta.35 — 2026-10-15

The cancel and refund request `type` has its own schema. **Nothing changes on the wire.** A void still sends `VOID` and a refund still sends `REFUND`.

### Corrected

- **New `TransactionCancelType`, holding `VOID` and `REFUND`.** `TransactionCancelRequest.type` and `TransactionRefundRequest.type` now refer to it. Before, both borrowed `TransactionType`, which also listed `SALE`, `AUTH`, `PAYOUT` and `CAPTURE`, none of which this operation accepts. Each transaction type schema now has one job: `TransactionCreateType` for the create request, `TransactionCancelType` for the cancel and refund requests, and `TransactionResponseType` for responses and the `type` report filter.
- **`TransactionType` is removed.** Nothing refers to it anymore. A generated SDK loses that type, and its cancel and refund request models use `TransactionCancelType` instead.


## 1.0.0-beta.34 — 2026-10-15

Updating a client requires `dba` and `admin_contact_details` as well as `parent_id`. **The API already changed on 2026-10-14. This release makes the document match it.**

### Breaking

- **`UpdateClientsRequest` requires `dba` and `admin_contact_details`.** Since 2026-10-14, in production and in dev, the gateway refuses an update that leaves either one out, with `400` and error code `INVALID_REQUEST_DATA`. Until then both were optional, and beta.33 still said so. An update that changed only the phone number, or only the metadata, now fails.
Send the client's current `dba` when you are not renaming it. `admin_contact_details` has to be present, but nothing inside it is required, so `{}` is enough when the contact details are not changing. `dba` no longer accepts `null`.
A generated SDK now requires both fields when it builds an update request.


### Corrected

- **A missing `parent_id` returns `400`, not `500`.** beta.33 said an update without `parent_id` fails with `500` and `SYSTEM_ERROR`. Since 2026-10-14 it fails with `400` and `INVALID_REQUEST_DATA`, and `details` names each missing field, `parent_id` included.


## 1.0.0-beta.33 — 2026-10-14

Updating a client requires `parent_id`, and the document now says so. **The API did not change. Your generated SDK did.**

### Corrected

- **`UpdateClientsRequest` requires `parent_id`.** The schema declared no `parent_id`, said every field was optional, and refused any property it did not declare. The gateway needs it: an update that leaves it out fails with `500` and error code `SYSTEM_ERROR`. So an update built from this document, or from a generated SDK, could never succeed. The schema now declares `parent_id` as a required string, the same as on `CreateClientRequest`.
Send the client's current parent. The gateway stores the value as the client's parent, so sending a different one moves the client under that account.
A generated SDK now requires `parent_id` when it builds an update request, so code that builds one without it stops compiling. That code always failed at run time.


## 1.0.0-beta.32 — 2026-10-14

An ACH sale or payout made with a payment token can now carry its own SEC code. **This adds one optional field. A request you send today behaves as it did.**

### Added

- **`standard_entry_class` on `PaymentTokenInput`.** A request with `payment_method.type` of `TOKEN` may send the Standard Entry Class (SEC) code for that one ACH transaction. Until now a token request could not carry one, so every sale and payout made with a bank-account token ran as `WEB`, whatever the customer authorized. The response and `GET /v1/transactions/{id}` report the code that was used.
A payment token never stores a SEC code, so send it on every request that needs a code other than `WEB`. Leave it out and the transaction uses `WEB`, as before. Send it with a token that stands for a card and the request is refused with `400` and error code `INVALID_REQUEST_DATA`.
The field has no schema `default`, unlike `standard_entry_class` on `BankAccountInput`. A generated SDK that fills in defaults would otherwise send `WEB` on every token request, card tokens included, and every card-token sale would be refused.


## 1.0.0-beta.31 — 2026-10-14

A bank account `description` you leave out defaults to your DBA, not `ACH Transaction`. **The API did not change. Your generated SDK did.**

### Corrected

- **An omitted bank account `description` defaults to the merchant's DBA.** The schema declared `default: ACH Transaction`, and its text said two things at once: that a blank value fell back to the merchant's name, and that an omitted one became `ACH Transaction`. The gateway fills in the merchant's DBA, cut to 30 characters. The `default` is gone, and the text and the ACH sale example now say what the gateway does.
Generated SDKs applied the old default for you. The Java, Kotlin, JavaScript and C# SDKs set `description` to `ACH Transaction` whenever your code did not, so the gateway received a value and never substituted your DBA. From this release they leave the field out, and those payments carry your DBA on the customer's bank statement instead. To keep `ACH Transaction`, send it explicitly.


## 1.0.0-beta.30 — 2026-10-08

Documentation only. **No request or response changed.** One sentence on the
`type` filter described an error the API no longer returns.

### Corrected

- **Filtering `GET /v1/transactions` by `type=VOID` returns 400, not a server error.** The `type` parameter's description said `VOID` produced a server
error. Since 2026-09-14, an unrecognized query
value is rejected with 400 `INVALID_REQUEST_DATA` instead. `VOID` was never a
filter value. A cancelled payment is reported as `CANCEL`, and that is the
value to filter on.
The `PAYOUT` warning beside it stays. `type=PAYOUT` is still accepted and
ignored in production.


## 1.0.0-beta.29 — 2026-10-06

The request payment method type is now spelled like every other enum in the API:
`CARD`, `ACH` and `TOKEN`. **The API did not change. Your generated SDK did.**

### Breaking

- **`PaymentMethodTypeInput` is `ACH`, `CARD`, `TOKEN`.** It was `ACH`, `Card`,
`Token`. The `type` constant on `CardInput` and `PaymentTokenInput` and the
`PaymentMethodInput` discriminator keys moved with it. A generated SDK renames
the enum members and discriminator values, so code naming `Card` or `Token`
stops compiling. Switch to `CARD` and `TOKEN`.
If you call the API over HTTP without an SDK, nothing breaks. The gateway
matches `payment_method.type` without regard to case, so `Card` and `Token`
keep working alongside `CARD` and `TOKEN`. Send the new spellings anyway; they
are the ones the document promises.
Responses do not change. `PaymentMethodTypeOutput` stays `ACH`, `CARD`: a
token-funded transaction reports the card or bank account behind the token,
and the token itself comes back in `payment_token`.


## 1.0.0-beta.28 — 2026-09-30

Documentation only. **No request or response changed**, and no field moved. Five
descriptions named two units for the same number.

### Corrected

- **The batch amount fields now name one unit: cents.** On `BatchElasticModel`,
the descriptions of `net_amount`, `approved_amount`, `captured_amount`,
`void_amount` and `refund_amount` each opened "…amount in dollars" and then
said "Amount in cents". The fields are integer cents, and
the schema has said `integer` since `beta.10`. Only the leftover "in dollars"
was wrong. It is gone, and each description now reads like
`batch_net_deposit`'s beside it.
Nothing about the contract changed. If you treated these fields as cents, as
the type required, you were always correct. The generated SDKs carry the
descriptions as doc comments, so their text changes with this release.


## 1.0.0-beta.27 — 2026-09-18

Documentation only. **No request or response changed**, and no field moved. One
example value contradicted the constraint printed beside it.

### Corrected

- **`bank_number` no longer shows a six-character example against a four-character limit.** On the request side the field declares `maxLength: 4` and its example
read `"123456"`, so anyone who copied the example got a 400 from the gateway. The value left behind when the field was capped for
`beta.23`; the cap moved and the example did not. It now reads `"1234"`, which
is what `TsysIntegrationRequestExample` in this same document has said all
along. The nullable twin on the detail schema carries no `maxLength`, so its
example was legal but described a bank number TSYS never issues — it moved to
`"1234"` as well, so the field reads the same everywhere.
Nothing about the contract changed. If you were sending a four-character
`bank_number`, as the constraint required, you were always correct.


## 1.0.0-beta.26 — 2026-09-17

Documentation only. **No request or response changed**, and nothing you built
against `beta.25` needs touching. Three descriptions were wrong or silent about
behavior the gateway has always had, and there is one new example.

Found while rewriting the developer portal's Payments section against this
document.

### Added

- **A `payout` example on `POST /v1/transactions`,** request and response. There
was no `PAYOUT` example before, so the only way to see the shape was to read
`BankAccountInput` and infer it. `PayoutRequestExample` shows an ACH credit
with `standard_entry_class: CCD`; `PayoutResponseExample` shows the approved
result, including the ACH return fields in their not-yet-returned state.


### Corrected

- **`Tip` now says how it relates to `amount`.** It never did, and the two
readings differ by the size of the tip. `amount` is the whole charge and `tip`
declares how much of that charge is gratuity — a $100.00 bill with a $20.00
tip is `amount: 12000` with `tip: 2000`. The tip is reported to the processor
separately at settlement and never increases the settled amount. **If you were sending the pre-tip figure in `amount`, you have been under-charging.**
- **`SaleRecurringSubsequentRequestExample` no longer claims its rules are enforced by the schema.** They are not: `TransactionCreateRequest` and
`RecurringDetails` carry flat `required` lists and no conditional validation,
so a generated client accepts a request the gateway then rejects with 400
`INVALID_ACTION`. The rules are real and the gateway enforces them; only the
attribution was wrong.
- **A subsequent recurring payment may carry either `initial_transaction_id` or `initial_network_transaction_id`.** The same example said the first was
required. It is not — the gateway accepts either, and
`initial_network_transaction_id` is the one to send when the agreement began
outside this gateway.


## 1.0.0-beta.25 — 2026-09-16

Payment method changes on both sides of the contract, plus the client request
schemas. **Most of this is the document catching up with the service**, so read
the Corrected section first — if you built to the old text rather than to
observed behavior, that is where you find out.

**This version covers four spec pushes, not one.** The version did not move on
any of them, so the release check failed each time and no SDK was
built. No SDK was ever published from those four commits. Nothing is missing
from your build as a result — the SDKs go straight from `beta.24` to this
version, carrying everything below.

### Breaking

- **`payment_method` on an ACH response replaces `name` with `first_name` and `last_name`.** The gateway made this change on 2026-09-11 and
this document did not follow, so `beta.24` has been describing a field the API
stopped sending. `BankAccountOutput` sets `additionalProperties: false`, which
means a client validating an ACH transaction response against `beta.24` has
been rejecting every one of them since that date. Read `last_name` where you
read `name`; it carries the same value the old field did.
- **`PAYMENT_TOKEN` is gone from `PaymentMethodTypeOutput`, and `PaymentTokenOutput` is gone from `PaymentMethodOutput`.** Your generated enum
loses a member, so code naming it stops compiling. **That code was already dead.** The gateway never returned it: paying with a token resolves the token
to the card or bank account behind it and reports that, so a response is `CARD`
or `ACH` and the token comes back in `payment_token` on either one. Delete the
branch rather than replacing it.


### Corrected

- **`payment_method.card_type` is always `UNKNOWN` on a transaction response.**
Not sometimes — always. The processor does not return the card type on the
authorization, so the gateway has nothing to report yet, and the determined
value appears when you read the transaction back from `GET /v1/transactions` or
`GET /v1/transactions/{id}`. `TransactionResponse` now states the general rule
this is an instance of: the transaction and reporting endpoints return the same
object, a transaction response is the earlier of the two, and some values are
not known when the gateway answers the original request. Treat `UNKNOWN` as
"not yet known" on a transaction response and "the processor could not
determine it" on a reporting response.
- **`card_type` on `GET /v1/transactions` accepts a comma-separated list.** It
always did; the document declared a single value. Send `card_type=CREDIT,DEBIT`
to match either.
- **`card_type` on `GET /v1/batches/transactions` is a different parameter with a narrower value set.** It takes `DEBIT` or `CREDIT` only — `UNKNOWN` returns 400
there — and it does not accept a list. `CREDIT` on that operation also returns
transactions whose card type is undetermined, because open-batch eligibility
groups the two together. The same value therefore selects a wider set of rows
there than on `GET /v1/transactions`. One schema was describing both operations
and could not describe either correctly.
- **`metadata` states the four limits the gateway enforces.** At most 50 keys,
keys matching `^[a-zA-Z0-9_-]+$` up to 40 characters, values up to 500
characters. All four were already enforced and the 50-key cap was not written
down anywhere. They are machine-readable now, so your SDK can check them
instead of your reading this paragraph.
- **`admin_contact_details` on a client request is no longer the same schema as on a response.** The request schemas are new: `ClientAdminContactDetailsInput` on
create, where `first_name`, `last_name` and `email` are required, non-nullable
and must not be empty, and `ClientAdminContactDetailsUpdate` on update, where
every property is optional. The old shared schema declared those properties
required *and* nullable at once, so sending `"first_name": null` satisfied the
document and returned 400 from the service.


### Added

- **A partial client update is now legal in the document.** `POST /v1/clients/{clientId}` has always accepted a body changing only what you name;
the shared schema made the document demand four contact properties you had no
intention of changing. Send only what you are changing. Re-sending the rest was
how a value got overwritten with a stale copy.


### Known limitation

- **The response `admin_contact_details` still declares its four properties required and nullable.** That is deliberate and correct on a response: the keys
are always present, and a client created through an internal path can genuinely
carry null. Only the request side was tightened.
- **`format: email` on `admin_contact_details.email` does not describe what is accepted.** The gateway enforces a stricter rule than the format implies — it
rejects plus-addressing and every top-level domain longer than four characters,
so `ops+paradise@example.com` and `ops@acme.agency` both return 400. That is a
service defect rather than a documentation one, and it is being fixed rather
than documented. Until it ships, avoid both shapes.
- **`admin_contact_details.phone` accepts less than the `Phone` schema it points at.** A separator directly after the country code is rejected there and accepted
on `primary_phone` and `support_phone` — so `+1-800-555-1234`, which this
document publishes as an example on that schema, returns 400 on the admin
contact. Also a service defect being fixed rather than documented. Until it
ships, send that field without a separator after the country code:
`+18005551234`.
- **`website` on `POST /v1/clients` is capped at 30 characters and this document still does not say so.** A correction is planned. The cap comes from the
acceptor URL field the processor carries.


## 1.0.0-beta.24 — 2026-09-04

Four properties that report a returned VeriCheck ACH transaction. **The service already emits them** — they shipped on 2026-09-04 — so this version is the document catching up, not a new capability.

That matters if you validate responses. `TransactionResponse` sets
`additionalProperties: false`, so a client checking a live response against
`beta.23` has been rejecting every transaction since the service shipped. This
version is the fix for that.

### Added

- **`TransactionResponse` gains `is_returned`, `returned_at`, `return_code` and `return_reason`.** An ACH transaction can be returned days after it was
approved, and it can be returned after it has already settled. Nothing in the
contract said so before this version.
- One schema change reaches all seven operations that return a transaction,
because `TransactionsListResponse.transactions` items point at this schema:
`POST /v1/transactions`, `GET /v1/transactions`, `GET /v1/transactions/{id}`,
`POST /v1/transactions/{id}/capture`, `POST /v1/transactions/{id}/cancel`,
`GET /v1/batches/transactions` and `GET /v1/batches/{id}/transactions`.


**What you have to do.** Nothing, to keep compiling — all four are optional, the
way every other property on this schema is. Two things are worth changing anyway:

- **Read `is_returned` alongside `is_settled`, not instead of it.** A transaction
can be both settled and returned. A returned transaction that never settled is
also possible.
- **Anything computing a success state from `result` alone should now also check `is_returned`.** An approved transaction that was later returned is not a
successful one, and before this version you had no way to tell.


### Known limitation

- **`is_returned` is false on a card transaction, never absent.** Only ACH can be
returned. The property is a plain `boolean` rather than nullable, so a card
transaction reports `false`. Confirmed against dev on 2026-09-06 across 25
card rows and 39 ACH rows — all 64 carried the property.
- **`return_code` and `return_reason` are omitted, not null, when there is no return.** Neither key appeared on any of those 64 rows. `returned_at` behaves
differently: it is always present and null. So read all three with a
key-missing check, not only a null check.
- **A returned transaction has not been observed on dev.** No row in either
sample carried `is_returned` true, so the shape of a populated `returned_at`
is declared here rather than measured. It is typed `format: date-time`, and
VeriCheck's own events feed reports timestamps as `2021-07-26 16:44:25 ET`,
which the gateway converts before returning it. If you see the `ET` form reach you, that conversion is missing
and the value is violating this document — report it.
- **`return_code` is a free-form string, not an enum.** The NACHA codes are not
published as an enum with `x-enumDescriptions` yet. That work is planned.
Do not switch on the value without a default branch.
- **No filter or sort by return state.** `GET /v1/transactions` gains no query
parameter here, so finding returned transactions means reading the flag on each
row. Whether a return should also raise a webhook is an open
question.
- The emitted OpenAPI document names `is_returned` in its `required` list. That
is an artifact of how the document is generated rather than a statement about
the contract, and it is deliberately not carried into this document. Treat all
four as optional, the way every other property on this schema is.


## 1.0.0-beta.23 — 2026-09-02

Required fields the gateway has always enforced and the spec never named. **The API did not change.** Every request this version rejects was already being
rejected; the document simply now says so, and your generated SDK will now say so
at compile time instead of leaving you to find out at runtime.

### Breaking

- **`POST /v1/integrations` and `POST /v1/integrations/{id}` require 23 more fields on a TSYS `integration_details`.** The list goes from six to
twenty-nine: `address1`, `agent_bank_number`, `agent_chain_number`,
`amex_number`, `amex_opt_blue`, `association`, `bin`, `descriptor_city`,
`descriptor_postal`, `descriptor_state`, `discover_number`, `industry`,
`merchant_aba_number`, `merchant_settlement_agent_number`, `mid`, `mvv`,
`reimbursement_attribute`, `sharing_group`, `store_number`, `terminal_id`,
`terminal_number`, `url` and `v_number` join the six already there. The gateway already rejected an empty value for every one of them, so a
boarding request that omitted any of them was already returning `400`.
- **The same 23 stopped accepting `null`.** They were typed `[string, "null"]`
and are now `string` — `amex_opt_blue` likewise becomes a plain `boolean`.
`.NotEmpty()` rejects null as well as `""`, so the nullable typing was never
true.
- **`POST /v1/batches/close` requires `client_id`, `processor`, `batch_type` and `created_at`.** The gateway already rejected a request missing any of them. `client_id`
and `created_at` also stop accepting `null`. **`batch_id` is unchanged** —
omitting it still closes every open batch for the client, which is the normal
call.


**What you have to do.** Regenerate, then expect compile errors wherever you
build a TSYS integration or a batch close. Fill in the fields you were omitting.
If your code already sent all of them, nothing changes for you but the type
signatures.

### Corrected

- Fourteen `integration_details` fields now carry the `maxLength` the service
enforces: `address1` 32, `bank_number` 4, `descriptor_city` 32,
`descriptor_country` 3, `descriptor_postal` 10, `descriptor_state` 2,
`descriptor_store_number` 4, `fcsid` 7, `merchant_aba_number` 9,
`merchant_settlement_agent_number` 4, `name` 25, `sharing_group` 30,
`terminal_id` 8, `v_number` 8. These mirror the TSYS G3v088 field widths and
have always been enforced. A 40-character `descriptor_city` was returning `400`
against a document that declared no limit.
- `TsysIntegrationRequestExample` gained the four required fields it never
carried — `amex_number`, `association`, `bin`, `discover_number` — and its
`bank_number` dropped from `123456` to `1234`, because six characters exceeded
the cap the service applies. Both the request and response examples changed, so
they still agree.


### Known limitation

- The spec still does not declare `minLength: 1` on the 52 fields where the
service rejects an empty string. Almost all of them are required fields whose
`.NotEmpty()` rule already says so, and restating it as a length would state the
same fact twice. The three that are not required — `bank_number`, `mcc` and
`name` — are required too. So no field on the integration schema earned a
length floor.


## 1.0.0-beta.22 — 2026-08-30

### Added

- `card_type` on `GET /v1/transactions` accepts `UNKNOWN`. The processor reports
it when it cannot determine the card type, and it is the common case, so a
filter of `CREDIT` or `DEBIT` alone returns very few rows.


### Corrected

- `EntryMethod` said "Currently the only option is `ECOMMERCE`" while its enum
had carried `MOTO` since `beta.20`. The description now names both values.
Document only — no request or response shape moved.


## 1.0.0-beta.21 — 2026-08-30

### Breaking

- The `type` filter on `GET /v1/transactions` no longer accepts `VOID`. Sending
it returns a server error. A canceled payment is reported as `CANCEL`, so
filter on that instead.


### Added

- The `type` filter accepts `PAYOUT`, `REFUND`, and `CANCEL`, and is now declared
as an array so a comma-separated list is explicit — `type=SALE,AUTH`. Matching
is case-insensitive.
- `phone` on `admin_contact_details` is optional and accepts null. It was
required.


### Known limitation

- Filtering by `PAYOUT` is accepted and then ignored. It returns unfiltered
results rather than an error.


## 1.0.0-beta.20 — 2026-08-29

The reconciliation release. The spec was compared against the service's emitted
Swagger, its C# source, and its validators, and corrected to describe what the
API actually does. Twenty commits landed under this one version. **If you are on `beta.19` or earlier, regenerate your SDK and read this entry in full.**

### Breaking

- `entry_method` moved from the transaction request root into `payment_method`.
Send it inside the card object.
- `POST /v1/transactions/{id}/capture` no longer takes `type` in the body. The
endpoint implies it.
- `POST /v1/integrations` no longer takes `message`, and no longer requires it.


### Added

- `POST /v1/clients` returns `api_key`. **This is the only call that ever returns it.** Store it as a secret; no later call reissues it.
- `metadata` on the client, integration, and TSYS host detail objects.
- `correlation_id` on the batch, client, and integration responses.
- `recurring` on the transaction request and response, and
`initial_network_transaction_id` on `recurring_details`.
- `settlement_issue` on the transaction response.
- `currency` on the transaction request.
- `entry_method` on the card payment method, with `MOTO` alongside `ECOMMERCE`.
Both are Card Not Present.
- A declared `502` response, so a processor failure is part of the contract.
- Vericheck onboarding request and response schemas.
- The integration summary response, and `VERICHECK` on the `integration` filter.
- Roughly forty TSYS host detail fields on the integration request and response.


### Corrected

- `Processor` takes `Tsys` and `Vericheck`, in that casing. The enum previously
said `TSYS` and `VERICHECK`, which no integration has ever returned. This field
is parsed case-sensitively, so the old spelling was always rejected.
- `phone` became `phone_number` on the client and TSYS host detail objects, and
`fax` became `fax_number`. The service has always used the longer names.
- `configuration_id` became `id` on the integration responses.
- Several fields the API returns as null were declared non-nullable, so a
strongly typed SDK threw on real responses: `website` on the create-client
response, `initiator` and `recurring_details` on the transaction response, and
`closed_at_date_time` and `settled_at_date_time` on a batch.
- `BankAccountOutput` requires `type`, so the payment method discriminator
compiles.
- `PaymentMethodType` split into `PaymentMethodTypeInput` and
`PaymentMethodTypeOutput`, and transaction type split into
`TransactionCreateType` and `TransactionResponseType`. A request and a response
do not use the same vocabulary, and one enum could not describe both.
- Several integer fields were declared as `number`, which generates a floating
point type in most SDKs. They are now `integer`.


## 1.0.0-beta.19 — 2026-08-24

### Corrected

- Version bump only. No content changed, and `beta.18` was never published.