Skip to main content
POST
/
transactions
Crear transacción
curl --request POST \
  --url http://api.gu1.ai/transactions \
  --header 'Authorization: Bearer <token>' \
  --header 'Content-Type: application/json' \
  --data '
{
  "externalId": "<string>",
  "type": "<string>",
  "amount": 123,
  "currency": "<string>",
  "exchangeRate": 123,
  "status": "<string>",
  "paymentMethod": "<string>",
  "description": "<string>",
  "category": "<string>",
  "transactedAt": "<string>",
  "executeRules": true,
  "asyncRules": true,
  "configRulesExecution": {},
  "validateExistingEntity": true,
  "riskMatrixId": [
    "<string>"
  ],
  "riskMatrixIds": [
    "<string>"
  ],
  "originEntityId": "<string>",
  "originExternalId": "<string>",
  "originTaxId": "<string>",
  "originName": "<string>",
  "originCountry": "<string>",
  "originDetails": {},
  "destinationEntityId": "<string>",
  "destinationExternalId": "<string>",
  "destinationTaxId": "<string>",
  "destinationName": "<string>",
  "destinationCountry": "<string>",
  "destinationDetails": {},
  "locationDetails": {},
  "deviceDetails": {},
  "channel": "<string>",
  "reason": "<string>",
  "timeZone": "<string>",
  "metadata": {}
}
'
import requests

url = "http://api.gu1.ai/transactions"

payload = {
"externalId": "<string>",
"type": "<string>",
"amount": 123,
"currency": "<string>",
"exchangeRate": 123,
"status": "<string>",
"paymentMethod": "<string>",
"description": "<string>",
"category": "<string>",
"transactedAt": "<string>",
"executeRules": True,
"asyncRules": True,
"configRulesExecution": {},
"validateExistingEntity": True,
"riskMatrixId": ["<string>"],
"riskMatrixIds": ["<string>"],
"originEntityId": "<string>",
"originExternalId": "<string>",
"originTaxId": "<string>",
"originName": "<string>",
"originCountry": "<string>",
"originDetails": {},
"destinationEntityId": "<string>",
"destinationExternalId": "<string>",
"destinationTaxId": "<string>",
"destinationName": "<string>",
"destinationCountry": "<string>",
"destinationDetails": {},
"locationDetails": {},
"deviceDetails": {},
"channel": "<string>",
"reason": "<string>",
"timeZone": "<string>",
"metadata": {}
}
headers = {
"Authorization": "Bearer <token>",
"Content-Type": "application/json"
}

response = requests.post(url, json=payload, headers=headers)

print(response.text)
const options = {
method: 'POST',
headers: {Authorization: 'Bearer <token>', 'Content-Type': 'application/json'},
body: JSON.stringify({
externalId: '<string>',
type: '<string>',
amount: 123,
currency: '<string>',
exchangeRate: 123,
status: '<string>',
paymentMethod: '<string>',
description: '<string>',
category: '<string>',
transactedAt: '<string>',
executeRules: true,
asyncRules: true,
configRulesExecution: {},
validateExistingEntity: true,
riskMatrixId: ['<string>'],
riskMatrixIds: ['<string>'],
originEntityId: '<string>',
originExternalId: '<string>',
originTaxId: '<string>',
originName: '<string>',
originCountry: '<string>',
originDetails: {},
destinationEntityId: '<string>',
destinationExternalId: '<string>',
destinationTaxId: '<string>',
destinationName: '<string>',
destinationCountry: '<string>',
destinationDetails: {},
locationDetails: {},
deviceDetails: {},
channel: '<string>',
reason: '<string>',
timeZone: '<string>',
metadata: {}
})
};

fetch('http://api.gu1.ai/transactions', options)
.then(res => res.json())
.then(res => console.log(res))
.catch(err => console.error(err));
<?php

$curl = curl_init();

