Payments switch · direct integration

Cygma API

A direct JSON gateway to Cygma — sale, pre-auth, completion, refund, reversal, balance inquiry, tokenization, and batch settlement, all over plain HTTPS. Built for cart vendors, kiosks, VAR integrations, and anywhere a merchant needs to authorize a card payment without routing through an intermediate gateway.

Overview

Introduction

Cygma is a full ISO 8583 payments switch wrapped in a JSON API. One bearer credential per terminal, one POST per transaction, standard ISO response codes that mean exactly what the spec says they mean.

Every endpoint is a POST with a JSON body. The body shape is the same across every transaction type — only theMessageType, ProcessingCode, and the URL path change.

  • Production: https://api.cygma.com:443
  • Cert sandbox: https://apicert-sandbox.cygma.com:9443
3 steps

Quickstart

  1. Get your CardAcquirerId, TerminalId, and SecurityControlInformation from EPI. The first two go in plaintext per request; the third is the auth secret.
  2. POST your transaction JSON to the matching URL (one URL per transaction type — see Sale below for the canonical shape).
  3. Read ResponseCode from the response. "00" means approved; anything else, look up in Response codes.
curl -X POST https://api.cygma.com:443/hostapi/saledebitebt/ \
  -H "Content-Type: application/json" \
  -d '{
  "MessageType": "200",
  "PrimaryAccountNumber": "4005529091234562",
  "ProcessingCode": "000000",
  "TransactionAmount": "000000000400",
  "LocalTransactionTime": "103200",
  "LocalTransactionDate": "04/09",
  "SystemsTraceNumber": "599220",
  "ExpirationDate": "31/12",
  "POSEntryMode": "010",
  "POSConditionCode": "59",
  "TerminalId": "<TERMINAL_ID>",
  "CardAcquirerId": "<YOUR_CARD_ACQUIRER_ID>",
  "SecurityControlInformation": "<SCI>"
}'
Credentials

Authentication

Three fields identify the request — all live in the JSON body:

  • CardAcquirerId (DE 42) — your parent merchant number, assigned by EPI when the merchant is provisioned.
  • TerminalId (DE 41) — unique per merchant. EPI assigns this when the merchant is provisioned.
  • SecurityControlInformation (DE 53) — the per-terminal secret. Cygma validates the (acquirer, SCI) pair on every call and declines if either doesn’t match what they have stored. Treat it like a bearer token.

TLS terminates at the Cygma edge. There’s no Authorization header — credentials travel in the body.

// Required on EVERY request
{
  "CardAcquirerId":             "<YOUR_CARD_ACQUIRER_ID>",   // DE 42, assigned by EPI
  "TerminalId":                 "<TERMINAL_ID>",     // per-merchant, DE 41
  "SecurityControlInformation": "<SCI>"              // DE 53 — secret, never expose
}
Every request

Message envelope

Every transaction uses the same outer shape. A few details that aren’t obvious from the spec PDFs:

  • MessageType is the 3-digit form ("200"), not the canonical ISO four-digit MTI. "0200" returns a generic ErrorCode: 100000 / "Server error" without ever reaching the ISO layer.
  • LocalTransactionDate and ExpirationDate need slashes on input (MM/DD, YY/MM). The gateway echoes back the no-slash form in responses but rejects unsigned input.
  • TransactionAmount is 12 digits, zero-padded, in cents (minor units). "000000000400" = $4.00.
  • SystemsTraceNumber (STAN) must be unique per terminal per business day. Reversals match on (terminal, STAN), so keep it stable across retries of the same intent.
  • Most optional/conditional fields live inside RequestDataElements — Cygma calls this the “Private Use” data element.
