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.
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
Quickstart
- Get your
CardAcquirerId,TerminalId, andSecurityControlInformationfrom EPI. The first two go in plaintext per request; the third is the auth secret. - POST your transaction JSON to the matching URL (one URL per transaction type — see Sale below for the canonical shape).
- Read
ResponseCodefrom 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>"
}'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
}Message envelope
Every transaction uses the same outer shape. A few details that aren’t obvious from the spec PDFs:
MessageTypeis the 3-digit form ("200"), not the canonical ISO four-digit MTI."0200"returns a genericErrorCode: 100000 / "Server error"without ever reaching the ISO layer.LocalTransactionDateandExpirationDateneed slashes on input (MM/DD,YY/MM). The gateway echoes back the no-slash form in responses but rejects unsigned input.TransactionAmountis 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.
| Field | Type | Description |
|---|---|---|
MessageTyperequired | string | 3-digit MTI (e.g. '200') — not the 4-digit ISO form. |
ProcessingCoderequired | string | 6 digits; selects the transaction type. |
TransactionAmountrequired | string | 12 digits, zero-padded, in cents. '000000000400' = $4.00. |
LocalTransactionTimerequired | string | Local time as hhmmss. |
LocalTransactionDaterequired | string | MM/DD, with a slash on input. |
SystemsTraceNumberrequired | string | 6-digit STAN, unique per terminal per business day. |
ExpirationDaterequired | string | Card expiry as YY/MM, with a slash on input. |
POSEntryModerequired | string | How the card was captured (DE 22). |
POSConditionCoderequired | string | POS condition (DE 25). |
TerminalIdrequired | string | Per-merchant terminal id (DE 41), assigned by EPI. |
CardAcquirerIdrequired | string | Acquirer / parent merchant number (DE 42), assigned by EPI. |
SecurityControlInformationrequired | string | Per-terminal secret (DE 53). Treat like a bearer token. |
PrimaryAccountNumberrequired | string | Card number (PAN). Omit when sending track or token data. |
RequestDataElementsoptional | object | Optional / conditional fields — Cygma's 'Private Use' element. |
RequestDataElements.InvoiceReferenceNumberoptional | string | Your order / invoice reference. |
RequestDataElements.HardwareVendorIdentifieroptional | string | Terminal hardware vendor id. |
RequestDataElements.SoftwareIdentifieroptional | string | Integration software id. |
RequestDataElements.CardTypeoptional | string | Card type hint (e.g. 'CR'). |
RequestDataElements.ElectronicCommerceIndicatoroptional | string | ECI 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"
}
}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.
| Scenario | POSEntryMode | POSConditionCode |
|---|---|---|
| EMV chip read | 051 | 00 |
| Contactless / Apple Pay / Google Pay | 071 | 00 |
| Swipe (EMV fallback) | 801 | 00 |
| Key-entered, card present | 011 | 71 |
| Key-entered, CNP (MOTO / e-commerce) | 012 | 08 |
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" }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.
| Path | What 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. |
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.
| Field | Type | Description |
|---|---|---|
MessageTyperequired | string | Always '200' for a sale. |
ProcessingCoderequired | string | '000000' — debit sale. |
PrimaryAccountNumberrequired | string | Card number (PAN). |
RequestDataElements.InvoiceReferenceNumberoptional | string | Your order reference, stored on the transaction. |
RequestDataElements.ZipCodeoptional | string | Billing ZIP — its presence triggers AVS. See AVS verification. |
RequestDataElements.CVV2RequestValueoptional | string | 6-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 "
}
}'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.
| Field | Type | Description |
|---|---|---|
MessageTyperequired | string | Always '200'. |
ProcessingCoderequired | string | '000000'. |
POSEntryModerequired | string | '012' — key-entered, cardholder not present. |
POSConditionCoderequired | string | CNP / MOTO condition code (e.g. '08'). |
PrimaryAccountNumberrequired | string | Card number (PAN). |
RequestDataElements.ElectronicCommerceIndicatorrequired | string | ECI for the channel (e.g. '01', or '07' for a TLS-secured channel). |
RequestDataElements.CardHolderFirstNameoptional | string | Cardholder first name. |
RequestDataElements.CardHolderLastNameoptional | string | Cardholder last name. |
RequestDataElements.ZipCodeoptional | string | Billing ZIP for AVS. |
RequestDataElements.Addressoptional | string | Billing street for AVS. |
RequestDataElements.CVV2RequestValueoptional | string | 6-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 "
}
}'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.
/hostapi/preauthorization/Step 1 — reserve funds at the high-water amount. MTI 100./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.
| Field | Type | Description |
|---|---|---|
MessageTyperequired | string | '100' for the pre-auth; '220' for the completion. |
ProcessingCoderequired | string | '000000'. |
TransactionAmountrequired | string | Pre-auth: the high-water hold amount. Completion: the final captured amount. 12-digit cents. |
AuthorizationIdResponserequired | string | Auth code from the pre-auth; required on the completion to match. |
RequestDataElements.OriginalSystemTraceAuditNumberrequired | string | STAN of the original pre-auth (completion only). |
RequestDataElements.OriginalTransactionDateoptional | string | Date 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"
}
}'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.
/hostapi/onlinerefund/Refund when the original transaction is in a different batch (the common case). MTI 200./hostapi/offlinerefund/Refund within the same batch as the original. MTI 220.| Field | Type | Description |
|---|---|---|
MessageTyperequired | string | Still '200' — same as a sale. |
ProcessingCoderequired | string | '200000' — credit rather than debit. |
TransactionAmountrequired | string | Amount to credit back, 12-digit cents. |
PrimaryAccountNumberrequired | string | Card 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"
}'Reversal (void)
Reverses an authorized transaction. Send within the same business day for a clean void; after end-of-day, use Refund instead.
/hostapi/reversalfull/Full reversal — voids the whole sale. MTI 400./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”.
| Field | Type | Description |
|---|---|---|
MessageTyperequired | string | '400' full reversal; '420' partial reversal. |
ProcessingCoderequired | string | '000000'. |
TransactionAmountrequired | string | Amount to reverse, 12-digit cents. |
AuthorizationIdResponserequired | string | Auth code of the original transaction. |
RequestDataElements.OriginalSystemTraceAuditNumberrequired | string | STAN of the original — reversals match on (TerminalId, STAN). |
RequestDataElements.OriginalMessageTyperequired | string | MTI of the original transaction (e.g. '200'). |
RequestDataElements.OriginalAmountrequired | string | Original 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"
}
}'Balance inquiry
Inquires the available balance on a card. No transaction is recorded — pure read.
| Field | Type | Description |
|---|---|---|
MessageTyperequired | string | '100'. |
ProcessingCoderequired | string | '310000' — balance inquiry. |
TransactionAmountrequired | string | All zeros — read-only, no amount is moved. |
PrimaryAccountNumberrequired | string | Card 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>"
}'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.
| Field | Type | Description |
|---|---|---|
MessageTyperequired | string | '100'. |
ProcessingCoderequired | string | '000000'. |
TransactionAmountrequired | string | All zeros — zero-dollar probe, no hold is placed. |
PrimaryAccountNumberrequired | string | Card to verify. |
ExpirationDaterequired | string | Card expiry as YY/MM, with a slash. |
RequestDataElements.ZipCodeoptional | string | Billing ZIP for AVS. |
RequestDataElements.CVV2RequestValueoptional | string | 6-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 "
}
}'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.
| Field | Type | Description |
|---|---|---|
TokenizationElements.Tokenoptional | string | Token (T03, 13–19 digits) standing in for the PAN; send in place of PrimaryAccountNumber on voids, refunds, and recurring charges. |
TokenizationElements.TokenRequestorIdoptional | string | TRID (T07) — 11-digit token requestor identifier, echoed on token responses. |
TokenizationElements.TokenIndicatoroptional | string | Inline 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" },
...
}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:
| Field | Type | Description |
|---|---|---|
RequestDataElements.MarketSpecificDataRequestrequired | string | A single space (' '). The presence of the field is the signal; the content does not matter. |
RequestDataElements.RequestedACIrequired | string | Always 'Y'. |
RequestDataElements.CommercialRequestIndicatorrequired | string | '1' requests commercial-card qualification. |
RequestDataElements.CustomerCoderequired | string | Last 4 digits of the card, or any merchant-supplied alphanumeric (max 17 chars). |
RequestDataElements.SalesTaxAmountrequired | string | Implied decimal at position 2. '150' = $1.50. |
RequestDataElements.SalesTaxCollectedIndicatorrequired | string | '1' = tax was collected. |
RequestDataElements.InvoiceERCReferenceNumberrequired | string | Transaction number in the batch (1, 2, 3...). |
RequestDataElements.MerchantOrderCustomerReferenceNumberrequired | string | Same value as InvoiceERCReferenceNumber. |
RequestDataElements.OrderDaterequired | string | Order 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"
}
}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.
| Field | Type | Description |
|---|---|---|
RequestDataElements.Level3Items1required | string | Packed 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"
}
}Surcharge / Tip / Cashback
Surcharge, tip, and cashback amounts ride in theAdditionalAmounts field (DE 54). Two rules:
- The first two digits of
ProcessingCodechange to"09". TransactionAmountis the total — base + surcharge + tip + cashback.
| Field | Type | Description |
|---|---|---|
ProcessingCoderequired | string | First two digits become '09' when additional amounts are present (e.g. '090000'). |
TransactionAmountrequired | string | The total: base + surcharge + tip + cashback. 12-digit cents. |
AdditionalAmountsrequired | string | 20-character positional string (DE 54). Layout below. |
AdditionalAmounts is a 20-character positional string:
| Pos | Field | Value |
|---|---|---|
| 1-2 | Account type | "00" default account |
| 3-4 | Amount type | "42" surcharge · "43" tip · "40" cashback |
| 5-7 | Currency code | "840" (USD) |
| 8 | Credit / debit | "C" credit · "D" debit |
| 9-20 | Amount (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",
...
}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.
| Field | Type | Description |
|---|---|---|
RequestDataElements.CVV2RequestValueoptional | string | Fixed 6-character control field: presence flag + response-request flag + the code, left-justified and space-padded. Layout below. |
| Position | Meaning | Values |
|---|---|---|
| 1 | Presence | 1 = code was sent · 0 = not provided |
| 2 | Response request | 1 = return the response code and the CVV2 result code |
| 3–6 | The code | 3-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):
| Code | Meaning |
|---|---|
| M | Match |
| N | No match |
| P | Not processed |
| S | Merchant flagged the code as not present, but it should be on the card |
| U | Issuer 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 type | Name | Length | Normalized result |
|---|---|---|---|
| Visa | CVV2 | 3 | M / N / P / S / U |
| Mastercard | CVC2 | 3 | M / N / P / S / U |
| Discover | CID | 3 | M / N / P / S / U |
| American Express | CID | 4 | M / 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" }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.
| Field | Type | Description |
|---|---|---|
RequestDataElements.ZipCodeoptional | string | Cardholder billing ZIP. AVS runs whenever this is present. |
RequestDataElements.Addressoptional | string | Cardholder billing street. AVS runs whenever this is present. |
| Code | Meaning |
|---|---|
| M | Address + ZIP match |
| Y | Address + ZIP (5-digit) match |
| X | Address + ZIP (9-digit) match |
| A | Address matches, ZIP does not |
| Z | ZIP matches, address does not |
| W | ZIP (9-digit) matches, address does not |
| P | Postal matches, street not verified |
| B | Street matches (intl), postal not verified |
| D | Address + ZIP match (intl) |
| N | No match — neither address nor ZIP |
| I | Address not verified (intl) |
| G | Non-U.S. issuer, not verified |
| R | Retry — issuer system unavailable |
| S | AVS not supported by issuer |
| U | Address information unavailable |
| E | AVS 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": "..."
}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.
/hostapi/settlementrequest/Reconcile — post your tallied batch totals. MTI 500, ProcessingCode 920000. 00 = settled; 95 = mismatch./hostapi/batchupload/On a 95: upload each captured transaction, one POST per capture. MTI 320, same BatchNumber./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 95 — totals mismatch. Cygma's tape and yours disagree, so it needs the itemized batch to reconcile.
On a 95, run the two-step upload flow:
- Post one
/hostapi/batchupload/(MTI 320) for each captured transaction in the batch — the sameBatchNumber, echoing the originalSystemsTraceNumber,ProcessingCode,TransactionAmount, andAuthorizationIdResponse. - 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 returns00and the batch settles.
The totals block is four count / amount pairs. Amounts are 12-digit, zero-padded, in minor units (cents):
| Field | Type | Description |
|---|---|---|
RequestDataElements.CreditCardSalesCountrequired | string | Number of credit sales in the batch. |
RequestDataElements.CreditCardSalesAmountrequired | string | Total credit sales, 12-digit cents. |
RequestDataElements.CreditCardRefundCountrequired | string | Number of credit refunds. |
RequestDataElements.CreditCardRefundAmountrequired | string | Total credit refunds, 12-digit cents. |
RequestDataElements.DebitCardSalesCountoptional | string | Number of debit sales. |
RequestDataElements.DebitCardSalesAmountoptional | string | Total debit sales, 12-digit cents. |
RequestDataElements.DebitCardRefundCountoptional | string | Number of debit refunds. |
RequestDataElements.DebitCardRefundAmountoptional | string | Total 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"
}
}'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
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:
010manual / key-entered (default)021Track 2 read022Track 1 read050EMV chip070contactless090EMV 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).
| Field | Type | Description |
|---|---|---|
Track2Dataoptional | string | Raw track 2 data from a swipe (or the chip's track-2 image on an EMV read). Omit PrimaryAccountNumber when this is sent. |
Track1Dataoptional | string | Raw track 1 data (DE 45; less common — some MSRs send both). |
ICCSystemRelatedDataoptional | string | DE 55 — the EMV ICC TLV payload (hex) from the chip kernel. Required for chip / contactless reads. |
RequestDataElements.KeySerialNumberoptional | string | 3DES DUKPT Key Serial Number when the capture device encrypts (P2PE). |
POSEntryModerequired | string | Set 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",
...
},
...
}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)