Skip to main content

Transaction

The Transaction resource is the heart of the card transactional antifraud API. You send the transaction data before authorizing it and get back a recommendation (fraud_status) to decide whether or not to generate the authorization code.

The full integration flow has three steps:

  1. POST /card_issuance/transaction — sends the transaction for analysis and receives the recommendation.
  2. PUT /card_issuance/transaction/{id} — reports the actual outcome (captured, cancelled, chargeback). This feedback feeds the model and is what keeps decision quality up over time.
  3. GET /card_issuance/transaction/{id} — retrieves the current state and the event history of a transaction.
Start with the minimum payload

If you want to get an integration up quickly, go straight to Minimum payload. There are 13 required fields. Everything else is optional and serves to increase the model's accuracy.


Minimum payload

This is the smallest body accepted by POST /card_issuance/transaction. It contains only the required fields and is enough to receive a decision.

Minimum payload — 13 required fields
{
"id": "678",
"cardholder_id": "b812da2e-e6be-4712-8e57-6f3f2791625b",
"amount": 13725,
"currency": "BRL",
"installments": 1,
"authorization_date": "2026-08-07T13:25:42-03:00",
"authorization_type": "authorization",
"transaction_type": "credit",
"pan_entry_mode": "chip",
"pin_sent": true,
"terminal": {
"country_code": "BRA"
},
"merchant": {
"acquirer_id": "250",
"merchant_id": "123456",
"mcc": "5411"
},
"card": {
"brand": "visa",
"category": "black",
"bin": "498406",
"last4": "1234",
"issuer_country_code": "BRA"
}
}

Response:

{
"id": "678",
"fraud_status": "automatically_approved"
}
The more data, the better the decision

The optional fields (location, terminal capabilities, card limits, merchant address) are not required by validation, but they feed the models and rules directly. An integration that sends only the minimum works, but it tends to produce more false positives.


Send a transaction for analysis

ENDPOINT
/card_issuance/transaction
METHOD
POST

Query parameters

analyze boolean optional — defaults to true When true, the transaction goes through the fraud engines and the response brings a recommendation. When false, the transaction is only recorded in the cardholder's history (with no analysis cost) and the response returns not_analyzed. Use analyze=false for transactions you have already decided by other means, but which should still make up the cardholder's historical behavior.
When using analyze=false

Also send transaction_status and response_code in the body, reporting the outcome you have already applied. Without them, the transaction is recorded as pending and the cardholder's history loses information.

Request examples

import requests

BASE_URL = "https://api.sandbox.caas.qitech.app"
API_KEY = "YOUR_API_KEY"

payload = {
"id": "678",
"cardholder_id": "b812da2e-e6be-4712-8e57-6f3f2791625b",
"amount": 13725,
"currency": "BRL",
"installments": 1,
"authorization_date": "2026-08-07T13:25:42-03:00",
"authorization_type": "authorization",
"transaction_type": "credit",
"pan_entry_mode": "chip",
"pin_sent": True,
"terminal": {"country_code": "BRA"},
"merchant": {"acquirer_id": "250", "merchant_id": "123456", "mcc": "5411"},
"card": {
"brand": "visa",
"category": "black",
"bin": "498406",
"last4": "1234",
"issuer_country_code": "BRA",
},
}

response = requests.post(
f"{BASE_URL}/card_issuance/transaction",
params={"analyze": "true"},
json=payload,
headers={"Authorization": API_KEY},
timeout=5,
)

response.raise_for_status()
print(response.json()) # {'id': '678', 'fraud_status': 'automatically_approved'}

Response

idstringThe same id you sent in the request.fraud_statusenumThe antifraud engine's recommendation. See fraud_status.
{
"id": "678",
"fraud_status": "automatically_approved"
}
Behavior in case of internal unavailability

If the decision engines become unavailable, the API returns automatically_approved instead of an error. This is intentional: antifraud must never bring down card authorization. Even so, handle timeouts on your side with a defined fallback policy.


Transaction object

Root fields