FieldTypeDescription
MessageTyperequiredstring3-digit MTI (e.g. '200') — not the 4-digit ISO form.
ProcessingCoderequiredstring6 digits; selects the transaction type.
TransactionAmountrequiredstring12 digits, zero-padded, in cents. '000000000400' = $4.00.
LocalTransactionTimerequiredstringLocal time as hhmmss.
LocalTransactionDaterequiredstringMM/DD, with a slash on input.
SystemsTraceNumberrequiredstring6-digit STAN, unique per terminal per business day.
ExpirationDaterequiredstringCard expiry as YY/MM, with a slash on input.
POSEntryModerequiredstringHow the card was captured (DE 22).
POSConditionCoderequiredstringPOS condition (DE 25).
TerminalIdrequiredstringPer-merchant terminal id (DE 41), assigned by EPI.
CardAcquirerIdrequiredstringAcquirer / parent merchant number (DE 42), assigned by EPI.
SecurityControlInformationrequiredstringPer-terminal secret (DE 53). Treat like a bearer token.
PrimaryAccountNumberrequiredstringCard number (PAN). Omit when sending track or token data.
RequestDataElementsoptionalobjectOptional / conditional fields — Cygma's 'Private Use' element.
RequestDataElements.InvoiceReferenceNumberoptionalstringYour order / invoice reference.
RequestDataElements.HardwareVendorIdentifieroptionalstringTerminal hardware vendor id.
RequestDataElements.SoftwareIdentifieroptionalstringIntegration software id.
RequestDataElements.CardTypeoptionalstringCard type hint (e.g. 'CR').
RequestDataElements.ElectronicCommerceIndicatoroptionalstringECI for CNP transactions (e.g. '07').
{
  "MessageType": "200",                            // 3-digit MTI
  "ProcessingCode": "000000",                      // 6 digits
  "TransactionAmount": "000000000400",             // 12 digits, cents
  "LocalTransactionTime": "103200",                // hhmmss
  "LocalTransactionDate": "04/09",                 // MM/DD with slash
  "SystemsTraceNumber": "599220",                  // 6-digit STAN
  "ExpirationDate": "31/12",                       // YY/MM with slash
  "POSEntryMode": "010",                           // see DE 22
  "POSConditionCode": "59",                        // see DE 25
  "TerminalId": "<TERMINAL_ID>",
  "CardAcquirerId": "<YOUR_CARD_ACQUIRER_ID>",
  "SecurityControlInformation": "<SCI>",
  "PrimaryAccountNumber": "4005529091234562",
  "RequestDataElements": {
    "InvoiceReferenceNumber":     "ORDER-1042",
    "HardwareVendorIdentifier":   "F150",
    "SoftwareIdentifier":         "1013",
    "CardType":                   "CR",
    "ElectronicCommerceIndicator": "07"
  }
}
Qualify the transaction right

POS entry + condition codes

The (POSEntryMode, POSConditionCode) pair tells Cygma how the card data got into the terminal. Picking the right pair matters — it drives interchange qualification, chargeback liability, and (for some processors) whether the transaction downgrades to a higher-cost tier.

ScenarioPOSEntryModePOSConditionCode
EMV chip read05100
Contactless / Apple Pay / Google Pay07100
Swipe (EMV fallback)80100
Key-entered, card present01171
Key-entered, CNP (MOTO / e-commerce)01208

Misclassifying a CNP transaction as card-present (or vice versa) can cost the merchant interchange and expose them to chargebacks they wouldn’t otherwise face. When in doubt, pick the more conservative classification.

// EMV chip read
{ "POSEntryMode": "051", "POSConditionCode": "00" }

// Contactless (Apple/Google Pay, tap)
{ "POSEntryMode": "071", "POSConditionCode": "00" }

// Magstripe swipe — EMV fallback
{ "POSEntryMode": "801", "POSConditionCode": "00" }

// Key-entered, card + cardholder present
{ "POSEntryMode": "011", "POSConditionCode": "71" }

// Key-entered, cardholder NOT present (MOTO / e-commerce)
{ "POSEntryMode": "012", "POSConditionCode": "08" }
one URL per transaction

Transaction types

Every transaction type has its own dedicated URL under /hostapi/ — the JSON envelope is the same shape everywhere; only the URL (and a couple of type-specific fields) changes. The everyday set is documented in depth below; the rest are listed here so you know the full vocabulary.

PathWhat it does
/saledebitebt/The everyday sale: authorize and store in the terminal batch for end-of-day settlement. Chip, tap, swipe, or keyed.
/mailorder/Card-not-present sale — e-commerce and phone orders. Same envelope, CNP condition coding and ECI.
/preauthorization/ + /completion/Hold now, capture later. Pre-auth places the hold (fuel, hospitality, bar tabs); Completion settles it once the final amount is known — tips included.
/authorization/An 0100 auth-only: obtain an approval that is NOT stored in the batch. Follow with an advice at fulfilment, or a reversal to release the hold if the order can’t ship.
/onlinerefund/Refund a cardholder when the original isn’t in the open batch — host-approved before the terminal journals it.
/offlinerefund/ · /offlinesale/Offline entries: refunds processed off-line, tip-adjusted completions, and voice-authorized (IVR) sales pushed into the batch after the fact.
/reversalfull/Void / timeout reversal. Sent when no valid auth response arrived in 35–45s (retry up to 3×), or to undo a same-batch transaction.
/reversalpartial/Partial reversal — release the difference when the final sale is less than the authorized amount (lodging, fuel).
/adjustsale/ · /adjustcredit/Adjustments — notify the host the amount of a prior sale/credit changed (tip adjust being the classic).
/cardverification/Zero-dollar verification — confirm a card is live (AVS/CVV probe) before you know the real amount.
/balanceinquiry/ · /availablefunds/Balance lookups — gift/debit card balance, or available funds on a debit/credit card.
/cashadvance/ · /salecash/Cash transactions — cash advance against a card’s cash limit; cash-back sales.
/payment/Payment (bill-pay style credit to a card account).
/settlementrequest/ · /settlementtrailer/ · /batchupload/End of day. Settlement Request compares terminal vs host totals; on mismatch (code 95) the terminal uploads each capture via Batch Upload, then closes with Settlement Trailer.
POST /hostapi/saledebitebt/

Sale

MTI 200, ProcessingCode 000000. The canonical card-present sale — terminal swipes / chip-reads / key-enters, terminal sends, gateway authorizes, transaction is stored in the terminal batch for end-of-day settlement.