curl_setopt_array($curl, [
CURLOPT_URL => "http://api.gu1.ai/transactions",
CURLOPT_RETURNTRANSFER => true,
CURLOPT_ENCODING => "",
CURLOPT_MAXREDIRS => 10,
CURLOPT_TIMEOUT => 30,
CURLOPT_HTTP_VERSION => CURL_HTTP_VERSION_1_1,
CURLOPT_CUSTOMREQUEST => "POST",
CURLOPT_POSTFIELDS => json_encode([
'externalId' => '<string>',
'type' => '<string>',
'amount' => 123,
'currency' => '<string>',
'exchangeRate' => 123,
'status' => '<string>',
'paymentMethod' => '<string>',
'description' => '<string>',
'category' => '<string>',
'transactedAt' => '<string>',
'executeRules' => true,
'asyncRules' => true,
'configRulesExecution' => [

],
'validateExistingEntity' => true,
'riskMatrixId' => [
'<string>'
],
'riskMatrixIds' => [
'<string>'
],
'originEntityId' => '<string>',
'originExternalId' => '<string>',
'originTaxId' => '<string>',
'originName' => '<string>',
'originCountry' => '<string>',
'originDetails' => [

],
'destinationEntityId' => '<string>',
'destinationExternalId' => '<string>',
'destinationTaxId' => '<string>',
'destinationName' => '<string>',
'destinationCountry' => '<string>',
'destinationDetails' => [

],
'locationDetails' => [

],
'deviceDetails' => [

],
'channel' => '<string>',
'reason' => '<string>',
'timeZone' => '<string>',
'metadata' => [

]
]),
CURLOPT_HTTPHEADER => [
"Authorization: Bearer <token>",
"Content-Type: application/json"
],
]);

$response = curl_exec($curl);
$err = curl_error($curl);

curl_close($curl);

if ($err) {
echo "cURL Error #:" . $err;
} else {
echo $response;
}
package main

import (
"fmt"
"strings"
"net/http"
"io"
)

func main() {

url := "http://api.gu1.ai/transactions"

payload := strings.NewReader("{\n \"externalId\": \"<string>\",\n \"type\": \"<string>\",\n \"amount\": 123,\n \"currency\": \"<string>\",\n \"exchangeRate\": 123,\n \"status\": \"<string>\",\n \"paymentMethod\": \"<string>\",\n \"description\": \"<string>\",\n \"category\": \"<string>\",\n \"transactedAt\": \"<string>\",\n \"executeRules\": true,\n \"asyncRules\": true,\n \"configRulesExecution\": {},\n \"validateExistingEntity\": true,\n \"riskMatrixId\": [\n \"<string>\"\n ],\n \"riskMatrixIds\": [\n \"<string>\"\n ],\n \"originEntityId\": \"<string>\",\n \"originExternalId\": \"<string>\",\n \"originTaxId\": \"<string>\",\n \"originName\": \"<string>\",\n \"originCountry\": \"<string>\",\n \"originDetails\": {},\n \"destinationEntityId\": \"<string>\",\n \"destinationExternalId\": \"<string>\",\n \"destinationTaxId\": \"<string>\",\n \"destinationName\": \"<string>\",\n \"destinationCountry\": \"<string>\",\n \"destinationDetails\": {},\n \"locationDetails\": {},\n \"deviceDetails\": {},\n \"channel\": \"<string>\",\n \"reason\": \"<string>\",\n \"timeZone\": \"<string>\",\n \"metadata\": {}\n}")

req, _ := http.NewRequest("POST", url, payload)

req.Header.Add("Authorization", "Bearer <token>")
req.Header.Add("Content-Type", "application/json")

res, _ := http.DefaultClient.Do(req)

defer res.Body.Close()
body, _ := io.ReadAll(res.Body)

fmt.Println(string(body))

}
HttpResponse<String> response = Unirest.post("http://api.gu1.ai/transactions")
.header("Authorization", "Bearer <token>")
.header("Content-Type", "application/json")
.body("{\n \"externalId\": \"<string>\",\n \"type\": \"<string>\",\n \"amount\": 123,\n \"currency\": \"<string>\",\n \"exchangeRate\": 123,\n \"status\": \"<string>\",\n \"paymentMethod\": \"<string>\",\n \"description\": \"<string>\",\n \"category\": \"<string>\",\n \"transactedAt\": \"<string>\",\n \"executeRules\": true,\n \"asyncRules\": true,\n \"configRulesExecution\": {},\n \"validateExistingEntity\": true,\n \"riskMatrixId\": [\n \"<string>\"\n ],\n \"riskMatrixIds\": [\n \"<string>\"\n ],\n \"originEntityId\": \"<string>\",\n \"originExternalId\": \"<string>\",\n \"originTaxId\": \"<string>\",\n \"originName\": \"<string>\",\n \"originCountry\": \"<string>\",\n \"originDetails\": {},\n \"destinationEntityId\": \"<string>\",\n \"destinationExternalId\": \"<string>\",\n \"destinationTaxId\": \"<string>\",\n \"destinationName\": \"<string>\",\n \"destinationCountry\": \"<string>\",\n \"destinationDetails\": {},\n \"locationDetails\": {},\n \"deviceDetails\": {},\n \"channel\": \"<string>\",\n \"reason\": \"<string>\",\n \"timeZone\": \"<string>\",\n \"metadata\": {}\n}")
.asString();
require 'uri'
require 'net/http'

