# QI Tech — Risk Solutions › Antifraude transacional de cartão

Documentação da QI Tech em texto corrido, para colar em um LLM.
Fonte: https://docs.qitech.com.br
5 página(s).

Índice:
- Alertas de Portadores (/documentation/caas/card_issuance/alerts)
- Status HTTP (/documentation/caas/card_issuance/http_status)
- Introdução (/documentation/caas/card_issuance/introduction)
- Padrões (/documentation/caas/card_issuance/standards)
- Transaction (/documentation/caas/card_issuance/transaction)

---

# Alertas de Portadores

URL: /documentation/caas/card_issuance/alerts

Alertas de Portadores

Os alertas gerados pela ferramenta antifraude são notificados por meio de Webhook. Para tanto, é necessário, por meio da equipe do [suporte](mailto:suporte.caas@qitech.com.br), configurar um endereço do endpoint por onde vamos notificar as notificações e também um *secret_token* que será utilizado para assinar a requisição.

Nesta notificação enviaremos informações dos alertas gerados, bem como de qual portador se trata, para que o cliente possa tomar alguma ação, por exemplo, enviar um *push notification* para o portador.

## Requisição

Request Body

```json
    {
        "alert_key": "123456",
        "cardholder_id": "ef47bc3f-61ac-4b85-ad67-0cfa3a422201",
        "company_name": "Cliente 1",
        "irregularity_type" : "fraud",
        "risk_level": "critical"
    }
```

A requisição possui o formato acima e notifica a abertura de um novo alerta para um Portador - descrito pelo *cardholder_id*

## Assinatura do Webhook

Para garantir que a requisição recebida no seu endpoint partiu dos nossos servidores, enviamos uma assinatura HMAC no header `Signature`. Você recalcula essa assinatura do seu lado e compara com a recebida — se forem iguais, a requisição é confiável.

### Como a assinatura é calculada

```text
Signature = HMAC-SHA1(signature_key, endpoint + method + payload)  →  hexadecimal
```

Os três componentes são concatenados **nesta ordem, sem separador**:

| Componente | O que é |
| --- | --- |
| `endpoint` | A URL completa do seu webhook, exatamente como foi configurada com o suporte (incluindo `https://` e eventual query string). |
| `method` | O verbo HTTP em **letras maiúsculas** — sempre `POST` nas notificações de alerta. |
| `payload` | O corpo da requisição **exatamente como recebido**, byte a byte. |
| `signature_key` | O `secret_token` que você combinou com o suporte. É a chave do HMAC, não parte da mensagem. |

:::danger Use o corpo bruto, nunca o JSON reserializado
A assinatura é calculada sobre os bytes exatos do corpo. Se você desserializar o JSON e serializar de novo antes de validar, a ordem das chaves e o espaçamento mudam, e a assinatura **nunca** vai bater.

Leia o corpo como string/bytes brutos primeiro, valide a assinatura, e só depois faça o parse. Nos exemplos abaixo isso aparece como `request.data`, `file_get_contents('php://input')`, `req.rawBody` etc.
:::

:::caution Acentuação no payload
Nós serializamos o corpo com `ensure_ascii=False`, ou seja, caracteres acentuados vão como UTF-8 literal (`"João"`), e não escapados (`"João"`). Trate o corpo como UTF-8 ao calcular o HMAC — é o comportamento padrão em todas as linguagens abaixo, mas é a causa mais comum de assinatura divergente quando o `company_name` tem acento.
:::

### Exemplos de validação

**Python**

```python
import hashlib
import hmac

SIGNATURE_KEY = "YOUR_SECRET_TOKEN"
WEBHOOK_URL = "https://seu-dominio.com/webhooks/qitech/alertas"

def calculate_signature(endpoint: str, method: str, payload: str) -> str:
    hmac_obj = hmac.new(
        SIGNATURE_KEY.encode("utf-8"),
        (endpoint + method + payload).encode("utf-8"),
        hashlib.sha1,
    )
    return hmac_obj.hexdigest()

def is_valid(received_signature: str, raw_body: str) -> bool:
    expected = calculate_signature(WEBHOOK_URL, "POST", raw_body)
    # compare_digest evita ataques de temporização
    return hmac.compare_digest(expected, received_signature)

# Exemplo com Flask
from flask import Flask, request

app = Flask(__name__)

@app.route("/webhooks/qitech/alertas", methods=["POST"])
def receive_alert():
    raw_body = request.get_data(as_text=True)  # corpo bruto, sem parse
    received = request.headers.get("Signature", "")

    if not is_valid(received, raw_body):
        return "", 401

    alert = request.get_json()  # parse só depois de validar
    print(alert["cardholder_id"], alert["risk_level"])
    return "", 200
```

**PHP**

```php
<?php

const SIGNATURE_KEY = 'YOUR_SECRET_TOKEN';
const WEBHOOK_URL   = 'https://seu-dominio.com/webhooks/qitech/alertas';

function calculateSignature(string $endpoint, string $method, string $payload): string
{
    return hash_hmac('sha1', $endpoint . $method . $payload, SIGNATURE_KEY);
}

function isValid(string $receivedSignature, string $rawBody): bool
{
    $expected = calculateSignature(WEBHOOK_URL, 'POST', $rawBody);
    // hash_equals evita ataques de temporização
    return hash_equals($expected, $receivedSignature);
}

// Recebendo a notificação
$rawBody  = file_get_contents('php://input');           // corpo bruto, sem parse
$received = $_SERVER['HTTP_SIGNATURE'] ?? '';

if (!isValid($received, $rawBody)) {
    http_response_code(401);
    exit;
}

$alert = json_decode($rawBody, true);                   // parse só depois de validar
error_log($alert['cardholder_id'] . ' - ' . $alert['risk_level']);

http_response_code(200);
```