idstringrequiredTransaction identifier in your system. Maximum of 36 characters. Must be unique per authorization process — a repeated id returns HTTP 409.cardholder_idstringrequiredCardholder identifier in your system. Maximum of 200 characters. This is the key that groups the behavioral history — always use the same value for the same cardholder.amountintegerrequiredTransaction amount in cents, in the currency given by currency. Between 0 and 1000000000.currencyenumrequiredTransaction currency in ISO 4217 (BRL, USD, EUR…), corresponding to the ApplicationCurrencyCode of ISO 8583.installmentsintegerrequiredNumber of installments. Between 0 and 24. Use 1 for single-payment transactions.authorization_datedatetimerequiredDate and time when the transaction started, with timezone, in the format YYYY-MM-DDThh:mm:ss±hh:mm. See the note about the format.authorization_typeenumrequiredAuthorization type. See authorization_type.transaction_typeenumrequiredFunction used: credit, debit or prepaid.pan_entry_modeenumrequiredPAN entry mode, derived from DE 22 (Sub Field 1) of ISO 8583. See pan_entry_mode.pin_sentbooleanrequiredIndicates whether a PIN was entered at the terminal.terminalobjectrequiredTerminal data. See terminal object.merchantobjectrequiredMerchant data. See merchant object.cardobjectrequiredCard data. See card object.accountholder_idstringoptionalIdentifier of the account holder, when different from the cardholder (additional cards, corporate cards). Maximum of 200 characters.group_idstringoptionalGroup or category the cardholder belongs to in your system. Maximum of 200 characters. Useful to segment rules by portfolio.brl_converted_amountintegeroptionalTransaction amount converted to Brazilian reais, in cents. You do not need to send this field — when currency is different from BRL, QI Tech computes the conversion internally; when it is BRL, the value equals amount. If sent, it is overwritten.locationobjectoptionalGeographic location of the transaction. See location object.authentication_typestringoptionalAuthentication method applied to the transaction (for example, the result of a 3-D Secure). Maximum of 200 characters.risk_assessmentenumoptionalRisk classification assigned by the card brand or by the acquirer in the messaging. See risk_assessment.cvv_presencebooleanoptionalIndicates whether the CVV was provided in the transaction. A relevant signal in e-commerce transactions.transaction_statusenumoptionalTransaction status. Send it in the POST only when using analyze=false and the authorization decision has already been made. See transaction_status.response_codestringoptionalTransaction response code according to the Response Code field of ISO 8583. Exactly 1 or 2 characters. Like transaction_status, it only makes sense in the POST with analyze=false.
Full payload
{
"id": "678",
"cardholder_id": "b812da2e-e6be-4712-8e57-6f3f2791625b",
"accountholder_id": "0f5e2d1c-4a3b-4c6d-9e8f-1a2b3c4d5e6f",
"group_id": "8507884b-c30f-4b45-951c-f0bf366926fc",
"amount": 13725,
"currency": "BRL",
"installments": 6,
"authorization_date": "2026-08-07T13:25:42-03:00",
"authorization_type": "authorization",
"transaction_type": "credit",
"pan_entry_mode": "chip",
"pin_sent": true,
"source_account": "credit_facility",
"authentication_type": "3ds_authenticated",
"risk_assessment": "low_risk",
"cvv_presence": true,
"location": {
"latitude": -23.5613,
"longitude": -46.6565,
"altitude": 760
},
"terminal": {
"id": "12345678",
"country_code": "BRA",
"terminal_type": "5",
"pin_entry_capability": true,
"magnetic_stripe_capability": true,
"contactless_capability": true,
"chip_capability": true
},
"merchant": {
"acquirer_id": "250",
"merchant_id": "123456",
"payment_facilitator": "PAGSEGURO",
"sub_merchant": "LOJA 042",
"name": "SUPERMERCADO EXEMPLO",
"street": "RUA CMDTE X, 127",
"city": "SAO PAULO",
"region": "SP",
"postal_code": "04570-140",
"mcc": "5411"
},
"card": {
"brand": "visa",
"category": "black",
"holder_id": "b812da2e-e6be-4712-8e57-6f3f2791625b",
"issuing_date": "2025-10-08T07:13:12-03:00",
"unblock_date": "2025-10-12T07:13:12-03:00",
"expiration_date": "2030-12-31",
"bin": "498406",
"last4": "1234",
"total_credit_limit": 2500000,
"used_credit_limit": 732625,
"issuer_country_code": "BRA"
}
}
Unexpected fields are rejected

