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:
POST /card_issuance/transaction— sends the transaction for analysis and receives the recommendation.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.GET /card_issuance/transaction/{id}— retrieves the current state and the event history of a transaction.
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.
{
"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 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
Query parameters
analyze boolean optional — defaults totrue
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.
analyze=falseAlso 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
- Python
- PHP
- Node.js
- Java
- C#
- curl
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'}
<?php
$baseUrl = 'https://api.sandbox.caas.qitech.app';
$apiKey = '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',
],
];
$ch = curl_init($baseUrl . '/card_issuance/transaction?analyze=true');
curl_setopt_array($ch, [
CURLOPT_POST => true,
CURLOPT_RETURNTRANSFER => true,
CURLOPT_TIMEOUT => 5,
CURLOPT_HTTPHEADER => [
'Content-Type: application/json',
'Authorization: ' . $apiKey,
],
CURLOPT_POSTFIELDS => json_encode($payload),
]);
$body = curl_exec($ch);
$status = curl_getinfo($ch, CURLINFO_HTTP_CODE);
curl_close($ch);
if ($status !== 200) {
throw new RuntimeException("Antifraud returned HTTP {$status}: {$body}");
}
$result = json_decode($body, true);
echo $result['fraud_status']; // automatically_approved
const BASE_URL = "https://api.sandbox.caas.qitech.app";
const API_KEY = "YOUR_API_KEY";
const 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",
},
};
async function analyzeTransaction() {
const response = await fetch(
`${BASE_URL}/card_issuance/transaction?analyze=true`,
{
method: "POST",
headers: {
"Content-Type": "application/json",
Authorization: API_KEY,
},
body: JSON.stringify(payload),
signal: AbortSignal.timeout(5000),
},
);
if (!response.ok) {
throw new Error(`Antifraud returned HTTP ${response.status}`);
}
const result = await response.json();
console.log(result.fraud_status); // automatically_approved
return result;
}
analyzeTransaction();
import java.net.URI;
import java.net.http.HttpClient;
import java.net.http.HttpRequest;
import java.net.http.HttpResponse;
import java.time.Duration;
public class AnalyzeTransaction {
private static final String BASE_URL = "https://api.sandbox.caas.qitech.app";
private static final String API_KEY = "YOUR_API_KEY";
public static void main(String[] args) throws Exception {
String 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"
}
}
""";
HttpClient client = HttpClient.newBuilder()
.connectTimeout(Duration.ofSeconds(5))
.build();
HttpRequest request = HttpRequest.newBuilder()
.uri(URI.create(BASE_URL + "/card_issuance/transaction?analyze=true"))
.header("Content-Type", "application/json")
.header("Authorization", API_KEY)
.timeout(Duration.ofSeconds(5))
.POST(HttpRequest.BodyPublishers.ofString(payload))
.build();
HttpResponse<String> response =
client.send(request, HttpResponse.BodyHandlers.ofString());
if (response.statusCode() != 200) {
throw new IllegalStateException(
"Antifraud returned HTTP " + response.statusCode() + ": " + response.body());
}
System.out.println(response.body());
// {"id":"678","fraud_status":"automatically_approved"}
}
}
using System;
using System.Net.Http;
using System.Text;
using System.Text.Json;
using System.Threading.Tasks;
public class AnalyzeTransaction
{
private const string BaseUrl = "https://api.sandbox.caas.qitech.app";
private const string ApiKey = "YOUR_API_KEY";
public static async Task Main()
{
var payload = new
{
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 = new { country_code = "BRA" },
merchant = new { acquirer_id = "250", merchant_id = "123456", mcc = "5411" },
card = new
{
brand = "visa",
category = "black",
bin = "498406",
last4 = "1234",
issuer_country_code = "BRA"
}
};
using var client = new HttpClient { Timeout = TimeSpan.FromSeconds(5) };
client.DefaultRequestHeaders.Add("Authorization", ApiKey);
var content = new StringContent(
JsonSerializer.Serialize(payload), Encoding.UTF8, "application/json");
var response = await client.PostAsync(
$"{BaseUrl}/card_issuance/transaction?analyze=true", content);
var body = await response.Content.ReadAsStringAsync();
if (!response.IsSuccessStatusCode)
{
throw new InvalidOperationException(
$"Antifraud returned HTTP {(int)response.StatusCode}: {body}");
}
Console.WriteLine(body);
// {"id":"678","fraud_status":"automatically_approved"}
}
}
curl -X POST \
'https://api.sandbox.caas.qitech.app/card_issuance/transaction?analyze=true' \
-H 'Content-Type: application/json' \
-H 'Authorization: YOUR_API_KEY' \
-d '{
"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 you sent in the request.fraud_statusenumThe antifraud engine's recommendation. See fraud_status.{
"id": "678",
"fraud_status": "automatically_approved"
}
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
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.{
"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"
}
}
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
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
}
}
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
{
"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
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"
}
}
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
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
}
}
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
| Value | Meaning |
|---|---|
authorization | Purchase authorization — MTI x1xx (DMS) and x2xx (SMS). |
pre_authorization | Pre-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. |
reversal | Authorization reversal, to release limit before Clearing/BASE II — MTI x4xx. |
transaction_type
| Value | Meaning |
|---|---|
credit | Transaction on the credit function. |
debit | Transaction on the debit function. |
prepaid | Transaction on the prepaid function. |
pan_entry_mode
Derived from DE 22 (Sub Field 1) of ISO 8583.
| Value | ISO 8583 | Meaning |
|---|---|---|
unknown | 00 | Entry mode unknown. |
typed | 01 | PAN keyed in manually. |
bar_code | 03 | PAN read by barcode. |
ocr | 04 | PAN read by OCR. |
chip | 05 | PAN read by the EMV chip. |
track_1 | 06 | PAN read from Track 1 of the stripe. |
contactless | 07 | PAN read by contactless (Contactless EMV). |
fallback_typed | 79 | Chip/stripe read failed and the PAN was keyed in. Also used when the acquirer is not certified for chip or stripe. |
fallback_magnetic_stripe | 80 | Chip read failed and the transaction proceeded through the magnetic stripe. |
ecommerce | 81 | E-commerce / card-not-present transaction. |
magnetic_stripe | 90 | Magnetic stripe transaction. |
manual | — | Manual entry of the card data outside a terminal flow. |
stored_credentials | — | Transaction with stored credentials (subscriptions, recurring charges, wallets with a tokenized card). |
stored_credentials and recurring chargesRecurring 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.
| Value | ISO 8583 | Meaning |
|---|---|---|
default | 00 | Default or unspecified. |
saving_account | 10 | Savings account. |
checking_account | 20 | Checking account. |
credit_facility | 30 | Card statement. |
universal_account | 40 | Universal account. |
investment_account | 50 | Investment account. |
electronic_purse | 60 | Balance stored on the card chip. |
brand
| Value | Card brand |
|---|---|
visa | Visa |
mastercard | Mastercard |
elo | Elo |
diners_club | Diners Club |
american_express | American Express |
category
| Value | Category |
|---|---|
classic | Classic |
gold | Gold |
platinum | Platinum |
black | Black / Infinite |
travel | Travel |
corporate | Corporate / Business |
prepaid | Prepaid |
postpaid | Postpaid |
terminal_type
According to TerminalType of ISO 8583. Sent as a string.
| Value | Meaning |
|---|---|
0 | Unknown |
1 | No terminal used |
2 | Magnetic stripe reader |
3 | Barcode |
4 | OCR |
5 | Magnetic stripe reader and EMV chip reader |
6 | Key entry only |
7 | Magnetic stripe reader and key entry |
8 | Stripe reader, key entry and EMV chip |
9 | EMV chip reader |
risk_assessment
Risk classification received in the messaging (for example, PSD2 TRA or the card brand's assessment).
| Value | Meaning |
|---|---|
not_evaluated | No risk assessment was performed. |
low_risk | The transaction was classified as low risk. |
non_low_risk | The transaction was not classified as low risk. |
transaction_status
Transaction status within the authorization lifecycle.
| Value | Meaning |
|---|---|
pending | Authorization pending. Initial state assigned automatically. |
authorized | Authorized, awaiting capture. |
not_authorized | Not authorized by the issuer. |
captured | Captured. |
cleared | Received in Clearing / BASE II. |
cancelled | Cancelled in full. |
partially_cancelled | Partially cancelled. |
chargeback | Received a full chargeback. |
partial_chargeback | Received a partial chargeback. |
pending cannot be sentpending 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.
| Value | Meaning | Recommended action |
|---|---|---|
automatically_approved | The transaction pattern is consistent with the cardholder's behavior. | Generate the authorization code. |
automatically_declined | The transaction presents relevant fraud risk. | Decline the authorization. |
not_analyzed | The request was sent with analyze=false. No analysis was performed. | Follow your own decision. |
Update the status of a transaction
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:
- Full update
- Partial update
For any status that affects the transaction as a whole.
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"
}
Required for partially_cancelled and partial_chargeback.
partially_cancelled or partial_chargeback.partial_amountintegerrequiredCancelled/refunded amount in cents, from 1 to 1000000000. It cannot exceed the amount still available on the transaction.response_codestringoptionalISO 8583 response code. 1 or 2 characters.{
"transaction_status": "partially_cancelled",
"partial_amount": 3000,
"response_code": "00"
}
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
- Python
- PHP
- Node.js
- Java
- C#
- curl
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
<?php
$baseUrl = 'https://api.sandbox.caas.qitech.app';
$apiKey = 'YOUR_API_KEY';
$transactionId = '678';
$payload = [
'transaction_status' => 'captured',
'response_code' => '00',
];
$ch = curl_init("{$baseUrl}/card_issuance/transaction/{$transactionId}");
curl_setopt_array($ch, [
CURLOPT_CUSTOMREQUEST => 'PUT',
CURLOPT_RETURNTRANSFER => true,
CURLOPT_TIMEOUT => 5,
CURLOPT_HTTPHEADER => [
'Content-Type: application/json',
'Authorization: ' . $apiKey,
],
CURLOPT_POSTFIELDS => json_encode($payload),
]);
$body = curl_exec($ch);
$status = curl_getinfo($ch, CURLINFO_HTTP_CODE);
curl_close($ch);
if ($status !== 200) {
throw new RuntimeException("Failed to update status: HTTP {$status} — {$body}");
}
const BASE_URL = "https://api.sandbox.caas.qitech.app";
const API_KEY = "YOUR_API_KEY";
const TRANSACTION_ID = "678";
async function updateStatus() {
const response = await fetch(
`${BASE_URL}/card_issuance/transaction/${TRANSACTION_ID}`,
{
method: "PUT",
headers: {
"Content-Type": "application/json",
Authorization: API_KEY,
},
body: JSON.stringify({
transaction_status: "captured",
response_code: "00",
}),
signal: AbortSignal.timeout(5000),
},
);
if (!response.ok) {
throw new Error(`Failed to update status: HTTP ${response.status}`);
}
}
updateStatus();
import java.net.URI;
import java.net.http.HttpClient;
import java.net.http.HttpRequest;
import java.net.http.HttpResponse;
import java.time.Duration;
public class UpdateTransactionStatus {
private static final String BASE_URL = "https://api.sandbox.caas.qitech.app";
private static final String API_KEY = "YOUR_API_KEY";
private static final String TRANSACTION_ID = "678";
public static void main(String[] args) throws Exception {
String payload = """
{ "transaction_status": "captured", "response_code": "00" }
""";
HttpClient client = HttpClient.newHttpClient();
HttpRequest request = HttpRequest.newBuilder()
.uri(URI.create(BASE_URL + "/card_issuance/transaction/" + TRANSACTION_ID))
.header("Content-Type", "application/json")
.header("Authorization", API_KEY)
.timeout(Duration.ofSeconds(5))
.PUT(HttpRequest.BodyPublishers.ofString(payload))
.build();
HttpResponse<String> response =
client.send(request, HttpResponse.BodyHandlers.ofString());
if (response.statusCode() != 200) {
throw new IllegalStateException(
"Failed to update status: HTTP " + response.statusCode()
+ " — " + response.body());
}
}
}
using System;
using System.Net.Http;
using System.Text;
using System.Text.Json;
using System.Threading.Tasks;
public class UpdateTransactionStatus
{
private const string BaseUrl = "https://api.sandbox.caas.qitech.app";
private const string ApiKey = "YOUR_API_KEY";
private const string TransactionId = "678";
public static async Task Main()
{
var payload = new { transaction_status = "captured", response_code = "00" };
using var client = new HttpClient { Timeout = TimeSpan.FromSeconds(5) };
client.DefaultRequestHeaders.Add("Authorization", ApiKey);
var content = new StringContent(
JsonSerializer.Serialize(payload), Encoding.UTF8, "application/json");
var response = await client.PutAsync(
$"{BaseUrl}/card_issuance/transaction/{TransactionId}", content);
if (!response.IsSuccessStatusCode)
{
var body = await response.Content.ReadAsStringAsync();
throw new InvalidOperationException(
$"Failed to update status: HTTP {(int)response.StatusCode} — {body}");
}
}
}
curl -X PUT \
'https://api.sandbox.caas.qitech.app/card_issuance/transaction/678' \
-H 'Content-Type: application/json' \
-H 'Authorization: YOUR_API_KEY' \
-d '{ "transaction_status": "captured", "response_code": "00" }'
Retrieve a transaction
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.
- Python
- PHP
- Node.js
- Java
- C#
- curl
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"])
<?php
$baseUrl = 'https://api.sandbox.caas.qitech.app';
$apiKey = 'YOUR_API_KEY';
$transactionId = '678';
$ch = curl_init("{$baseUrl}/card_issuance/transaction/{$transactionId}");
curl_setopt_array($ch, [
CURLOPT_RETURNTRANSFER => true,
CURLOPT_TIMEOUT => 5,
CURLOPT_HTTPHEADER => ['Authorization: ' . $apiKey],
]);
$body = curl_exec($ch);
$status = curl_getinfo($ch, CURLINFO_HTTP_CODE);
curl_close($ch);
if ($status === 404) {
throw new RuntimeException("Transaction {$transactionId} not found.");
}
$transaction = json_decode($body, true);
echo $transaction['fraud_status'];
const BASE_URL = "https://api.sandbox.caas.qitech.app";
const API_KEY = "YOUR_API_KEY";
const TRANSACTION_ID = "678";
async function getTransaction() {
const response = await fetch(
`${BASE_URL}/card_issuance/transaction/${TRANSACTION_ID}`,
{ headers: { Authorization: API_KEY } },
);
if (response.status === 404) {
throw new Error(`Transaction ${TRANSACTION_ID} not found.`);
}
const transaction = await response.json();
console.log(transaction.fraud_status, transaction.transaction_status);
return transaction;
}
getTransaction();
import java.net.URI;
import java.net.http.HttpClient;
import java.net.http.HttpRequest;
import java.net.http.HttpResponse;
import java.time.Duration;
public class GetTransaction {
private static final String BASE_URL = "https://api.sandbox.caas.qitech.app";
private static final String API_KEY = "YOUR_API_KEY";
private static final String TRANSACTION_ID = "678";
public static void main(String[] args) throws Exception {
HttpClient client = HttpClient.newHttpClient();
HttpRequest request = HttpRequest.newBuilder()
.uri(URI.create(BASE_URL + "/card_issuance/transaction/" + TRANSACTION_ID))
.header("Authorization", API_KEY)
.timeout(Duration.ofSeconds(5))
.GET()
.build();
HttpResponse<String> response =
client.send(request, HttpResponse.BodyHandlers.ofString());
if (response.statusCode() == 404) {
throw new IllegalStateException("Transaction " + TRANSACTION_ID + " not found.");
}
System.out.println(response.body());
}
}
using System;
using System.Net;
using System.Net.Http;
using System.Threading.Tasks;
public class GetTransaction
{
private const string BaseUrl = "https://api.sandbox.caas.qitech.app";
private const string ApiKey = "YOUR_API_KEY";
private const string TransactionId = "678";
public static async Task Main()
{
using var client = new HttpClient { Timeout = TimeSpan.FromSeconds(5) };
client.DefaultRequestHeaders.Add("Authorization", ApiKey);
var response = await client.GetAsync(
$"{BaseUrl}/card_issuance/transaction/{TransactionId}");
if (response.StatusCode == HttpStatusCode.NotFound)
{
throw new InvalidOperationException($"Transaction {TransactionId} not found.");
}
Console.WriteLine(await response.Content.ReadAsStringAsync());
}
}
curl 'https://api.sandbox.caas.qitech.app/card_issuance/transaction/678' \
-H 'Authorization: YOUR_API_KEY'
Response
The response returns every field you sent in the POST, plus the fields below.
Fields of transaction_events[]:
Fields of fraud_events[]:
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"
}
| Status | Situation | How to solve it |
|---|---|---|
| 400 | Invalid 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. |
| 400 | Transaction has a final status on the PUT. | The transaction is already in a final status and does not accept new updates. |
| 400 | partial_amount greater than the available amount. | Send an amount less than or equal to the balance not yet cancelled. |
| 401 | Authorization header missing or API Key deactivated. | Check the header and the status of your key. |
| 403 | Invalid API Key or endpoint for internal use. | Confirm the key with support. |
| 404 | Transaction not found for your API Key. | Check the id used in the path. |
| 406 | The request body is not valid JSON. | Check the Content-Type and the serialization. |
| 409 | id already processed previously. | Generate a unique id per authorization process. |
| 500 | Internal error. | Our specialists are notified automatically. |
| 503 | Infrastructure 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:
amount | fraud_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
Do not use real personal or company data in QI Tech's Sandbox environment.
Integration checklist
-
POST /card_issuance/transactionwith the minimum payload returning200in Sandbox. -
idguaranteed unique per authorization process (test the409by resending the sameid). -
cardholder_idstable for the same cardholder across transactions. -
authorization_datein the format with a:00or:30offset. - Monetary amounts in cents, as integers.
- Timeout handling with a defined fallback policy (approve or decline on your own).
-
PUTsent for every outcome: capture, cancellation, chargeback. - Cardholder Alerts webhook configured with support.