**Node.js**

```javascript
const crypto = require("crypto");
const express = require("express");

const SIGNATURE_KEY = "YOUR_SECRET_TOKEN";
const WEBHOOK_URL = "https://seu-dominio.com/webhooks/qitech/alertas";

function calculateSignature(endpoint, method, payload) {
  return crypto
    .createHmac("sha1", SIGNATURE_KEY)
    .update(endpoint + method + payload, "utf8")
    .digest("hex");
}

function isValid(receivedSignature, rawBody) {
  const expected = calculateSignature(WEBHOOK_URL, "POST", rawBody);
  const a = Buffer.from(expected, "utf8");
  const b = Buffer.from(receivedSignature, "utf8");
  // timingSafeEqual exige buffers de mesmo tamanho
  return a.length === b.length && crypto.timingSafeEqual(a, b);
}

const app = express();

// express.raw preserva o corpo bruto — NÃO use express.json() nesta rota
app.post(
  "/webhooks/qitech/alertas",
  express.raw({ type: "application/json" }),
  (req, res) => {
    const rawBody = req.body.toString("utf8");
    const received = req.get("Signature") || "";

    if (!isValid(received, rawBody)) {
      return res.sendStatus(401);
    }

    const alert = JSON.parse(rawBody); // parse só depois de validar
    console.log(alert.cardholder_id, alert.risk_level);
    res.sendStatus(200);
  },
);

app.listen(3000);
```

**Java**

```java
import java.nio.charset.StandardCharsets;
import java.security.MessageDigest;
import javax.crypto.Mac;
import javax.crypto.spec.SecretKeySpec;

public class WebhookSignature {

    private static final String SIGNATURE_KEY = "YOUR_SECRET_TOKEN";
    private static final String WEBHOOK_URL =
            "https://seu-dominio.com/webhooks/qitech/alertas";

    public static String calculateSignature(String endpoint, String method, String payload)
            throws Exception {
        Mac mac = Mac.getInstance("HmacSHA1");
        mac.init(new SecretKeySpec(
                SIGNATURE_KEY.getBytes(StandardCharsets.UTF_8), "HmacSHA1"));

        byte[] digest = mac.doFinal(
                (endpoint + method + payload).getBytes(StandardCharsets.UTF_8));

        StringBuilder hex = new StringBuilder(digest.length * 2);
        for (byte b : digest) {
            hex.append(String.format("%02x", b));
        }
        return hex.toString();
    }

    public static boolean isValid(String receivedSignature, String rawBody)
            throws Exception {
        String expected = calculateSignature(WEBHOOK_URL, "POST", rawBody);
        // MessageDigest.isEqual evita ataques de temporização
        return MessageDigest.isEqual(
                expected.getBytes(StandardCharsets.UTF_8),
                receivedSignature.getBytes(StandardCharsets.UTF_8));
    }
}
```

Em Spring Boot, receba o corpo como `String` para preservar os bytes originais:

```java
import org.springframework.http.ResponseEntity;
import org.springframework.web.bind.annotation.*;

@RestController
public class AlertController {

    @PostMapping("/webhooks/qitech/alertas")
    public ResponseEntity<Void> receiveAlert(
            @RequestBody String rawBody,                       // corpo bruto, sem parse
            @RequestHeader(value = "Signature", required = false) String signature)
            throws Exception {

        if (signature == null || !WebhookSignature.isValid(signature, rawBody)) {
            return ResponseEntity.status(401).build();
        }

        // parse só depois de validar (ex.: com Jackson)
        return ResponseEntity.ok().build();
    }
}
```

**C#**

```csharp
using System;
using System.Security.Cryptography;
using System.Text;

public static class WebhookSignature
{
    private const string SignatureKey = "YOUR_SECRET_TOKEN";
    private const string WebhookUrl =
        "https://seu-dominio.com/webhooks/qitech/alertas";

    public static string CalculateSignature(string endpoint, string method, string payload)
    {
        using var hmac = new HMACSHA1(Encoding.UTF8.GetBytes(SignatureKey));
        var digest = hmac.ComputeHash(Encoding.UTF8.GetBytes(endpoint + method + payload));
        return Convert.ToHexString(digest).ToLowerInvariant();
    }

    public static bool IsValid(string receivedSignature, string rawBody)
    {
        var expected = CalculateSignature(WebhookUrl, "POST", rawBody);
        // FixedTimeEquals evita ataques de temporização
        return CryptographicOperations.FixedTimeEquals(
            Encoding.UTF8.GetBytes(expected),
            Encoding.UTF8.GetBytes(receivedSignature));
    }
}
```

Em ASP.NET Core, leia o corpo bruto antes de qualquer desserialização:

```csharp
app.MapPost("/webhooks/qitech/alertas", async (HttpRequest request) =>
{
    using var reader = new StreamReader(request.Body, Encoding.UTF8);
    var rawBody = await reader.ReadToEndAsync();          // corpo bruto, sem parse

    var received = request.Headers["Signature"].ToString();

    if (!WebhookSignature.IsValid(received, rawBody))
    {
        return Results.Unauthorized();
    }

    // parse só depois de validar
    return Results.Ok();
});
```

:::tip Assinatura não bate? Verifique nesta ordem
1. **O corpo foi reserializado?** É a causa mais frequente. Use o corpo bruto.
2. **A URL está idêntica?** Uma barra final a mais ou a menos (`/alertas` vs `/alertas/`) muda a assinatura. Use exatamente a URL configurada com o suporte.
3. **O método está em maiúsculas?** Deve ser `POST`, não `post`.
4. **A ordem da concatenação está certa?** É `endpoint + method + payload`, nessa ordem.
5. **O digest está em hexadecimal minúsculo?** Não é Base64.
:::