The schema uses additionalProperties: false on every object. Any field outside the ones listed here makes the request return HTTP 400, even if the rest of the payload is correct.

Format of authorization_date

The validator only accepts timezone offsets ending in :00 or :30 (for example -03:00, +05:30, -04:00). A Z suffix and offsets such as -03:15 are rejected with HTTP 400. Fractional seconds are optional and accept 1 to 6 digits:

2026-08-07T13:25:42-03:00 ✅
2026-08-07T13:25:42.123456-03:00 ✅
2026-08-07T13:25:42Z ❌ use -00:00
2026-08-07T13:25:42-03:15 ❌ offset not allowed

The same rule applies to card.issuing_date and card.unblock_date.


terminal object

country_codeenumrequiredTerminal country in ISO 3166-1 alpha-3 (BRA, USA, PRT…). Terminal Country Code field of ISO 8583.idstringoptionalTerminal identifier sent by the acquirer. Maximum of 8 characters. An empty string is treated as absent.terminal_typestringoptionalTerminal type according to TerminalType of ISO 8583. Maximum of 10 characters. See terminal_type.pin_entry_capabilitybooleanoptionalDoes the terminal allow entering a PIN? TerminalPINEntryCapability field of ISO 8583.magnetic_stripe_capabilitybooleanoptionalCan the terminal read magnetic stripe? TerminalPANEntryCapability field (DE 123).contactless_capabilitybooleanoptionalDoes the terminal accept contactless transactions? TerminalPANEntryCapability field (DE 123).chip_capabilitybooleanoptionalCan the terminal read an EMV chip? TerminalPANEntryCapability field (DE 123).
{
"terminal": {
"id": "12345678",
"country_code": "BRA",
"terminal_type": "5",
"pin_entry_capability": true,
"magnetic_stripe_capability": true,
"contactless_capability": true,
"chip_capability": true
}
}
Change from the previous version of this documentation

Only country_code is required inside terminal. Older versions of this page listed terminal_type, pin_entry_capability and chip_capability as required — they are optional.


merchant object

acquirer_idstringrequiredAcquirer identifier. Maximum of 11 characters. Acquirer Identifier field (DE 32) of ISO 8583.merchant_idstringrequiredMerchant identifier at the acquirer. Maximum of 15 characters. Merchant Identifier field of ISO 8583.mccenumrequired4-digit Merchant Category Code, according to ISO 18245. Only valid MCCs from the official list are accepted — a code outside the list returns HTTP 400.namestringoptionalMerchant name according to the messaging. Maximum of 200 characters.payment_facilitatorstringoptionalPayment facilitator (sub-acquirer) involved in the transaction. Maximum of 200 characters.sub_merchantstringoptionalSub-merchant, when the transaction goes through a facilitator. Maximum of 200 characters.streetstringoptionalMerchant street address. Card Acceptor Street Address field.citystringoptionalMerchant city. Card Acceptor City field.regionstringoptionalMerchant region/state. Card Acceptor Region Code field.postal_codestringoptionalMerchant postal code. Card Acceptor Postal Code field.
{
"merchant": {
"acquirer_id": "250",
"merchant_id": "123456",
"payment_facilitator": "PAGSEGURO",
"sub_merchant": "LOJA 042",
"name": "SUPERMERCADO EXEMPLO",
"street": "RUA CMDTE X, 127",
"city": "SAO PAULO",
"region": "SP",
"postal_code": "04570-140",
"mcc": "5411"
}
}

card object