url = URI("http://api.gu1.ai/transactions")

http = Net::HTTP.new(url.host, url.port)

request = Net::HTTP::Post.new(url)
request["Authorization"] = 'Bearer <token>'
request["Content-Type"] = 'application/json'
request.body = "{\n \"externalId\": \"<string>\",\n \"type\": \"<string>\",\n \"amount\": 123,\n \"currency\": \"<string>\",\n \"exchangeRate\": 123,\n \"status\": \"<string>\",\n \"paymentMethod\": \"<string>\",\n \"description\": \"<string>\",\n \"category\": \"<string>\",\n \"transactedAt\": \"<string>\",\n \"executeRules\": true,\n \"asyncRules\": true,\n \"configRulesExecution\": {},\n \"validateExistingEntity\": true,\n \"riskMatrixId\": [\n \"<string>\"\n ],\n \"riskMatrixIds\": [\n \"<string>\"\n ],\n \"originEntityId\": \"<string>\",\n \"originExternalId\": \"<string>\",\n \"originTaxId\": \"<string>\",\n \"originName\": \"<string>\",\n \"originCountry\": \"<string>\",\n \"originDetails\": {},\n \"destinationEntityId\": \"<string>\",\n \"destinationExternalId\": \"<string>\",\n \"destinationTaxId\": \"<string>\",\n \"destinationName\": \"<string>\",\n \"destinationCountry\": \"<string>\",\n \"destinationDetails\": {},\n \"locationDetails\": {},\n \"deviceDetails\": {},\n \"channel\": \"<string>\",\n \"reason\": \"<string>\",\n \"timeZone\": \"<string>\",\n \"metadata\": {}\n}"

response = http.request(request)
puts response.read_body
{
  "transaction": {},
  "rulesExecutionSummary": {},
  "asyncRules": true,
  "rulesEvaluationStatus": "<string>"
}

Resumen

Crea una nueva transacción. Con executeRules: true (por defecto), el motor de reglas corre en forma síncrona y la respuesta incluye un rulesExecutionSummary completo cuando las reglas terminan en la misma request. Si no enviás asyncRules (por defecto false), el comportamiento es el de siempre — las integraciones existentes no cambian.

Endpoint

POST http://api.gu1.ai/transactions

Autenticación

Requiere una API key válida en el header Authorization:
Authorization: Bearer YOUR_API_KEY