## Retentativas

A notificação é considerada realizada quando recebe como resposta um HTTP Status 200. Caso as notificações falhem, serão feitas 5 retentativas, com os seguintes intervalos, até que um 200 seja retornado ou as tentativas terminem:

* 30 segundos
* 60 segundos
* 120 segundos
* 240 segundos
* 360 segundos

---

# Status HTTP

URL: /documentation/caas/card_issuance/http_status

Todas as APIs da QI Tech utilizam a seguinte padronização nos status HTTP de retorno, de acordo com o RFC 7231 :

Status HTTP | Significado | Descrição
---------- | ------- | ---------------------------------
400 | Bad Request | A requisição enviada possui algum erro de formatação. Na maioria dos casos, retornamos no corpo da mensagem uma explicação de onde está o erro.
401 | Unauthorized | Houve algum problema na autenticação, verifique se a API Key está correta e no header correto, de acordo com a seção Autenticação .
403 | Forbidden | O endpoint acessado é de uso interno e não está disponível para esta API Key.
404 | Not Found | O dado requisitado não foi encontrado usando a chave utilizada. Este status também é retornado quando um endpoint inválido é requisitado.
405 | Method Not Allowed | O método HTTP utilizado não se aplica ao endpoint utilizado.
406 | Not Acceptable | Os dados enviados no corpo da requisição são inválidos. Em geral, isso significa que os dados enviados não são um JSON válido.
409 | Conflict | O id da requisição corresponde a um id já processado anteriormente. Este status é retornado no caso de requisições duplicadas enviadas ao servidor.
500 | Internal Server Error | Tivemos um problema para processar esta requisição, ao encontrarmos esse erro nossos especialistas são automaticamente notificados e iniciam a análise e solução imediatamente.
503 | Service Unavailable | Você se deparou com uma indisponibilidade, planejada ou não, de infraestrutura dos nossos servidores.

---

# Introdução

URL: /documentation/caas/card_issuance/introduction

Bem vindo à API de Prevenção a Fraudes em emissão de cartões da QI Tech! Você pode utilizar a nossa API para acessar os endpoints, a fim de receber a resposta de uma transação, além de utilizar para atualizar a situação de uma transação.

:::info **Atenção**

Atenção, esta API é direcionada para emissores de cartão, ou seja, empresas que dão o cartão na mão do portador para que ele possa transacionar. Ela tem como objetivo realizar toda a análise de segurança nas transações do seu cliente, evitando fraudes e outros tipos de incidentes (Transações decorrentes de assaltos, por exemplo).
:::

## Como funciona

A integração tem três chamadas. Todas usam a mesma API Key no header `Authorization`.

| Passo | Chamada | O que faz |
| --- | --- | --- |
| 1 | `POST /card_issuance/transaction` | Envia a transação **antes da autorização** e devolve a recomendação em `fraud_status`. |
| 2 | `PUT /card_issuance/transaction/{id}` | Informa o desfecho real (capturada, cancelada, chargeback). Retroalimenta o modelo. |
| 3 | `GET /card_issuance/transaction/{id}` | Consulta o estado atual e o histórico de eventos. |

Além disso, alertas comportamentais sobre o portador são entregues por [Webhook](/documentation/caas/card_issuance/alerts).

O `fraud_status` devolvido no passo 1 assume um destes valores:

| Valor | Ação recomendada |
| --- | --- |
| `automatically_approved` | Gerar o código de autorização. |
| `automatically_declined` | Negar a autorização. |
| `not_analyzed` | A requisição usou `analyze=false`; siga a sua própria decisão. |

:::tip Integre em minutos
A página [Transaction](/documentation/caas/card_issuance/transaction) abre com um **payload mínimo** de 13 campos e traz exemplos prontos em Python, PHP, Node.js, Java, C# e curl.
:::

## Problemas?

Nós não somos uma companhia que se esconde atrás de uma API! Entre em contato com o nosso suporte e nós responderemos o mais rápido possível. Fique à vontade para nos ligar caso deseje uma resposta rápida!

### Adoramos Feedback

Mesmo que você já tenha resolvido o seu problema ou que ele seja muito simples (Até mesmo um typo ou uma organização inadequada que você já entendeu), envie-nos um e-mail, assim nós tornamos a documentação cada vez mais prática e a próxima pessoa não vai precisar sofrer as dores que você sofreu!

## Ambientes

Possuímos dois ambientes para os nossos clientes. As URLs base das APIs são:

* Produção - `https://api.caas.qitech.app/card_issuance/`
* Sandbox - `https://api.sandbox.caas.qitech.app/card_issuance/`

:::danger Aviso Importante!
Não devem ser usados dados reais de pessoas físicas e/ou jurídicas nos ambientes de Sandbox da QI Tech.  
:::

No ambiente de Sandbox, as análises enviadas não são cobradas e são respondidas de acordo com regras pré estabelecidas.

Para a análise de uma transação, a seguinte regra é aplicada sobre o valor da transação:

Mínimo | Máximo | Decisão
------ | ------ | -------
10000 | - | automatically_approved
0 | 9999 | automatically_declined

## Somente HTTPS

Por questão de segurança, toda a comunicação com as APIs da QI Tech deve ser realizada utilizando a comunicação HTTPS. Para evitar que, por desatenção ou outro motivo, sejam feitas chamadas HTTP, este servidor somente disponibiliza a porta 443 com comunicação TLS 1.2. Chamadas realizadas utilizando outros protocolos serão automaticamente negadas.

## Autenticação

> Para autenticar uma chamada, utilize o código seguinte:

```shell
# No shell, você somente precisa adicionar o header adequado em cada requisição
curl "api_endpoint_here"
  -H "Authorization: EXAMPLE-OF-API-KEY"
```