brandenumrequiredCard brand. See brand.categoryenumrequiredCard category. See category.binstringrequiredCard BIN. Exactly 6 numeric digits.last4stringrequiredLast four digits of the card. Exactly 4 numeric digits.issuer_country_codeenumrequiredIssuer country in ISO 3166-1 alpha-3.holder_idstringoptionalIdentifier of the cardholder linked to this specific plastic, useful when the same cardholder_id has multiple cards. Maximum of 200 characters.issuing_datedatetimeoptionalDate and time the card was issued, with timezone. Recently issued cards are a relevant risk signal.unblock_datedatetimeoptionalDate and time the cardholder unblocked the card, with timezone.expiration_datedateoptionalCard expiration date in the format YYYY-MM-DD (use the last day of the month).total_credit_limitintegeroptionalTotal credit limit of the cardholder, in cents. For prepaid cards, the available balance.used_credit_limitintegeroptionalLimit already used, in cents, before the transaction under analysis.
{
"card": {
"brand": "visa",
"category": "black",
"holder_id": "b812da2e-e6be-4712-8e57-6f3f2791625b",
"issuing_date": "2025-10-08T07:13:12-03:00",
"unblock_date": "2025-10-12T07:13:12-03:00",
"expiration_date": "2030-12-31",
"bin": "498406",
"last4": "1234",
"total_credit_limit": 2500000,
"used_credit_limit": 732625,
"issuer_country_code": "BRA"
}
}
Change from the previous version of this documentation

issuing_date and expiration_date are not required, contrary to what the previous version of this page indicated. The required fields in card are only brand, category, bin, last4 and issuer_country_code.


location object

latitudenumberrequired if location is sentTransaction latitude, between -90 and 90.longitudenumberrequired if location is sentTransaction longitude, between -180 and 180.altitudenumberoptionalAltitude in meters, between 0 and 100000.
{
"location": {
"latitude": -23.5613,
"longitude": -46.6565,
"altitude": 760
}
}
Optional object with required fields

location as a whole is optional. But if you send the object, latitude and longitude become required inside it. If you do not have the coordinates, omit the entire object instead of sending it empty.


Enumerators

authorization_type

ValueMeaning
authorizationPurchase authorization — MTI x1xx (DMS) and x2xx (SMS).
pre_authorizationPre-authorization to reserve limit (hotel, vehicle rental, fuel stations) — MTI x1xx (DMS) and Transaction Type 60 in the first two digits of the Processing Code.
reversalAuthorization reversal, to release limit before Clearing/BASE II — MTI x4xx.

transaction_type

ValueMeaning
creditTransaction on the credit function.
debitTransaction on the debit function.
prepaidTransaction on the prepaid function.

pan_entry_mode

Derived from DE 22 (Sub Field 1) of ISO 8583.

ValueISO 8583Meaning
unknown00Entry mode unknown.
typed01PAN keyed in manually.
bar_code03PAN read by barcode.
ocr04PAN read by OCR.
chip05PAN read by the EMV chip.
track_106PAN read from Track 1 of the stripe.
contactless07PAN read by contactless (Contactless EMV).
fallback_typed79Chip/stripe read failed and the PAN was keyed in. Also used when the acquirer is not certified for chip or stripe.
fallback_magnetic_stripe80Chip read failed and the transaction proceeded through the magnetic stripe.
ecommerce81E-commerce / card-not-present transaction.
magnetic_stripe90Magnetic stripe transaction.
manualManual entry of the card data outside a terminal flow.
stored_credentialsTransaction with stored credentials (subscriptions, recurring charges, wallets with a tokenized card).
stored_credentials and recurring charges

Recurring transactions flagged as ecommerce tend to get more declines than expected, because the model treats them as card-not-present without context. Use stored_credentials whenever the charge uses a credential previously authorized by the cardholder.

source_account

Derived from the ISO 8583 Processing Code. Optional field.

ValueISO 8583Meaning
default00Default or unspecified.
saving_account10Savings account.
checking_account20Checking account.
credit_facility30Card statement.
universal_account40Universal account.
investment_account50Investment account.
electronic_purse60Balance stored on the card chip.

brand

