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.
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.
| 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.
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.
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.
Documentation only. No request or response changed, and no field moved. One example value contradicted the constraint printed beside it.
bank_numberno longer shows a six-character example against a four-character limit. On the request side the field declaresmaxLength: 4and 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 forbeta.23; the cap moved and the example did not. It now reads"1234", which is whatTsysIntegrationRequestExamplein this same document has said all along. The nullable twin on the detail schema carries nomaxLength, 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.
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.
- A
payoutexample onPOST /v1/transactions, request and response. There was noPAYOUTexample before, so the only way to see the shape was to readBankAccountInputand infer it.PayoutRequestExampleshows an ACH credit withstandard_entry_class: CCD;PayoutResponseExampleshows the approved result, including the ACH return fields in their not-yet-returned state.
Tipnow says how it relates toamount. It never did, and the two readings differ by the size of the tip.amountis the whole charge andtipdeclares how much of that charge is gratuity — a $100.00 bill with a $20.00 tip isamount: 12000withtip: 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 inamount, you have been under-charging.SaleRecurringSubsequentRequestExampleno longer claims its rules are enforced by the schema. They are not:TransactionCreateRequestandRecurringDetailscarry flatrequiredlists and no conditional validation, so a generated client accepts a request the gateway then rejects with 400INVALID_ACTION. The rules are real and the gateway enforces them; only the attribution was wrong.- A subsequent recurring payment may carry either
initial_transaction_idorinitial_network_transaction_id. The same example said the first was required. It is not — the gateway accepts either, andinitial_network_transaction_idis the one to send when the agreement began outside this gateway.
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.
payment_methodon an ACH response replacesnamewithfirst_nameandlast_name. The gateway made this change on 2026-09-11 and this document did not follow, sobeta.24has been describing a field the API stopped sending.BankAccountOutputsetsadditionalProperties: false, which means a client validating an ACH transaction response againstbeta.24has been rejecting every one of them since that date. Readlast_namewhere you readname; it carries the same value the old field did.PAYMENT_TOKENis gone fromPaymentMethodTypeOutput, andPaymentTokenOutputis gone fromPaymentMethodOutput. 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 isCARDorACHand the token comes back inpayment_tokenon either one. Delete the branch rather than replacing it.
payment_method.card_typeis alwaysUNKNOWNon 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 fromGET /v1/transactionsorGET /v1/transactions/{id}.TransactionResponsenow 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. TreatUNKNOWNas "not yet known" on a transaction response and "the processor could not determine it" on a reporting response.card_typeonGET /v1/transactionsaccepts a comma-separated list. It always did; the document declared a single value. Sendcard_type=CREDIT,DEBITto match either.card_typeonGET /v1/batches/transactionsis a different parameter with a narrower value set. It takesDEBITorCREDITonly —UNKNOWNreturns 400 there — and it does not accept a list.CREDITon 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 onGET /v1/transactions. One schema was describing both operations and could not describe either correctly.metadatastates 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_detailson a client request is no longer the same schema as on a response. The request schemas are new:ClientAdminContactDetailsInputon create, wherefirst_name,last_nameandemailare required, non-nullable and must not be empty, andClientAdminContactDetailsUpdateon update, where every property is optional. The old shared schema declared those properties required and nullable at once, so sending"first_name": nullsatisfied the document and returned 400 from the service.
- 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.
- The response
admin_contact_detailsstill 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: emailonadmin_contact_details.emaildoes 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, soops+paradise@example.comandops@acme.agencyboth 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.phoneaccepts less than thePhoneschema it points at. A separator directly after the country code is rejected there and accepted onprimary_phoneandsupport_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.websiteonPOST /v1/clientsis 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.
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.
TransactionResponsegainsis_returned,returned_at,return_codeandreturn_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.transactionsitems 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/transactionsandGET /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_returnedalongsideis_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
resultalone should now also checkis_returned. An approved transaction that was later returned is not a successful one, and before this version you had no way to tell.
is_returnedis false on a card transaction, never absent. Only ACH can be returned. The property is a plainbooleanrather than nullable, so a card transaction reportsfalse. Confirmed against dev on 2026-09-06 across 25 card rows and 39 ACH rows — all 64 carried the property.return_codeandreturn_reasonare omitted, not null, when there is no return. Neither key appeared on any of those 64 rows.returned_atbehaves 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_returnedtrue, so the shape of a populatedreturned_atis declared here rather than measured. It is typedformat: date-time, and VeriCheck's own events feed reports timestamps as2021-07-26 16:44:25 ET, which the gateway converts before returning it. If you see theETform reach you, that conversion is missing and the value is violating this document — report it. return_codeis a free-form string, not an enum. The NACHA codes are not published as an enum withx-enumDescriptionsyet. That work is planned. Do not switch on the value without a default branch.- No filter or sort by return state.
GET /v1/transactionsgains 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_returnedin itsrequiredlist. 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.
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.
POST /v1/integrationsandPOST /v1/integrations/{id}require 23 more fields on a TSYSintegration_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,urlandv_numberjoin 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 returning400.- The same 23 stopped accepting
null. They were typed[string, "null"]and are nowstring—amex_opt_bluelikewise becomes a plainboolean..NotEmpty()rejects null as well as"", so the nullable typing was never true. POST /v1/batches/closerequiresclient_id,processor,batch_typeandcreated_at. The gateway already rejected a request missing any of them.client_idandcreated_atalso stop acceptingnull.batch_idis 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.
- Fourteen
integration_detailsfields now carry themaxLengththe service enforces:address132,bank_number4,descriptor_city32,descriptor_country3,descriptor_postal10,descriptor_state2,descriptor_store_number4,fcsid7,merchant_aba_number9,merchant_settlement_agent_number4,name25,sharing_group30,terminal_id8,v_number8. These mirror the TSYS G3v088 field widths and have always been enforced. A 40-characterdescriptor_citywas returning400against a document that declared no limit. TsysIntegrationRequestExamplegained the four required fields it never carried —amex_number,association,bin,discover_number— and itsbank_numberdropped from123456to1234, because six characters exceeded the cap the service applies. Both the request and response examples changed, so they still agree.
- The spec still does not declare
minLength: 1on 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,mccandname— are required too. So no field on the integration schema earned a length floor.
card_typeonGET /v1/transactionsacceptsUNKNOWN. The processor reports it when it cannot determine the card type, and it is the common case, so a filter ofCREDITorDEBITalone returns very few rows.
EntryMethodsaid "Currently the only option isECOMMERCE" while its enum had carriedMOTOsincebeta.20. The description now names both values. Document only — no request or response shape moved.
- The
typefilter onGET /v1/transactionsno longer acceptsVOID. Sending it returns a server error. A canceled payment is reported asCANCEL, so filter on that instead.
- The
typefilter acceptsPAYOUT,REFUND, andCANCEL, and is now declared as an array so a comma-separated list is explicit —type=SALE,AUTH. Matching is case-insensitive. phoneonadmin_contact_detailsis optional and accepts null. It was required.
- Filtering by
PAYOUTis accepted and then ignored. It returns unfiltered results rather than an error.
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.
entry_methodmoved from the transaction request root intopayment_method. Send it inside the card object.POST /v1/transactions/{id}/captureno longer takestypein the body. The endpoint implies it.POST /v1/integrationsno longer takesmessage, and no longer requires it.
POST /v1/clientsreturnsapi_key. This is the only call that ever returns it. Store it as a secret; no later call reissues it.metadataon the client, integration, and TSYS host detail objects.correlation_idon the batch, client, and integration responses.recurringon the transaction request and response, andinitial_network_transaction_idonrecurring_details.settlement_issueon the transaction response.currencyon the transaction request.entry_methodon the card payment method, withMOTOalongsideECOMMERCE. Both are Card Not Present.- A declared
502response, so a processor failure is part of the contract. - Vericheck onboarding request and response schemas.
- The integration summary response, and
VERICHECKon theintegrationfilter. - Roughly forty TSYS host detail fields on the integration request and response.
ProcessortakesTsysandVericheck, in that casing. The enum previously saidTSYSandVERICHECK, which no integration has ever returned. This field is parsed case-sensitively, so the old spelling was always rejected.phonebecamephone_numberon the client and TSYS host detail objects, andfaxbecamefax_number. The service has always used the longer names.configuration_idbecameidon the integration responses.- Several fields the API returns as null were declared non-nullable, so a strongly typed SDK threw on real responses:
websiteon the create-client response,initiatorandrecurring_detailson the transaction response, andclosed_at_date_timeandsettled_at_date_timeon a batch. BankAccountOutputrequirestype, so the payment method discriminator compiles.PaymentMethodTypesplit intoPaymentMethodTypeInputandPaymentMethodTypeOutput, and transaction type split intoTransactionCreateTypeandTransactionResponseType. 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 nowinteger.
- Version bump only. No content changed, and
beta.18was never published.