> Substitua a API key 'EXAMPLE-OF-API-KEY' com a sua chave adquirida com o nosso suporte.

Utilizamos uma API Key para permitir acesso a nossa API. Ela provavelmente já foi enviada por e-mail para você. Caso você ainda não tenha recebido a sua chave, envie um e-mail para suporte.caas@qitech.com.br .

Nossa API espera receber a API Key em todas as requisições ao nosso servidor em um header como o abaixo:

`Authorization: EXAMPLE-OF-API-KEY`

:::info **Atenção**

Você deve substituir EXAMPLE-OF-API-KEY com a API Key recebida do suporte.
:::

---

# Padrões

URL: /documentation/caas/card_issuance/standards

Para facilitar a integração e garantir a integridade da informação, foram definidos alguns padrões que são seguidos em toda a API.

## Valores Monetários
> Exemplos:

```
10000
12345
98741
1223
1
0
```

Os valores devem ser enviados como inteiro em centavos.

## Data e Hora com Fuso Horário
> Alguns exemplos:

```
2019-10-15T22:35:12-03:00
2018-05-01T13:32:11+00:00
2019-05-01T00:00:00+00:00
```

É representada conforme a ISO 8601. Neste caso, o fuso-horário é colocado logo após o horário e deve representar o fuso do local onde aquele dado será valido. Por exemplo, se um aluguel estiver marcado para começar às 09:30 no aeroporto de Brasília, o horário enviado deverá ser representado por 09:30-03:00, se o aluguel estiver marcado para começar às 09:30 em Manaus, deverá ser representado por 09:30-04:00.

A máscara utilizada para validação é a seguinte:

`YYYY-MM-ddThh:mm:ss±hh:mm`

## Data e Hora sem Fuso Horario
> Alguns exemplos:

```
2019-10-15T22:35:12Z
2018-05-01T13:32:11Z
2019-05-01T00:00:00Z
```

É representada conforme a ISO 8601. Dados que independem de fuso-horário deverão ser enviados sem ele, sempre em UTC, com a letra Z indicando que este dado está em UTC. O seguinte formato, portanto, será validado:

`YYYY-MM-ddThh:mm:ssZ`

## Data
> Alguns exemplos

``` 
2019-10-15
2019-01-01
2017-03-20
```

---

# Transaction

URL: /documentation/caas/card_issuance/transaction

Transaction

O recurso `Transaction` é o coração da API de antifraude transacional de cartão. Você envia os dados da transação **antes de autorizá-la** e recebe de volta uma recomendação (`fraud_status`) para decidir se gera ou não o código de autorização.

O fluxo completo de integração tem três passos:

1. **`POST /card_issuance/transaction`** — envia a transação para análise e recebe a recomendação.
2. **`PUT /card_issuance/transaction/{id}`** — informa o desfecho real (capturada, cancelada, chargeback). Esse retorno alimenta o modelo e é o que mantém a qualidade das decisões ao longo do tempo.
3. **`GET /card_issuance/transaction/{id}`** — consulta o estado atual e o histórico de eventos de uma transação.

:::tip Comece pelo payload mínimo
Se você quer subir uma integração rápida, vá direto para [Payload mínimo](#payload-minimo). São 13 campos obrigatórios. Todo o resto é opcional e serve para aumentar a acurácia do modelo.
:::

---

## Payload mínimo

Este é o menor corpo aceito pelo `POST /card_issuance/transaction`. Ele contém **apenas** os campos obrigatórios e é suficiente para receber uma decisão.

```json title="Payload mínimo — 13 campos obrigatórios"
{
  "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"
  }
}
```

Resposta:

```json
{
  "id": "678",
  "fraud_status": "automatically_approved"
}
```

:::info Quanto mais dados, melhor a decisão
Os campos opcionais (localização, capacidades do terminal, limites do cartão, endereço do lojista) não são exigidos pela validação, mas alimentam diretamente os modelos e as regras. Uma integração que envia apenas o mínimo funciona, mas tende a produzir mais falsos positivos.
:::

---

## Enviar uma transação para análise

ENDPOINT /card_issuance/transaction
MÉTODO POST

### Query parameters

analyze
boolean
opcional — padrão true
Quando true , a transação passa pelos motores de fraude e a resposta traz uma recomendação. Quando false , a transação é apenas registrada no histórico do portador (sem custo de análise) e a resposta retorna not_analyzed . Use analyze=false para transações que você já decidiu por outros meios, mas que devem compor o comportamento histórico do portador.

:::caution Ao usar `analyze=false`
Envie também `transaction_status` e `response_code` no corpo, informando o desfeito que você já aplicou. Sem isso, a transação fica registrada como `pending` e o histórico do portador perde informação.
:::

### Exemplos de requisição

**Python**

```python
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**

```php
<?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("Antifraude retornou HTTP {$status}: {$body}");
}

$result = json_decode($body, true);
echo $result['fraud_status'];  // automatically_approved
```

**Node.js**

```javascript
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(`Antifraude retornou HTTP ${response.status}`);
  }

  const result = await response.json();
  console.log(result.fraud_status); // automatically_approved
  return result;
}

analyzeTransaction();
```

**Java**

```java
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(
                    "Antifraude retornou HTTP " + response.statusCode() + ": " + response.body());
        }

        System.out.println(response.body());
        // {"id":"678","fraud_status":"automatically_approved"}
    }
}
```

**C#**

```csharp
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(
                $"Antifraude retornou HTTP {(int)response.StatusCode}: {body}");
        }

        Console.WriteLine(body);
        // {"id":"678","fraud_status":"automatically_approved"}
    }
}
```

**curl**

```bash
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"
    }
  }'