ValueCard brand
visaVisa
mastercardMastercard
eloElo
diners_clubDiners Club
american_expressAmerican Express

category

ValueCategory
classicClassic
goldGold
platinumPlatinum
blackBlack / Infinite
travelTravel
corporateCorporate / Business
prepaidPrepaid
postpaidPostpaid

terminal_type

According to TerminalType of ISO 8583. Sent as a string.

ValueMeaning
0Unknown
1No terminal used
2Magnetic stripe reader
3Barcode
4OCR
5Magnetic stripe reader and EMV chip reader
6Key entry only
7Magnetic stripe reader and key entry
8Stripe reader, key entry and EMV chip
9EMV chip reader

risk_assessment

Risk classification received in the messaging (for example, PSD2 TRA or the card brand's assessment).

ValueMeaning
not_evaluatedNo risk assessment was performed.
low_riskThe transaction was classified as low risk.
non_low_riskThe transaction was not classified as low risk.

transaction_status

Transaction status within the authorization lifecycle.

ValueMeaning
pendingAuthorization pending. Initial state assigned automatically.
authorizedAuthorized, awaiting capture.
not_authorizedNot authorized by the issuer.
capturedCaptured.
clearedReceived in Clearing / BASE II.
cancelledCancelled in full.
partially_cancelledPartially cancelled.
chargebackReceived a full chargeback.
partial_chargebackReceived a partial chargeback.
pending cannot be sent

pending is assigned by the API itself when the transaction is created without a decision. It is not accepted in the body of either the POST or the PUT.

fraud_status

The recommendation returned by the antifraud engine.

ValueMeaningRecommended action
automatically_approvedThe transaction pattern is consistent with the cardholder's behavior.Generate the authorization code.
automatically_declinedThe transaction presents relevant fraud risk.Decline the authorization.
not_analyzedThe request was sent with analyze=false. No analysis was performed.Follow your own decision.

Update the status of a transaction

ENDPOINT
/card_issuance/transaction/TRANSACTION_ID
METHOD
PUT

Reporting the transaction's actual outcome is what feeds the rules and the model back. Without this step, the quality of the recommendations degrades over time.

The TRANSACTION_ID in the path is the same id you sent in the POST.

Request body

The body accepts two shapes, chosen according to the status:

For any status that affects the transaction as a whole.

transaction_statusenumrequiredNew status. Accepts authorized, not_authorized, captured, cleared, cancelled, partially_cancelled, chargeback or partial_chargeback.response_codestringoptionalISO 8583 response code. 1 or 2 characters.
{
"transaction_status": "captured",
"response_code": "00"
}
Final statuses cannot be changed

A transaction already in cancelled, partially_cancelled, chargeback or partial_chargeback is considered finalized. A new PUT on it returns HTTP 400 with the title Transaction has a final status.

Practical consequence: you cannot record two partial cancellations in sequence through the API. Plan to send the consolidated amount.

On success, the response is HTTP 200 with an empty body ({}).

Request examples

import requests

BASE_URL = "https://api.sandbox.caas.qitech.app"
API_KEY = "YOUR_API_KEY"
TRANSACTION_ID = "678"

response = requests.put(
f"{BASE_URL}/card_issuance/transaction/{TRANSACTION_ID}",
json={"transaction_status": "captured", "response_code": "00"},
headers={"Authorization": API_KEY},
timeout=5,
)

response.raise_for_status() # 200 with an empty body

Retrieve a transaction

ENDPOINT
/card_issuance/transaction/TRANSACTION_ID
METHOD
GET

Returns the current state of the transaction along with the full event history. If the id does not exist for your API Key, the response is HTTP 404.

import requests

BASE_URL = "https://api.sandbox.caas.qitech.app"
API_KEY = "YOUR_API_KEY"
TRANSACTION_ID = "678"

response = requests.get(
f"{BASE_URL}/card_issuance/transaction/{TRANSACTION_ID}",
headers={"Authorization": API_KEY},
timeout=5,
)

response.raise_for_status()
transaction = response.json()
print(transaction["fraud_status"], transaction["transaction_status"])

Response

The response returns every field you sent in the POST, plus the fields below.

fraud_statusenumCurrent antifraud recommendation.transaction_statusenumCurrent status of the transaction.brl_converted_amountintegerAmount converted to Brazilian reais, in cents, computed by QI Tech.transaction_eventsarrayHistory of the transaction's status changes, in chronological order.

Fields of transaction_events[]:

new_statusenumStatus assigned in this event.event_datedatetimeDate and time of the event, in UTC.partial_amountintegerPresent only in partial events.response_codestringPresent when informed in the update.
fraud_eventsarrayHistory of antifraud decisions.

Fields of fraud_events[]:

new_statusenumDecision assigned in this event.event_datedatetimeDate and time of the decision, in UTC.decision_metadataobjectReason for the decision. Brings reason and reason_description explaining why the transaction was approved or declined.
{
"id": "678",
"cardholder_id": "b812da2e-e6be-4712-8e57-6f3f2791625b",
"amount": 13725,
"brl_converted_amount": 13725,
"currency": "BRL",
"installments": 1,
"authorization_date": "2026-08-07T13:25:42-03:00",
"authorization_type": "authorization",
"transaction_type": "credit",
"pan_entry_mode": "chip",
"pin_sent": true,
"terminal": { "country_code": "BRA" },
"merchant": {
"acquirer_id": "250",
"merchant_id": "123456",
"mcc": "5411"
},
"card": {
"brand": "visa",
"category": "black",
"bin": "498406",
"last4": "1234",
"issuer_country_code": "BRA"
},
"fraud_status": "automatically_approved",
"transaction_status": "captured",
"fraud_events": [
{
"new_status": "automatically_approved",
"event_date": "2026-08-07T16:25:43Z",
"decision_metadata": {
"reason": "automatically_approved",
"reason_description": "O padrão transacional foi normal."
}
}
],
"transaction_events": [
{
"new_status": "authorized",
"event_date": "2026-08-07T16:25:43Z"
},
{
"new_status": "captured",
"event_date": "2026-08-07T18:02:10Z",
"response_code": "00"
}
]
}

Errors

All errors return a JSON body with the same format:

{
"title": "Duplicated external_id",
"description": "id: 678 already exists for company 3fa85f64-5717-4562-b3fc-2c963f66afa6"
}
StatusSituationHow to solve it
400Invalid payload: missing required field, enum outside the list, incorrect date format or a field not foreseen by the schema.Check the description, which points out the problematic field.
400Transaction has a final status on the PUT.The transaction is already in a final status and does not accept new updates.
400partial_amount greater than the available amount.Send an amount less than or equal to the balance not yet cancelled.
401Authorization header missing or API Key deactivated.Check the header and the status of your key.
403Invalid API Key or endpoint for internal use.Confirm the key with support.
404Transaction not found for your API Key.Check the id used in the path.
406The request body is not valid JSON.Check the Content-Type and the serialization.
409id already processed previously.Generate a unique id per authorization process.
500Internal error.Our specialists are notified automatically.
503Infrastructure unavailability.Apply retry with backoff.

The full list is in HTTP Status.


Testing in Sandbox

In Sandbox, analyses are not charged and the decision is deterministic, based only on the transaction amount:

amountfraud_status returned
>= 10000 (R$ 100.00 or more)automatically_approved
<= 9999 (up to R$ 99.99)automatically_declined

Sandbox base URL: https://api.sandbox.caas.qitech.app

Important warning

Do not use real personal or company data in QI Tech's Sandbox environment.


Integration checklist

  • POST /card_issuance/transaction with the minimum payload returning 200 in Sandbox.
  • id guaranteed unique per authorization process (test the 409 by resending the same id).
  • cardholder_id stable for the same cardholder across transactions.
  • authorization_date in the format with a :00 or :30 offset.
  • Monetary amounts in cents, as integers.
  • Timeout handling with a defined fallback policy (approve or decline on your own).
  • PUT sent for every outcome: capture, cancellation, chargeback.
  • Cardholder Alerts webhook configured with support.