Three ways to present the card (send exactly one): a keyed PrimaryAccountNumber + ExpirationDate(shown here), raw magstripe Track1Data / Track2Data (DE 45 / DE 35), or an EMV chip/contactless read via ICCSystemRelatedData (DE 55) — see Card entry: MSR / EMV for the track + EMV payload shapes and the matching POSEntryMode values.

For cardholder-not-present (e-commerce, MOTO), use /hostapi/mailorder/ instead — it sets POSConditionCode: "59" and expects ElectronicCommerceIndicator.

Read AuthorizationIdResponse — that’s the auth code you store on your order for future refund / reversal lookups.

Sending CVV2RequestValue or the billing ZipCode / Address? Those aren’t free-form — see CVV2 verification and AVS verification for the exact field formats and the result-code tables.

FieldTypeDescription
MessageTyperequiredstringAlways '200' for a sale.
ProcessingCoderequiredstring'000000' — debit sale.
PrimaryAccountNumberrequiredstringCard number (PAN).
RequestDataElements.InvoiceReferenceNumberoptionalstringYour order reference, stored on the transaction.
RequestDataElements.ZipCodeoptionalstringBilling ZIP — its presence triggers AVS. See AVS verification.
RequestDataElements.CVV2RequestValueoptionalstring6-character CVV2 control field. See CVV2 verification.
curl -X POST https://api.cygma.com:443/hostapi/saledebitebt/ \
  -H "Content-Type: application/json" \
  -d '{
  "MessageType": "200",
  "ProcessingCode": "000000",
  "TransactionAmount": "000000001500",
  "LocalTransactionTime": "144530",
  "LocalTransactionDate": "06/09",
  "SystemsTraceNumber": "000123",
  "ExpirationDate": "30/12",
  "POSEntryMode": "011",
  "POSConditionCode": "71",
  "TerminalId": "<TERMINAL_ID>",
  "CardAcquirerId": "<YOUR_CARD_ACQUIRER_ID>",
  "SecurityControlInformation": "<SCI>",
  "PrimaryAccountNumber": "4242424242424242",
  "RequestDataElements": {
    "InvoiceReferenceNumber": "ORD-1042",
    "ZipCode": "83702",
    "CVV2RequestValue": "11123 "
  }
}'
POST /hostapi/mailorder/

Mail order (CNP)

The CNP / e-commerce / MOTO equivalent of Sale. Same response shape — only the URL and POSConditionCode change. AVS happens off ZipCode + Address; the CVV2 result comes back in ResponseDataElements.CVV2ResultCode.

FieldTypeDescription
MessageTyperequiredstringAlways '200'.
ProcessingCoderequiredstring'000000'.
POSEntryModerequiredstring'012' — key-entered, cardholder not present.
POSConditionCoderequiredstringCNP / MOTO condition code (e.g. '08').
PrimaryAccountNumberrequiredstringCard number (PAN).
RequestDataElements.ElectronicCommerceIndicatorrequiredstringECI for the channel (e.g. '01', or '07' for a TLS-secured channel).
RequestDataElements.CardHolderFirstNameoptionalstringCardholder first name.
RequestDataElements.CardHolderLastNameoptionalstringCardholder last name.
RequestDataElements.ZipCodeoptionalstringBilling ZIP for AVS.
RequestDataElements.AddressoptionalstringBilling street for AVS.
RequestDataElements.CVV2RequestValueoptionalstring6-character CVV2 control field.
curl -X POST https://api.cygma.com:443/hostapi/mailorder/ \
  -H "Content-Type: application/json" \
  -d '{
  "MessageType": "200",
  "ProcessingCode": "000000",
  "TransactionAmount": "000000002500",
  "LocalTransactionTime": "094500",
  "LocalTransactionDate": "06/09",
  "SystemsTraceNumber": "000456",
  "ExpirationDate": "30/12",
  "POSEntryMode": "012",
  "POSConditionCode": "08",
  "TerminalId": "<TERMINAL_ID>",
  "CardAcquirerId": "<YOUR_CARD_ACQUIRER_ID>",
  "SecurityControlInformation": "<SCI>",
  "PrimaryAccountNumber": "4242424242424242",
  "RequestDataElements": {
    "ElectronicCommerceIndicator": "01",
    "CardHolderFirstName": "Jane",
    "CardHolderLastName": "Doe",
    "ZipCode": "83702",
    "Address": "412 W State St",
    "CVV2RequestValue": "11123 "
  }
}'
POST /hostapi/preauthorization/ → /completion/

Pre-auth + completion

Reserve funds at one amount, then capture at the final amount when you know it. Common for hospitality (hold $200, charge $187 on checkout), fuel (hold $125 at the pump, charge actual fill), or staged shipments.

POST/hostapi/preauthorization/Step 1 — reserve funds at the high-water amount. MTI 100.
POST/hostapi/completion/Step 2 — capture at the final amount after fulfillment. MTI 220; references the pre-auth via OriginalSystemTraceAuditNumber + AuthorizationIdResponse.