```

### Resposta

id
string
O mesmo id que você enviou na requisição.

fraud_status
enum
A recomendação do motor antifraude. Veja fraud_status .

```json
{
  "id": "678",
  "fraud_status": "automatically_approved"
}
```

:::caution Comportamento em caso de indisponibilidade interna
Se os motores de decisão ficarem indisponíveis, a API retorna `automatically_approved` em vez de erro. Isso é intencional: o antifraude nunca deve derrubar a autorização do cartão. Ainda assim, trate timeouts do seu lado com uma política de fallback definida.
:::

---

## Objeto Transaction

### Campos raiz

id
string
obrigatório
Identificador da transação no seu sistema. Máximo de 36 caracteres. Deve ser único por processo de autorização — um id repetido retorna HTTP 409.

cardholder_id
string
obrigatório
Identificador do portador no seu sistema. Máximo de 200 caracteres. É a chave que agrupa o histórico comportamental — use sempre o mesmo valor para o mesmo portador.

amount
integer
obrigatório
Valor da transação em centavos, na moeda de currency . Entre 0 e 1000000000 .

currency
enum
obrigatório
Moeda da transação em ISO 4217 ( BRL , USD , EUR …), correspondente ao ApplicationCurrencyCode da ISO 8583.

installments
integer
obrigatório
Número de parcelas. Entre 0 e 24 . Use 1 para transações à vista.

authorization_date
datetime
obrigatório
Data e hora de início da transação, com fuso horário , no formato YYYY-MM-DDThh:mm:ss±hh:mm . Veja a nota sobre o formato .

authorization_type
enum
obrigatório
Tipo de autorização. Veja authorization_type .

transaction_type
enum
obrigatório
Função utilizada: credit , debit ou prepaid .

pan_entry_mode
enum
obrigatório
Modo de entrada do PAN, derivado do DE 22 (Sub Field 1) da ISO 8583. Veja pan_entry_mode .

pin_sent
boolean
obrigatório
Indica se uma senha foi inserida no terminal.

terminal
object
obrigatório
Dados do terminal. Veja Objeto terminal .

merchant
object
obrigatório
Dados do estabelecimento. Veja Objeto merchant .

card
object
obrigatório
Dados do cartão. Veja Objeto card .

accountholder_id
string
opcional
Identificador do titular da conta, quando diferente do portador do cartão (cartões adicionais, cartões corporativos). Máximo de 200 caracteres.

group_id
string
opcional
Grupo ou categoria a que o portador pertence no seu sistema. Máximo de 200 caracteres. Útil para segmentar regras por carteira.

brl_converted_amount
integer
opcional
Valor da transação convertido para reais, em centavos. Você não precisa enviar este campo — quando currency é diferente de BRL , a QI Tech calcula a conversão internamente; quando é BRL , o valor é igual a amount . Se enviado, é sobrescrito.

location
object
opcional
Localização geográfica da transação. Veja Objeto location .

authentication_type
string
opcional
Método de autenticação aplicado à transação (por exemplo, o resultado de um 3-D Secure). Máximo de 200 caracteres.

risk_assessment
enum
opcional
Classificação de risco atribuída pela bandeira ou pelo adquirente na mensageria. Veja risk_assessment .

cvv_presence
boolean
opcional
Indica se o CVV foi informado na transação. Sinal relevante em transações de e-commerce.

transaction_status
enum
opcional
Situação da transação. Envie no POST apenas quando usar analyze=false e a decisão de autorização já tiver sido tomada. Veja transaction_status .

response_code
string
opcional
Response code da transação conforme o campo Response Code da ISO 8583. Exatamente 1 ou 2 caracteres. Assim como transaction_status , faz sentido no POST apenas com analyze=false .

```json title="Payload completo"
{
  "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"
  }
}
```

:::warning Campos não previstos são rejeitados
O schema usa `additionalProperties: false` em todos os objetos. Qualquer campo fora dos listados aqui faz a requisição retornar **HTTP 400**, mesmo que o restante do payload esteja correto.
:::

#### Formato de `authorization_date`

O validador aceita apenas offsets de fuso terminados em `:00` ou `:30` (por exemplo `-03:00`, `+05:30`, `-04:00`). Sufixo `Z` e offsets como `-03:15` são rejeitados com HTTP 400. Fração de segundo é opcional e aceita de 1 a 6 dígitos:

```text
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 não permitido
```

A mesma regra vale para `card.issuing_date` e `card.unblock_date`.

---

### Objeto `terminal`

country_code
enum
obrigatório
País do terminal em ISO 3166-1 alpha-3 ( BRA , USA , PRT …). Campo Terminal Country Code da ISO 8583.

id
string
opcional
Identificador do terminal enviado pela adquirente. Máximo de 8 caracteres. String vazia é tratada como ausente.

terminal_type
string
opcional
Tipo de terminal conforme TerminalType da ISO 8583. Máximo de 10 caracteres. Veja terminal_type .

pin_entry_capability
boolean
opcional
O terminal permite inserir senha? Campo TerminalPINEntryCapability da ISO 8583.

magnetic_stripe_capability
boolean
opcional
O terminal lê tarja magnética? Campo TerminalPANEntryCapability (DE 123).

contactless_capability
boolean
opcional
O terminal aceita transações por aproximação? Campo TerminalPANEntryCapability (DE 123).

chip_capability
boolean
opcional
O terminal lê chip EMV? Campo TerminalPANEntryCapability (DE 123).

```json
{
  "terminal": {
    "id": "12345678",
    "country_code": "BRA",
    "terminal_type": "5",
    "pin_entry_capability": true,
    "magnetic_stripe_capability": true,
    "contactless_capability": true,
    "chip_capability": true
  }
}
```

:::info Mudança em relação à versão anterior desta documentação
Apenas `country_code` é obrigatório dentro de `terminal`. As versões antigas desta página listavam `terminal_type`, `pin_entry_capability` e `chip_capability` como obrigatórios — eles são opcionais.
:::

---

### Objeto `merchant`

acquirer_id
string
obrigatório
Identificador da adquirente. Máximo de 11 caracteres. Campo Acquirer Identifier (DE 32) da ISO 8583.

merchant_id
string
obrigatório
Identificador do lojista na adquirente. Máximo de 15 caracteres. Campo Merchant Identifier da ISO 8583.

mcc
enum
obrigatório
Merchant Category Code de 4 dígitos, conforme ISO 18245. Aceita apenas MCCs válidos da lista oficial — um código fora da lista retorna HTTP 400.

name
string
opcional
Nome do lojista conforme a mensageria. Máximo de 200 caracteres.

payment_facilitator
string
opcional
Facilitador de pagamento (subadquirente) envolvido na transação. Máximo de 200 caracteres.

sub_merchant
string
opcional
Sublojista, quando a transação passa por um facilitador. Máximo de 200 caracteres.

street
string
opcional
Logradouro do lojista. Campo Card Acceptor Street Address .

city
string
opcional
Cidade do lojista. Campo Card Acceptor City .

region
string
opcional
Região/estado do lojista. Campo Card Acceptor Region Code .

postal_code
string
opcional
CEP do lojista. Campo Card Acceptor Postal Code .

```json
{
  "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"
  }
}
```

---

### Objeto `card`

brand
enum
obrigatório
Bandeira do cartão. Veja brand .

category
enum
obrigatório
Categoria do cartão. Veja category .

bin
string
obrigatório
BIN do cartão. Exatamente 6 dígitos numéricos.

last4
string
obrigatório
Quatro últimos dígitos do cartão. Exatamente 4 dígitos numéricos.

issuer_country_code
enum
obrigatório
País do emissor em ISO 3166-1 alpha-3.

holder_id
string
opcional
Identificador do portador vinculado a este plástico específico, útil quando um mesmo cardholder_id possui múltiplos cartões. Máximo de 200 caracteres.

issuing_date
datetime
opcional
Data e hora de emissão do cartão, com fuso horário. Cartões recém-emitidos são um sinal de risco relevante.

unblock_date
datetime
opcional
Data e hora em que o portador desbloqueou o cartão, com fuso horário.

expiration_date
date
opcional
Data de vencimento do cartão no formato YYYY-MM-DD (use o último dia do mês).

total_credit_limit
integer
opcional
Limite total de crédito do portador, em centavos. Para cartões pré-pagos, o saldo disponível.

used_credit_limit
integer
opcional
Limite já utilizado, em centavos, antes da transação em análise.

```json
{
  "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"
  }
}
```

:::info Mudança em relação à versão anterior desta documentação
`issuing_date` e `expiration_date` **não** são obrigatórios, ao contrário do que a versão anterior desta página indicava. Os obrigatórios em `card` são apenas `brand`, `category`, `bin`, `last4` e `issuer_country_code`.
:::

---

### Objeto `location`

latitude
number
obrigatório se location for enviado
Latitude da transação, entre -90 e 90 .

longitude
number
obrigatório se location for enviado
Longitude da transação, entre -180 e 180 .

altitude
number
opcional
Altitude em metros, entre 0 e 100000 .

```json
{
  "location": {
    "latitude": -23.5613,
    "longitude": -46.6565,
    "altitude": 760
  }
}
```

:::caution Objeto opcional com campos obrigatórios
`location` como um todo é opcional. Mas se você enviar o objeto, `latitude` e `longitude` passam a ser obrigatórios dentro dele. Se não tiver a coordenada, omita o objeto inteiro em vez de enviá-lo vazio.
:::

---

## Enumeradores

### `authorization_type`

| Valor | Significado |
| --- | --- |
| `authorization` | Autorização de compra — MTI x1xx (DMS) e x2xx (SMS). |
| `pre_authorization` | Pré-autorização para reserva de limite (hotel, locação de veículos, postos de combustível) — MTI x1xx (DMS) e *Transaction Type* `60` nos dois primeiros dígitos do Processing Code. |
| `reversal` | Cancelamento de autorização, para liberar limite antes do Clearing/BASE II — MTI x4xx. |

### `transaction_type`

| Valor | Significado |
| --- | --- |
| `credit` | Transação na função crédito. |
| `debit` | Transação na função débito. |
| `prepaid` | Transação na função pré-pago. |

### `pan_entry_mode`

Derivado do DE 22 (Sub Field 1) da ISO 8583.

| Valor | ISO 8583 | Significado |
| --- | --- | --- |
| `unknown` | 00 | Modo de entrada desconhecido. |
| `typed` | 01 | PAN digitado manualmente. |
| `bar_code` | 03 | PAN lido por código de barras. |
| `ocr` | 04 | PAN lido por OCR. |
| `chip` | 05 | PAN lido pelo chip EMV. |
| `track_1` | 06 | PAN lido pela Track 1 da tarja. |
| `contactless` | 07 | PAN lido por aproximação (Contactless EMV). |
| `fallback_typed` | 79 | Falha na leitura de chip/tarja e o PAN foi digitado. Também usado quando a adquirente não está homologada para chip ou tarja. |
| `fallback_magnetic_stripe` | 80 | Falha na leitura do chip e a transação prosseguiu pela tarja magnética. |
| `ecommerce` | 81 | Transação de e-commerce / cartão não presente. |
| `magnetic_stripe` | 90 | Transação por tarja magnética. |
| `manual` | — | Entrada manual dos dados do cartão fora do fluxo de terminal. |
| `stored_credentials` | — | Transação com credenciais armazenadas (assinaturas, cobranças recorrentes, carteiras com cartão tokenizado). |

:::tip `stored_credentials` e recorrências
Transações recorrentes marcadas como `ecommerce` tendem a receber mais recusas do que o esperado, porque o modelo as trata como cartão não presente sem contexto. Use `stored_credentials` sempre que a cobrança usar uma credencial previamente autorizada pelo portador.
:::

### `source_account`

Derivado do Processing Code da ISO 8583. Campo opcional.

| Valor | ISO 8583 | Significado |
| --- | --- | --- |
| `default` | 00 | Padrão ou não especificado. |
| `saving_account` | 10 | Conta poupança. |
| `checking_account` | 20 | Conta corrente. |
| `credit_facility` | 30 | Fatura do cartão. |
| `universal_account` | 40 | Conta universal. |
| `investment_account` | 50 | Conta de investimento. |
| `electronic_purse` | 60 | Saldo armazenado no chip do cartão. |

### `brand`

| Valor | Bandeira |
| --- | --- |
| `visa` | Visa |
| `mastercard` | Mastercard |
| `elo` | Elo |
| `diners_club` | Diners Club |
| `american_express` | American Express |

### `category`

| Valor | Categoria |
| --- | --- |
| `classic` | Classic |
| `gold` | Gold |
| `platinum` | Platinum |
| `black` | Black / Infinite |
| `travel` | Travel |
| `corporate` | Corporate / Business |
| `prepaid` | Pré-pago |
| `postpaid` | Pós-pago |

### `terminal_type`

Conforme *TerminalType* da ISO 8583. Enviado como string.

| Valor | Significado |
| --- | --- |
| `0` | Desconhecido |
| `1` | Nenhum terminal utilizado |
| `2` | Leitor de tarja magnética |
| `3` | Código de barras |
| `4` | OCR |
| `5` | Leitor de tarja magnética e de chip EMV |
| `6` | Apenas entrada por teclado |
| `7` | Leitor de tarja magnética e entrada por teclado |
| `8` | Leitor de tarja, entrada por teclado e chip EMV |
| `9` | Leitor de chip EMV |

### `risk_assessment`

Classificação de risco recebida na mensageria (por exemplo, TRA da PSD2 ou avaliação da bandeira).

| Valor | Significado |
| --- | --- |
| `not_evaluated` | Nenhuma avaliação de risco foi realizada. |
| `low_risk` | A transação foi classificada como de baixo risco. |
| `non_low_risk` | A transação **não** foi classificada como de baixo risco. |

### `transaction_status`

Situação da transação no ciclo de vida da autorização.

| Valor | Significado |
| --- | --- |
| `pending` | Autorização pendente. Estado inicial atribuído automaticamente. |
| `authorized` | Autorizada, aguardando captura. |
| `not_authorized` | Não autorizada pelo emissor. |
| `captured` | Capturada. |
| `cleared` | Recebida no Clearing / BASE II. |
| `cancelled` | Cancelada integralmente. |
| `partially_cancelled` | Cancelada parcialmente. |
| `chargeback` | Recebeu chargeback integral. |
| `partial_chargeback` | Recebeu chargeback parcial. |

:::note `pending` não é enviável
`pending` é atribuído pela própria API quando a transação é criada sem decisão. Ele não é aceito no corpo do `POST` nem do `PUT`.
:::

### `fraud_status`

A recomendação devolvida pelo motor antifraude.

| Valor | Significado | Ação recomendada |
| --- | --- | --- |
| `automatically_approved` | O padrão da transação é compatível com o comportamento do portador. | Gerar o código de autorização. |
| `automatically_declined` | A transação apresenta risco relevante de fraude. | Negar a autorização. |
| `not_analyzed` | A requisição foi enviada com `analyze=false`. Nenhuma análise foi realizada. | Seguir a sua própria decisão. |

---

## Atualizar o status de uma transação

ENDPOINT /card_issuance/transaction/ TRANSACTION_ID
MÉTODO PUT

Informar o desfecho real da transação é o que retroalimenta as regras e o modelo. Sem esse passo, a qualidade das recomendações degrada ao longo do tempo.

O `TRANSACTION_ID` no path é o mesmo `id` que você enviou no `POST`.

### Corpo da requisição

O corpo aceita **duas formas**, escolhidas conforme o status:

**Atualização total**

Para qualquer status que afete a transação por inteiro.

transaction_status
enum
obrigatório
Novo status. Aceita authorized , not_authorized , captured , cleared , cancelled , partially_cancelled , chargeback ou partial_chargeback .

response_code
string
opcional
Response code da ISO 8583. 1 ou 2 caracteres.

```json
{
  "transaction_status": "captured",
  "response_code": "00"
}
```

**Atualização parcial**

Obrigatória para `partially_cancelled` e `partial_chargeback`.

transaction_status
enum
obrigatório
Aceita apenas partially_cancelled ou partial_chargeback .

partial_amount
integer
obrigatório
Valor cancelado/estornado em centavos, de 1 a 1000000000 . Não pode exceder o valor ainda disponível da transação.

response_code
string
opcional
Response code da ISO 8583. 1 ou 2 caracteres.

```json
{
  "transaction_status": "partially_cancelled",
  "partial_amount": 3000,
  "response_code": "00"
}
```

:::danger Status finais não podem ser alterados
Uma transação que já está em `cancelled`, `partially_cancelled`, `chargeback` ou `partial_chargeback` é considerada finalizada. Um novo `PUT` sobre ela retorna **HTTP 400** com o título `Transaction has a final status`.

Consequência prática: você **não** consegue registrar dois cancelamentos parciais em sequência pela API. Planeje enviar o valor consolidado.
:::

Em caso de sucesso, a resposta é **HTTP 200** com corpo vazio (`{}`).

### Exemplos de requisição

**Python**

```python
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 com corpo vazio
```

**PHP**

```php
<?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("Falha ao atualizar status: HTTP {$status} — {$body}");
}
```

**Node.js**

```javascript
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(`Falha ao atualizar status: HTTP ${response.status}`);
  }
}