Parámetros de query

asyncRules
boolean
default:"false"
Con true y executeRules distinto de false, la transacción se crea al instante y la evaluación de reglas se encola en background. La respuesta HTTP vuelve antes de que terminen las reglas. El query param tiene prioridad sobre el mismo campo en el body JSON.Valores truthy aceptados: true, 1, "true", "1", "yes".No aplica en endpoints batch — solo POST /transactions (creación unitaria).

Cuerpo de la petición

Campos requeridos

externalId
string
required
Tu identificador único para esta transacción en tu sistema
type
string
required
Tipo de transacción. Opciones:
  • PAYMENT - Pago
  • TRANSFER - Transferencia
  • WITHDRAWAL - Retiro
  • DEPOSIT - Depósito
  • REFUND - Reembolso
  • CHARGEBACK - Contracargo
  • REVERSAL - Reversión
  • FEE - Comisión
  • ADJUSTMENT - Ajuste
  • OTHER - Otro
amount
number
required
Monto (debe ser cero o positivo)
currency
string
required
Código de moneda (3-4 caracteres, ej. “USD”, “EUR”, “BRL”)
exchangeRate
number
Tipo de cambio opcional, usado solo cuando la conversión automática a la moneda base de la organización no está disponible (error del proveedor, timeout o par no soportado). Si omitís este campo, el comportamiento es el de siempre — Gueno consulta el servicio de monedas como hoy.Semántica: unidades de moneda base por 1 unidad de currency. Monto normalizado en la moneda base de la org: montoNormalizado = amount × exchangeRate.Se ignora si la conversión automática tiene éxito (gana la tasa del proveedor).Necesario cuando la conversión automática no está disponible para monedas no convertibles (ver abajo). Ver Conversión de moneda.

Campos opcionales

status
string
default:"CREATED"
Estado. Opciones:
  • CREATED - Creada (por defecto)
  • PROCESSING - En proceso
  • SUSPENDED - Suspendida
  • SENT - Enviada
  • EXPIRED - Expirada
  • DECLINED - Rechazada
  • REFUNDED - Reembolsada
  • SUCCESSFUL - Exitosa
paymentMethod
string
Método de pago. Opciones: CARD, ACH, PIX, TED, BOLETO, WALLET, SWIFT, IBAN, CBU, CVU, DEBIN, GENERIC_BANK_ACCOUNT, MPESA, UPI, CHECK, ECHECK, QR_CODE, ONLINE_PAYMENT, WITHDRAWAL_ORDER
description
string
Descripción o notas
category
string
Categoría de la transacción
transactedAt
string
Fecha/hora ISO 8601 del hecho (por defecto: momento de creación). Se guarda en UTC. Usá Z u offset ±HH:MM en el string, o datetime sin offset junto con timeZone (hora local en ese huso).
executeRules
boolean
default:"true"
Si se ejecuta el motor de reglas. Con false se omiten reglas por completo (sync y async).
asyncRules
boolean
default:"false"
Misma semántica que el query asyncRules. Para ingestión de alto volumen: persistir la transacción rápido y revisar alertas después en gu1. Requiere executeRules: true (default). Ignorado si executeRules es false.
configRulesExecution
object
Ajuste opcional de la ejecución de reglas tras el alta (sync o async). No desactiva acciones createAlert ni la consolidación de investigaciones.
CampoTipoDefault
notificationsbooleantrue en la mayoría de organizaciones; false en Paytime prod (3bc1f621-27d4-423e-9d64-86680bec2388) si se omite
Con notifications: false, gu1 omite notificaciones in-app de la evaluación de reglas (matriz de riesgo / cambios de estado). Alertas e investigaciones siguen el flujo normal.Legacy KYT POST /legacy/kyt/verifyTransaction: mismo objeto en el body Gu2 como configRulesExecution; si se omite, Paytime prod recibe notifications: false por defecto (igual que POST /transactions).
validateExistingEntity
boolean
default:"false"
Con false (por defecto): si enviás originExternalId / destinationExternalId (o tax en raíz) y no hay entidad, la transacción se crea igual (sin vínculo), igual que antes.Con true: solo se valida un extremo (origen o destino) si enviaste al menos un identificador para ese extremo (originEntityId, originExternalId o originTaxId; lo mismo para destino). Cada campo enviado debe resolver a una persona/empresa existente en tu organización; si no, la API responde 400 con INVALID_ENTITY_REFERENCES y no crea la transacción. Si no enviás ningún identificador de origen (o de destino), ese extremo no se valida.