The completion needs to reference the pre-auth’s STAN + auth code so the gateway can match.

FieldTypeDescription
MessageTyperequiredstring'100' for the pre-auth; '220' for the completion.
ProcessingCoderequiredstring'000000'.
TransactionAmountrequiredstringPre-auth: the high-water hold amount. Completion: the final captured amount. 12-digit cents.
AuthorizationIdResponserequiredstringAuth code from the pre-auth; required on the completion to match.
RequestDataElements.OriginalSystemTraceAuditNumberrequiredstringSTAN of the original pre-auth (completion only).
RequestDataElements.OriginalTransactionDateoptionalstringDate of the original pre-auth as MMDD (completion only).
curl -X POST https://api.cygma.com:443/hostapi/completion/ \
  -H "Content-Type: application/json" \
  -d '{
  "MessageType": "220",
  "ProcessingCode": "000000",
  "TransactionAmount": "000000008500",
  "AuthorizationIdResponse": "234562",
  "RequestDataElements": {
    "OriginalSystemTraceAuditNumber": "000123",
    "OriginalTransactionDate": "0609"
  }
}'
POST /hostapi/onlinerefund/

Refund

Credit funds back to the cardholder. Same envelope as Sale — the only difference is ProcessingCode 200000 (credit rather than debit). MTI is still 200.

POST/hostapi/onlinerefund/Refund when the original transaction is in a different batch (the common case). MTI 200.
POST/hostapi/offlinerefund/Refund within the same batch as the original. MTI 220.
FieldTypeDescription
MessageTyperequiredstringStill '200' — same as a sale.
ProcessingCoderequiredstring'200000' — credit rather than debit.
TransactionAmountrequiredstringAmount to credit back, 12-digit cents.
PrimaryAccountNumberrequiredstringCard to refund.
curl -X POST https://api.cygma.com:443/hostapi/onlinerefund/ \
  -H "Content-Type: application/json" \
  -d '{
  "MessageType": "200",
  "ProcessingCode": "200000",
  "TransactionAmount": "000000001500",
  "LocalTransactionTime": "151230",
  "LocalTransactionDate": "06/09",
  "SystemsTraceNumber": "000789",
  "ExpirationDate": "30/12",
  "POSEntryMode": "010",
  "POSConditionCode": "00",
  "TerminalId": "<TERMINAL_ID>",
  "CardAcquirerId": "<YOUR_CARD_ACQUIRER_ID>",
  "SecurityControlInformation": "<SCI>",
  "PrimaryAccountNumber": "4242424242424242"
}'
POST /hostapi/reversalfull/ · /reversalpartial/

Reversal (void)

Reverses an authorized transaction. Send within the same business day for a clean void; after end-of-day, use Refund instead.

POST/hostapi/reversalfull/Full reversal — voids the whole sale. MTI 400.
POST/hostapi/reversalpartial/Partial reversal — releases the difference between an authorization and the final captured amount. Common in lodging. MTI 420.

Reversals match the original on (TerminalId, STAN). Per spec, retry up to three times if the gateway returns “please retry”.

FieldTypeDescription
MessageTyperequiredstring'400' full reversal; '420' partial reversal.
ProcessingCoderequiredstring'000000'.
TransactionAmountrequiredstringAmount to reverse, 12-digit cents.
AuthorizationIdResponserequiredstringAuth code of the original transaction.
RequestDataElements.OriginalSystemTraceAuditNumberrequiredstringSTAN of the original — reversals match on (TerminalId, STAN).
RequestDataElements.OriginalMessageTyperequiredstringMTI of the original transaction (e.g. '200').
RequestDataElements.OriginalAmountrequiredstringOriginal transaction amount, 12-digit cents.
curl -X POST https://api.cygma.com:443/hostapi/reversalfull/ \
  -H "Content-Type: application/json" \
  -d '{
  "MessageType": "400",
  "ProcessingCode": "000000",
  "TransactionAmount": "000000001500",
  "AuthorizationIdResponse": "234562",
  "RequestDataElements": {
    "OriginalSystemTraceAuditNumber": "000123",
    "OriginalMessageType": "200",
    "OriginalAmount": "000000001500"
  }
}'
POST /hostapi/balanceinquiry/

Balance inquiry

Inquires the available balance on a card. No transaction is recorded — pure read.

FieldTypeDescription
MessageTyperequiredstring'100'.
ProcessingCoderequiredstring'310000' — balance inquiry.
TransactionAmountrequiredstringAll zeros — read-only, no amount is moved.
PrimaryAccountNumberrequiredstringCard to inquire.
curl -X POST https://api.cygma.com:443/hostapi/balanceinquiry/ \
  -H "Content-Type: application/json" \
  -d '{
  "MessageType": "100",
  "ProcessingCode": "310000",
  "TransactionAmount": "000000000000",
  "PrimaryAccountNumber": "<CARD>",
  "TerminalId": "<TERMINAL_ID>",
  "CardAcquirerId": "<YOUR_CARD_ACQUIRER_ID>",
  "SecurityControlInformation": "<SCI>"
}'
POST /hostapi/cardverification/