updateStatus();
```

**Java**

```java
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(
                    "Falha ao atualizar status: HTTP " + response.statusCode()
                            + " — " + response.body());
        }
    }
}
```

**C#**

```csharp
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(
                $"Falha ao atualizar status: HTTP {(int)response.StatusCode} — {body}");
        }
    }
}
```

**curl**

```bash
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" }'
```

---

## Recuperar uma transação

ENDPOINT /card_issuance/transaction/ TRANSACTION_ID
MÉTODO GET

Retorna o estado atual da transação junto com o histórico completo de eventos. Se o `id` não existir para a sua API Key, a resposta é **HTTP 404**.

**Python**

```python
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**

```php
<?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("Transação {$transactionId} não encontrada.");
}

$transaction = json_decode($body, true);
echo $transaction['fraud_status'];
```

**Node.js**

```javascript
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(`Transação ${TRANSACTION_ID} não encontrada.`);
  }

  const transaction = await response.json();
  console.log(transaction.fraud_status, transaction.transaction_status);
  return transaction;
}

getTransaction();
```

**Java**

```java
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("Transação " + TRANSACTION_ID + " não encontrada.");
        }

        System.out.println(response.body());
    }
}
```

**C#**

```csharp
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($"Transação {TransactionId} não encontrada.");
        }

        Console.WriteLine(await response.Content.ReadAsStringAsync());
    }
}
```