Matrices de riesgo (opcional)

riskMatrixId
string | string[]
Compatible con clientes legacy: un UUID o un array de UUIDs de matrices de la organización. Si se envía una lista no vacía, solo se evalúan reglas activas asociadas a esas matrices (sin mezclar con reglas “sueltas” solo por triggers). Omití riskMatrixId y riskMatrixIds para conservar el comportamiento histórico basado en triggers.
riskMatrixIds
string[]
Forma preferida para varias matrices: lista ordenada de UUIDs. Tiene precedencia sobre riskMatrixId cuando viene informada y no vacía.

Origen (entidad)

Cómo se vincula el origen en gu1 (en orden; se aplica el primer criterio que resuelva):
  1. ID de entidad — si enviás originEntityId, se usa esa entidad (debe existir en tu organización).
  2. ID externo — si no enviaste originEntityId pero sí originExternalId y existe una persona/empresa con el mismo externalId, se vincula automáticamente.
  3. Tercer fallback: tax / documento (root) — si aún no hay vinculación, pero enviás en la raíz de la transacción originTaxId, el API busca una persona/empresa cuyo taxId coincida tras normalizar (solo letras y números, sin puntuación ni espacios; la comparación ignora mayúsculas).
Si resuelve el paso 2 o 3, se rellenan originEntityId y, si no los enviaste, se pueden completar originName y originCountry desde la entidad.Denormalización canónica (origen vinculado): cuando el origen queda vinculado a una persona/empresa — enviaste originEntityId, o el auto-link resolvió por originExternalId / originTaxId — gu1 sincroniza las columnas denormalizadas desde la fila de la entidad antes del insert:
Campo en la transacciónOrigen en entidad¿Se conserva lo que mandó el cliente?
originEntityIdentities.idSe setea en auto-link; no cambia si ya enviaste un UUID válido
originTaxIdentities.tax_idNo — siempre se pisa con la entidad vinculada
originExternalIdentities.external_idNo — siempre se pisa con la entidad vinculada
Si la entidad no tiene taxId o externalId, la columna correspondiente en la transacción queda en null, aunque hayas enviado valores en el body.Nota para integradores: podés mandar cualquier combinación de identificadores para vincular (precedencia: originEntityIdoriginExternalIdoriginTaxId). Tras el vínculo, los valores persistidos de originTaxId / originExternalId reflejan la entidad en gu1, no necesariamente lo que tipeaste. Esto alinea reglas transaccionales con eventos de usuario (entityId, entityExternalId, taxId en el evento).Si no hay coincidencia, la transacción se crea igual (comportamiento por defecto); quedan guardados los campos que enviaste, sin enlace a entidad. Con validateExistingEntity: true, si enviaste algún identificador de origen y no hay entidad, la API responde 400 y no crea la transacción.
(En originDetails también podés enviar un tax para el grafo, pero eso no vincula entidades: para vincular por documento usá originTaxId a nivel raíz.)
originEntityId
string
UUID de la entidad origen en gu1
originExternalId
string
Tu ID externo de la entidad origen. Segundo criterio de vinculación si omitís originEntityId. Tras vincular, el valor guardado es entities.external_id (no se conserva el del cliente si difiere).
originTaxId
string
CUIT o identificador fiscal / documento del origen a nivel raíz (no dentro de originDetails). Es el tercer criterio, después de originEntityId y originExternalId. Se compara con el taxId de entidades (persona/empresa) con la misma normalización alfanumérica. Si hay match, se rellenan enlace, nombre y país. Tras vincular, el valor guardado es entities.tax_id. Opcional, máx. 50 caracteres.
originName
string
Nombre del origen
originCountry
string
Código de país ISO 2 del origen (ej. “US”, “BR”, “AR”)
originDetails
object
Detalles del origen (dispositivo, cuenta, etc.). Ver Esquema de payment details para estructuras sugeridas. Campos extra permitidos. Si el origen no es una entidad en gu1, podés enviar identificadores en originDetails.paymentDetails para agrupar nodos pseudo en grafos (taxId, cbu, cvu, pixKey, clabe, alias, iban, holderId, debinId, cardFingerprint, accountNumber + bankCode opcional — ver prioridad en el schema).