Card verification

Probes the card without authorizing an amount. Use when you want to verify a card-on-file is still valid before billing — common for recurring subscriptions and stored-card vault enrollment.

FieldTypeDescription
MessageTyperequiredstring'100'.
ProcessingCoderequiredstring'000000'.
TransactionAmountrequiredstringAll zeros — zero-dollar probe, no hold is placed.
PrimaryAccountNumberrequiredstringCard to verify.
ExpirationDaterequiredstringCard expiry as YY/MM, with a slash.
RequestDataElements.ZipCodeoptionalstringBilling ZIP for AVS.
RequestDataElements.CVV2RequestValueoptionalstring6-character CVV2 control field.
curl -X POST https://api.cygma.com:443/hostapi/cardverification/ \
  -H "Content-Type: application/json" \
  -d '{
  "MessageType": "100",
  "ProcessingCode": "000000",
  "TransactionAmount": "000000000000",
  "PrimaryAccountNumber": "<CARD>",
  "ExpirationDate": "30/12",
  "RequestDataElements": {
    "ZipCode": "83702",
    "CVV2RequestValue": "11123 "
  }
}'
DE 61

Tokenization

The production tokenization path is a dedicated service, separate from this ISO-8583 gateway: a mutual-TLS API (tokenize / detokenize) that swaps a PAN for an opaque token and back. Tokenize the PAN once, store only the token, and at void / refund / recurring time detokenize just long enough to build the packet — the PAN never rests on your servers. It requires a client certificate (mTLS) and an IP allow-list, so it’s an EPI-operated integration rather than a public endpoint; ask EPI to enable it.

The Token then rides TokenizationElements.Token (tag T03) on the gateway call in place of PrimaryAccountNumber — one of PAN / track / token / encrypted-PAN is required on every authorization, financial, and reversal message, and the token satisfies that.

Note — inline indicator (unverified). The reference guide also documents an inline option: TokenizationElements.TokenIndicator: "R"on the sale to have the host return a token in the response. EPI has not certified this against live Cygma (the spec marks the standalone tokenization transaction “reserved for future use”), so the dedicated service above is the path we use.

FieldTypeDescription
TokenizationElements.TokenoptionalstringToken (T03, 13–19 digits) standing in for the PAN; send in place of PrimaryAccountNumber on voids, refunds, and recurring charges.
TokenizationElements.TokenRequestorIdoptionalstringTRID (T07) — 11-digit token requestor identifier, echoed on token responses.
TokenizationElements.TokenIndicatoroptionalstringInline option (unverified): 'R' on a sale/auth request asks the host to return a token in the response. Prefer the dedicated tokenize service.
// Dedicated tokenize service (mutual-TLS) — swap PAN <-> token.
// POST https://cygtoken.cygma.com:9454/api/v1/tokenize
{ "pan": "4242424242424242", "create": true }
//   -> { "token": "545721XXXXXXXX14" }

// POST https://cygtoken.cygma.com:9454/api/v1/detokenize
{ "token": "545721XXXXXXXX14" }
//   -> { "pan": "4242424242424242" }

// Then on the gateway, send the Token (T03) in place of the PAN
// for a void / refund / recurring charge:
{
  "MessageType": "0200",
  "ProcessingCode": "000000",
  "TransactionAmount": "000000002500",
  "TokenizationElements": { "Token": "545721XXXXXXXX14" },
  ...
}
Commercial card interchange

Level II qualification

Submitting Level II data unlocks the commercial-card interchange rate when the cardholder pays with a business / corporate card. The savings vs the consumer rate can be 30-80 basis points per transaction — meaningful at any volume.

To qualify for Level II, include these fields inRequestDataElements:

FieldTypeDescription
RequestDataElements.MarketSpecificDataRequestrequiredstringA single space (' '). The presence of the field is the signal; the content does not matter.
RequestDataElements.RequestedACIrequiredstringAlways 'Y'.
RequestDataElements.CommercialRequestIndicatorrequiredstring'1' requests commercial-card qualification.
RequestDataElements.CustomerCoderequiredstringLast 4 digits of the card, or any merchant-supplied alphanumeric (max 17 chars).
RequestDataElements.SalesTaxAmountrequiredstringImplied decimal at position 2. '150' = $1.50.
RequestDataElements.SalesTaxCollectedIndicatorrequiredstring'1' = tax was collected.
RequestDataElements.InvoiceERCReferenceNumberrequiredstringTransaction number in the batch (1, 2, 3...).
RequestDataElements.MerchantOrderCustomerReferenceNumberrequiredstringSame value as InvoiceERCReferenceNumber.
RequestDataElements.OrderDaterequiredstringOrder date as YYMMDD (today).

For key-entered card-present (POSEntryMode 011): also include ZipCode, Address, CVV2RequestValue.

For CNP / MOTO (POSEntryMode 012): include all the above PLUS ElectronicCommerceIndicator: "01" — using any other ECI for a CNP transaction will downgrade.