**curl**

```bash
curl 'https://api.sandbox.caas.qitech.app/card_issuance/transaction/678' \
  -H 'Authorization: YOUR_API_KEY'
```

### Resposta

A resposta devolve todos os campos que você enviou no `POST`, acrescidos dos campos abaixo.

fraud_status
enum
Recomendação atual do antifraude.

transaction_status
enum
Situação atual da transação.

brl_converted_amount
integer
Valor convertido para reais, em centavos, calculado pela QI Tech.

transaction_events
array
Histórico de mudanças de status da transação, em ordem cronológica.

**Campos de `transaction_events[]`:**

new_status
enum
Status atribuído neste evento.

event_date
datetime
Data e hora do evento, em UTC.

partial_amount
integer
Presente apenas em eventos parciais.

response_code
string
Presente quando informado na atualização.

fraud_events
array
Histórico de decisões do antifraude.

**Campos de `fraud_events[]`:**

new_status
enum
Decisão atribuída neste evento.

event_date
datetime
Data e hora da decisão, em UTC.

decision_metadata
object
Motivo da decisão. Traz reason e reason_description explicando por que a transação foi aprovada ou recusada.

```json
{
  "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"
    }
  ]
}
```

---

## Erros

Todos os erros retornam um corpo JSON com o mesmo formato:

```json
{
  "title": "Duplicated external_id",
  "description": "id: 678 already exists for company 3fa85f64-5717-4562-b3fc-2c963f66afa6"
}
```

| Status | Situação | Como resolver |
| --- | --- | --- |
| 400 | Payload inválido: campo obrigatório ausente, enum fora da lista, formato de data incorreto ou campo não previsto pelo schema. | Confira a `description`, que aponta o campo com problema. |
| 400 | `Transaction has a final status` no `PUT`. | A transação já está em status final e não aceita novas atualizações. |
| 400 | `partial_amount` maior que o valor disponível. | Envie um valor menor ou igual ao saldo ainda não cancelado. |
| 401 | Header `Authorization` ausente ou API Key desativada. | Verifique o header e o status da sua chave. |
| 403 | API Key inválida ou endpoint de uso interno. | Confirme a chave com o [suporte](mailto:suporte.caas@qitech.com.br). |
| 404 | Transação não encontrada para a sua API Key. | Verifique o `id` usado no path. |
| 406 | Corpo da requisição não é um JSON válido. | Verifique o `Content-Type` e a serialização. |
| 409 | `id` já processado anteriormente. | Gere um `id` único por processo de autorização. |
| 500 | Erro interno. | Nossos especialistas são notificados automaticamente. |
| 503 | Indisponibilidade de infraestrutura. | Aplique retry com backoff. |

A lista completa está em [Status HTTP](/documentation/caas/card_issuance/http_status).

---

## Testando no Sandbox

No Sandbox as análises não são cobradas e a decisão é determinística, baseada apenas no valor da transação:

| `amount` | `fraud_status` retornado |
| --- | --- |
| `>= 10000` (R$ 100,00 ou mais) | `automatically_approved` |
| `<= 9999` (até R$ 99,99) | `automatically_declined` |

Base URL de Sandbox: `https://api.sandbox.caas.qitech.app`

:::danger Aviso importante
Não utilize dados reais de pessoas físicas ou jurídicas no ambiente de Sandbox da QI Tech.
:::

---

## Checklist de integração

- [ ] `POST /card_issuance/transaction` com o payload mínimo retornando `200` no Sandbox.
- [ ] `id` único garantido por processo de autorização (teste o `409` reenviando o mesmo `id`).
- [ ] `cardholder_id` estável para o mesmo portador entre transações.
- [ ] `authorization_date` no formato com offset `:00` ou `:30`.
- [ ] Valores monetários em centavos, como inteiros.
- [ ] Tratamento de timeout com política de fallback definida (aprovar ou negar por conta própria).
- [ ] `PUT` enviado em todos os desfechos: captura, cancelamento, chargeback.
- [ ] Webhook de [Alertas de Portadores](/documentation/caas/card_issuance/alerts) configurado com o suporte.