Destino (entidad)

Vinculación del destino: misma precedencia que origen: destinationEntityIddestinationExternalIddestinationTaxId.Cuando el destino queda vinculado, gu1 siempre sincroniza destinationTaxId y destinationExternalId desde la entidad (mismas reglas que origen). Si no hay vínculo, se conservan los valores enviados.
destinationEntityId
string
UUID de la entidad destino en gu1
destinationExternalId
string
Tu ID externo de la entidad destino. Tras vincular, se guarda como entities.external_id.
destinationTaxId
string
CUIT o documento del destino a nivel raíz; tercer criterio de vinculación, después de destinationEntityId y destinationExternalId. Misma lógica que originTaxId (normalización, enriquecimiento de nombre y país). Tras vincular, se guarda como entities.tax_id. Opcional, máx. 50 caracteres.
destinationName
string
Nombre del destino
destinationCountry
string
Código de país ISO 2 del destino
destinationDetails
object
Detalles del destino (comercio, cuenta, etc.). Campos opcionales: mcc, merchantId, merchantName, deviceId, accountNumber, bankCode, bankName, etc. Campos extra permitidos. Si el destino no es una entidad en gu1, podés enviar los mismos identificadores en destinationDetails.paymentDetails para agrupar nodos pseudo (misma prioridad que origen — ver Payment Details Schema).

Ubicación y dispositivo

locationDetails
object
Ubicación: country, city, region, address, latitude, longitude, postalCode, etc.
deviceDetails
object
Dispositivo: deviceId, platform, osName, model, ipAddress, isVpn, isTor, etc.
channel
string
Canal (máx. 50 caracteres): mobile_app, web_browser, pos_terminal, api, atm, etc.
reason
string
Motivo del resultado (opcional). Ver Enum de motivos. Si se omite se usa WITHOUT_REASON.
timeZone
string
Zona horaria IANA opcional (independiente de operationalHours de la entidad). Valores de transaction_time_zone. Si se omite, queda null.Normalización de transactedAt: si transactedAt trae Z (como exige el validador de este endpoint), se guarda ese instante en UTC; timeZone es metadata opcional y no se usa para parsear. Con datetime local + timeZone (herramientas internas/lote), la API puede convertir hora local a UTC. Las integraciones que no envían timeZone siguen igual que antes. Las reglas de horario operativo usan el instante guardado + operationalHours.timezone de la entidad, no transaction.timeZone.Lista completa: Enum zona horaria.
metadata
object
Metadatos adicionales: tags, purpose, enhanced_due_diligence, block_reason, compliance_alert, etc. Campos custom permitidos.

Respuesta