// Add these to RequestDataElements on every transaction
// you want to qualify at Level II:

{
  "RequestDataElements": {
    "MarketSpecificDataRequest":             " ",
    "RequestedACI":                          "Y",
    "CommercialRequestIndicator":            "1",
    "CustomerCode":                          "4242",
    "SalesTaxAmount":                        "150",
    "SalesTaxCollectedIndicator":            "1",
    "InvoiceERCReferenceNumber":             "1",
    "MerchantOrderCustomerReferenceNumber":  "1",
    "OrderDate":                             "260609"
  }
}
Line-item detail · biggest interchange savings

Level III qualification

Level III unlocks the lowest commercial-card interchange tier — typically another 30-60 basis points off Level II. The cost is verbosity: you need to send line-item detail (qty, unit cost, commodity code, unit of measure, …) for every product on the order.

Cygma packs Level III line items into a single TLV (tag, length, value) string atRequestDataElements.Level3Items1. Each tag isL<number>, immediately followed by a 4-digit length, then the value — concatenated with no separators.

Required tags per line item: L12 (item count),L21 (order date), L30 (commodity code),L31 (product code), L32 (description),L33 (quantity), L34 (unit of measure),L35 (extended amount), L40 (unit cost),L45 (shipping method).

The complete TLV reference is in the Cygma API Developer Guide (RequestDataElements → Level III Line Items table). For most single-line orders, the sample above is a working starting point — copy it, swap in real values, ship.

FieldTypeDescription
RequestDataElements.Level3Items1requiredstringPacked TLV string of line-item detail (tag + 4-digit length + value, concatenated). See the tag breakdown above.
// Level III packs line-item detail into a single TLV
// (tag, length, value) string per item, on Level3Items1.
// Submit ALONGSIDE the Level II fields above.

{
  "RequestDataElements": {
    // ...all Level II fields above...
    "Level3Items1": "L1200011L150012000000000000L1800100000011963L1900100000011963L200015000000000000001L210006260609L240015000000000000001L3000100000000578L310007DEFAULTL320007DEFAULTL330009000000001L340002EAL350012000000001000L370012000000000000L380001NL390001NL400012000000001000L41000500000L4200040000L430012000000000000L45000201"
  }
}
DE 54 AdditionalAmounts

Surcharge / Tip / Cashback

Surcharge, tip, and cashback amounts ride in theAdditionalAmounts field (DE 54). Two rules:

  • The first two digits of ProcessingCode change to "09".
  • TransactionAmount is the total — base + surcharge + tip + cashback.
FieldTypeDescription
ProcessingCoderequiredstringFirst two digits become '09' when additional amounts are present (e.g. '090000').
TransactionAmountrequiredstringThe total: base + surcharge + tip + cashback. 12-digit cents.
AdditionalAmountsrequiredstring20-character positional string (DE 54). Layout below.

AdditionalAmounts is a 20-character positional string:

PosFieldValue
1-2Account type"00" default account
3-4Amount type"42" surcharge · "43" tip · "40" cashback
5-7Currency code"840" (USD)
8Credit / debit"C" credit · "D" debit
9-20Amount (cents)12 digits, zero-padded. "000000000500" = $5.00

Surcharge rules: max 3% of the base amount. Your terminal must vet the BIN before applying — if you send a surcharge on a debit card BIN, Cygma returns response code 97“Surcharge Not Permitted”. Re-prompt the user and resubmit without the surcharge.

Cashback is only valid on debit / EBT (the "D"credit-debit flag); never on credit. Surcharge is only valid on credit ("C"); never on debit.

// $171.66 sale with a $5.00 surcharge built-in.
// ProcessingCode first two digits become "09".
// TransactionAmount includes the surcharge.

{
  "MessageType":        "200",
  "ProcessingCode":     "090000",
  "TransactionAmount":  "000000017166",
  "AdditionalAmounts":  "0042840C000000000500",
  ...
}

// After the customer adds a $35 tip on the receipt, send a
// Tip Adjust with the new total:

{
  "TransactionAmount":  "000000020666",
  "AdditionalAmounts":  "0043840C000000003500",
  ...
}

// $30 debit sale + $10 cashback request:

{
  "TransactionAmount":  "000000003000",
  "AdditionalAmounts":  "0040840D000000001000",
  ...
}
RequestDataElements.CVV2RequestValue · result DE63.A42

CVV2 verification

Send the card security code in CVV2RequestValue inside RequestDataElements. It is not a bare 3–4 digit value — it is a fixed 6-character field. Sending the raw code (e.g. "123") is the classic 30 Format error on Mastercard and a suspect-fraud decline on Visa.

FieldTypeDescription
RequestDataElements.CVV2RequestValueoptionalstringFixed 6-character control field: presence flag + response-request flag + the code, left-justified and space-padded. Layout below.
PositionMeaningValues
1Presence1 = code was sent · 0 = not provided
2Response request1 = return the response code and the CVV2 result code
3–6The code3-digit CVV2 (Visa / MC / Discover) or 4-digit CID (Amex), left-justified and space-padded to 4. All four spaces when not provided.