transaction
object
La transacción creada (objeto). Incluye: id, externalId, organizationId, type, amount (string), currency, status, riskScore (string), flagged, channel, reason, timeZone (string | null), originDetails, destinationDetails, locationDetails, deviceDetails, processingTimeMs, processedAt, transactedAt, createdAt, updatedAt.
rulesExecutionSummary
object
Presente cuando executeRules es true.Síncrono (default): completo tras terminar las reglas en la misma request.Async (asyncRules=true): placeholder con success: true, rulesHit / rulesNoHit vacíos y matchedRulesCount: 0. Alertas y score se actualizan cuando termina el procesamiento en background.Omitido si executeRules es false. Ver Resumen de Ejecución de Reglas.
asyncRules
boolean
Presente y true solo cuando las reglas fueron encoladas. Omitido en el flujo síncrono por defecto.
rulesEvaluationStatus
string
En modo async: "queued". Omitido cuando las reglas corrieron síncronamente.

Ejemplo básico

curl -X POST http://api.gu1.ai/transactions \
  -H "Authorization: Bearer YOUR_API_KEY" \
  -H "Content-Type: application/json" \
  -d '{
    "externalId": "txn_12345",
    "type": "PAYMENT",
    "amount": 150.50,
    "currency": "USD",
    "originExternalId": "customer_001",
    "destinationExternalId": "merchant_456",
    "description": "Compra online"
  }'
const response = await fetch('http://api.gu1.ai/transactions', {
  method: 'POST',
  headers: {
    'Authorization': 'Bearer YOUR_API_KEY',
    'Content-Type': 'application/json'
  },
  body: JSON.stringify({
    externalId: 'txn_12345',
    type: 'PAYMENT',
    amount: 150.50,
    currency: 'USD',
    originExternalId: 'customer_001',
    destinationExternalId: 'merchant_456',
    description: 'Compra online'
  })
});
const data = await response.json();
console.log(data.transaction);
import requests
response = requests.post(
    'http://api.gu1.ai/transactions',
    headers={
        'Authorization': 'Bearer YOUR_API_KEY',
        'Content-Type': 'application/json'
    },
    json={
        'externalId': 'txn_12345',
        'type': 'PAYMENT',
        'amount': 150.50,
        'currency': 'USD',
        'originExternalId': 'customer_001',
        'destinationExternalId': 'merchant_456',
        'description': 'Compra online'
    }
)
transaction = response.json()['transaction']
print(transaction)

Reglas async (ingestión de alto volumen)

Para respuestas rápidas y revisar alertas después en gu1. Las reglas corren en background; rulesExecutionSummary.rulesHit viene vacío en la respuesta HTTP.
curl -X POST "http://api.gu1.ai/transactions?asyncRules=true" \
  -H "Authorization: Bearer YOUR_API_KEY" \
  -H "Content-Type: application/json" \
  -d '{
    "externalId": "txn_nightly_001",
    "type": "PAYMENT",
    "amount": 150.50,
    "currency": "BRL",
    "destinationExternalId": "merchant_456"
  }'
{
  "transaction": { "id": "...", "externalId": "txn_nightly_001", "status": "SUCCESSFUL" },
  "rulesExecutionSummary": {
    "success": true,
    "rulesHit": [],
    "rulesNoHit": [],
    "totalScore": 0,
    "matchedRulesCount": 0,
    "executionTimeMs": 0
  },
  "asyncRules": true,
  "rulesEvaluationStatus": "queued"
}
No uses la respuesta síncrona para aprobar o bloquear pagos cuando asyncRules=true. No hay un segundo webhook al terminar las reglas en background — monitoreá alertas en gu1 o consultá APIs de transacción/investigación.

Conversión de moneda

Cuando currency difiere de la moneda base de la organización (default USD), Gueno obtiene el tipo de cambio automáticamente. El comportamiento no cambia si omitís exchangeRate.

Orden de resolución

PasoCondiciónResultado
1currency igual a la baserateSource: no-conversion, exchangeRate: 1
2Conversión automática exitosaTasa del proveedor (ms-provider, caché, etc.)
3Falla la conversión y enviaste exchangeRaterateSource: client-provided
4Falla la conversión, sin exchangeRaterateSource: conversion-unavailable; sin monto normalizado en moneda base

Semántica de exchangeRate

  • Dirección: unidades de moneda base por 1 unidad de currency (igual que las tasas del proveedor).
  • Fórmula: montoNormalizado = amount × exchangeRate (persistido en la moneda base de la org).
  • No es override: si el paso 2 tiene éxito, se ignora exchangeRate del cliente.

Monedas no convertibles (conversión automática)

Gueno no obtiene tipo de cambio automático para estos códigos hoy. La conversión automática queda no disponible salvo que envíes exchangeRate:
MonedaCódigoNotas
WorldcoinWLDEl cliente debe enviar exchangeRate para monto normalizado en base
EthereumETHEl cliente debe enviar exchangeRate para monto normalizado en base
El resto de códigos ISO sigue el flujo automático habitual (proveedor o histórico con transactedAt). Enviá exchangeRate si necesitás monto normalizado en base para reglas y reportes.

Ejemplo — WLD con tasa del cliente

{
  "externalId": "txn-wld-001",
  "type": "PAYMENT",
  "amount": 26.16,
  "currency": "WLD",
  "exchangeRate": 2.41,
  "destinationExternalId": "merchant_001"
}
Respuesta (base USD — monto normalizado 63.05, rateSource: client-provided):
{
  "transaction": {
    "amount": "26.16",
    "currency": "WLD",
    "exchangeRate": "2.41",
    "rateSource": "client-provided",
    "currenciesExchange": [
      { "currency": "USD", "exchangeRate": 2.41, "value": 63.05 }
    ]
  }
}
El batch (POST /transactions/batch, upload, JSON) acepta el mismo exchangeRate opcional en cada fila, con la misma semántica.
Con Redis configurado, los jobs van a la cola transaction-rules-eval (workers dedicados si DISABLE_INPROCESS_BULL_WORKERS=true). Si Redis no está o falla el enqueue, la API igual responde 200 con asyncRules: true y ejecuta las reglas en el proceso de esa instancia (adecuado para dev o una sola instancia; alto volumen multi-instancia: Redis + workers).

Ejemplo de respuesta

{
  "transaction": {
    "id": "550e8400-e29b-41d4-a716-446655440000",
    "externalId": "txn_67890",
    "organizationId": "8e2f89ab-c216-4eb4-90eb-ca5d44499aaa",
    "type": "TRANSFER",
    "status": "CREATED",
    "amount": "1000.00",
    "currency": "BRL",
    "amountInUsd": "1000.00",
    "paymentMethod": "PIX",
    "riskScore": "15",
    "flagged": false,
    "channel": "mobile_app",
    "reason": "WITHOUT_REASON",
    "originDetails": {},
    "destinationDetails": {},
    "processingTimeMs": "120",
    "processedAt": "2024-10-03T14:30:00.000Z",
    "createdAt": "2024-10-03T14:30:00.000Z",
    "updatedAt": "2024-10-03T14:30:00.000Z"
  },
  "rulesExecutionSummary": {
    "rulesHit": [],
    "rulesNoHit": [],
    "actionsExecuted": {},
    "totalScore": 15
  }
}

Errores

400 - Datos inválidos

{
  "success": false,
  "error": {
    "code": "VALIDATION_ERROR",
    "message": "Validation failed",
    "details": ["Amount must be zero or a positive number", "Currency must be at least 3 characters"]
  }
}

409 - Transacción duplicada

{
  "success": false,
  "error": {
    "code": "DUPLICATE_TRANSACTION",
    "message": "Transaction with this external_id already exists",
    "details": { "field": "external_id", "value": "txn_12345" }
  }
}

429 - Límite de tasa

{
  "error": "Rate limit exceeded",
  "code": "RATE_LIMIT_EXCEEDED",
  "retryAfter": 3600
}

Próximos pasos