The issuer's answer is normalized into CVV2ResultCode (DE63.A42):

CodeMeaning
MMatch
NNo match
PNot processed
SMerchant flagged the code as not present, but it should be on the card
UIssuer not certified / unable to verify

The digit length and card-brand name for the code differ by network, but every brand normalizes to the same M/N/P/S/U result above. The raw per-network code is echoed in CVV2NetworkResultCode.

Card typeNameLengthNormalized result
VisaCVV23M / N / P / S / U
MastercardCVC23M / N / P / S / U
DiscoverCID3M / N / P / S / U
American ExpressCID4M / N / P / S / U
// CVV2RequestValue is a fixed 6-character control field — NOT a bare code:
//   pos 1     presence   "1" = sent · "0" = not provided
//   pos 2     response   "1" = return the response code AND the CVV2 result
//   pos 3-6   the code   left-justified, space-filled to 4

// Visa / Mastercard / Discover — 3-digit CVV2 "123"
"RequestDataElements": { "CVV2RequestValue": "11123 " }

// American Express — 4-digit CID "1234"
"RequestDataElements": { "CVV2RequestValue": "111234" }

// Not on hand — presence 0, still ask for the result
"RequestDataElements": { "CVV2RequestValue": "01    " }

// Result comes back normalized in CVV2ResultCode (DE63.A42):
"ResponseDataElements": { "CVV2ResultCode": "M" }
RequestDataElements.ZipCode + .Address · result AVSResponseCode

AVS verification

Submit the cardholder's billing ZIP in ZipCode and the street in Address, inside RequestDataElements. AVS runs whenever either is present — there is no explicit AVS request flag to set. The issuer's answer returns in AVSResponseCode; the raw per-network code is in AVSNetworkResultCode.

FieldTypeDescription
RequestDataElements.ZipCodeoptionalstringCardholder billing ZIP. AVS runs whenever this is present.
RequestDataElements.AddressoptionalstringCardholder billing street. AVS runs whenever this is present.
CodeMeaning
MAddress + ZIP match
YAddress + ZIP (5-digit) match
XAddress + ZIP (9-digit) match
AAddress matches, ZIP does not
ZZIP matches, address does not
WZIP (9-digit) matches, address does not
PPostal matches, street not verified
BStreet matches (intl), postal not verified
DAddress + ZIP match (intl)
NNo match — neither address nor ZIP
IAddress not verified (intl)
GNon-U.S. issuer, not verified
RRetry — issuer system unavailable
SAVS not supported by issuer
UAddress information unavailable
EAVS error / not allowed for this transaction
// AVS is triggered by the PRESENCE of the billing fields — there is
// no separate AVS request flag. Send either or both:
"RequestDataElements": {
  "ZipCode": "90210",
  "Address": "1 Market St"
}

// The issuer's answer comes back in AVSResponseCode
// (raw per-network code echoed in AVSNetworkResultCode):
"ResponseDataElements": {
  "AVSResponseCode": "Y",
  "AVSNetworkResultCode": "..."
}
POST /hostapi/settlementrequest/

Batch settlement

Cygma settles by batch. At end of business day, post your own tallied totals to /hostapi/settlementrequest/MTI 500, ProcessingCode 920000. Cygma compares them against what it captured during the day.

POST/hostapi/settlementrequest/Reconcile — post your tallied batch totals. MTI 500, ProcessingCode 920000. 00 = settled; 95 = mismatch.
POST/hostapi/batchupload/On a 95: upload each captured transaction, one POST per capture. MTI 320, same BatchNumber.
POST/hostapi/settlementtrailer/Close — re-send the trailer with the same totals as step 1. MTI 500.
  • ResponseCode 00 — totals match; the batch is closed. Done.
  • ResponseCode 95totals mismatch. Cygma's tape and yours disagree, so it needs the itemized batch to reconcile.

On a 95, run the two-step upload flow:

  1. Post one /hostapi/batchupload/ (MTI 320) for each captured transaction in the batch — the same BatchNumber, echoing the original SystemsTraceNumber, ProcessingCode, TransactionAmount, and AuthorizationIdResponse.
  2. Close with /hostapi/settlementtrailer/ (MTI 500) carrying the same count/amount totals you sent in step 1. Once the uploaded items add up to the trailer, Cygma returns 00 and the batch settles.

The totals block is four count / amount pairs. Amounts are 12-digit, zero-padded, in minor units (cents):

FieldTypeDescription
RequestDataElements.CreditCardSalesCountrequiredstringNumber of credit sales in the batch.
RequestDataElements.CreditCardSalesAmountrequiredstringTotal credit sales, 12-digit cents.
RequestDataElements.CreditCardRefundCountrequiredstringNumber of credit refunds.
RequestDataElements.CreditCardRefundAmountrequiredstringTotal credit refunds, 12-digit cents.
RequestDataElements.DebitCardSalesCountoptionalstringNumber of debit sales.
RequestDataElements.DebitCardSalesAmountoptionalstringTotal debit sales, 12-digit cents.
RequestDataElements.DebitCardRefundCountoptionalstringNumber of debit refunds.
RequestDataElements.DebitCardRefundAmountoptionalstringTotal debit refunds, 12-digit cents.
curl -X POST https://api.cygma.com:443/hostapi/settlementrequest/ \
  -H "Content-Type: application/json" \
  -d '{
  "MessageType": "500",
  "ProcessingCode": "920000",
  "TransactionAmount": "000000000000",
  "SystemsTraceNumber": "000999",
  "TerminalId": "<TERMINAL_ID>",
  "CardAcquirerId": "<YOUR_CARD_ACQUIRER_ID>",
  "SecurityControlInformation": "<SCI>",
  "RequestDataElements": {
    "CreditCardSalesCount": "12",
    "CreditCardSalesAmount": "000000054200",
    "CreditCardRefundCount": "1",
    "CreditCardRefundAmount": "000000002500"
  }
}'
DE 39

Response codes

ResponseCode follows the ISO 8583 standard — "00"is the only happy path. "10" (partial approval) is conditionally happy: Cygma approved a smaller amount than you requested; you ll find the approved amount in TransactionAmount on the response.

On a non-approved response, ResponseDataElements.AlternteResponseText(note the typo in the spec) often carries a human-readable message from the issuer.

// "00" = approved. Everything else needs handling.
// The full table lives in src/lib/external/cygma/codes.ts.

00  Approved
10  Partial approval
05  Do not honor
12  Invalid transaction
13  Invalid amount
14  Invalid card number
30  Format error
51  Insufficient funds
54  Expired card
55  Incorrect PIN
57  Transaction not permitted to cardholder
61  Exceeds withdrawal limit
78  Invalid CVV2
91  Issuer or switch inoperative
94  Duplicate transaction
95  Reconcile error (run Batch Upload)
99  Unknown error
POS terminals · mobile SDKs

Card entry: MSR / EMV

For POS terminals with a magstripe reader (or an EMV reader falling back to magstripe), pass the raw track data inTrack1Data / Track2Data and update POSEntryMode:

  • 010 manual / key-entered (default)
  • 021 Track 2 read
  • 022 Track 1 read
  • 050 EMV chip
  • 070 contactless
  • 090 EMV fallback to magstripe

EMV chip & contactless — for chip reads (dip or tap), send the ICC TLV payload from the kernel in ICCSystemRelatedData (DE 55) as a hex string, alongside the chip’s track-2 image in Track2Data, with POSEntryMode 050 (contact), 070 (contactless) or 090 (fallback to magstripe). Devices that encrypt at the read head (DUKPT / P2PE) also send the KeySerialNumber in RequestDataElements; end-to-end P2PE envelopes ride DE 60 (P2PE). PIN debit adds PINData (DE 52) + SecurityControlInformation (DE 53).

FieldTypeDescription
Track2DataoptionalstringRaw track 2 data from a swipe (or the chip's track-2 image on an EMV read). Omit PrimaryAccountNumber when this is sent.
Track1DataoptionalstringRaw track 1 data (DE 45; less common — some MSRs send both).
ICCSystemRelatedDataoptionalstringDE 55 — the EMV ICC TLV payload (hex) from the chip kernel. Required for chip / contactless reads.
RequestDataElements.KeySerialNumberoptionalstring3DES DUKPT Key Serial Number when the capture device encrypts (P2PE).
POSEntryModerequiredstringSet to match the read type: '021' track 2, '022' track 1, '050' EMV chip, '070' contactless, '090' EMV fallback.
// Track 2 swipe — pass the raw track, omit PAN.
{
  "MessageType": "200",
  "Track2Data": ";4242424242424242=29121011000000?",
  "POSEntryMode": "021",
  ...
}

// Track 1 — less common; some MSRs send both.
{
  "MessageType": "200",
  "Track1Data": "%B4242424242424242^DOE/JANE^29121010000?",
  "POSEntryMode": "022",
  ...
}

// EMV chip / contactless — DE 55 TLV (hex) + the chip's
// track-2 image; DUKPT KSN when the reader encrypts.
{
  "MessageType": "200",
  "ICCSystemRelatedData": "9F2608AB12…5F340101",
  "Track2Data": ";4242424242424242=29121011000000?",
  "POSEntryMode": "050",
  "RequestDataElements": {
    "KeySerialNumber": "FFFF9876543210E00001",
    ...
  },
  ...
}
apicert-sandbox.cygma.com:9443

Sandbox

Cert sandbox lives at apicert-sandbox.cygma.com:9443(the same port as production, just a different host). Same JSON shape, same response codes, no real money moves. Ask EPI for sandbox credentials.

# The same shape works in cert as in production — flip the host.
curl -X POST https://apicert-sandbox.cygma.com:9443/hostapi/saledebitebt/ \
  -H "Content-Type: application/json" -H "Api-Version: 1" \
  -d '{ "MessageType": "200", ... }'

# Approved test card (echo only — never reaches issuers):
#   PAN:        4005529091234562
#   Expiration: 31/12  (Dec 2031)