> ## Documentation Index
> Fetch the complete documentation index at: https://docs.gu1.ai/llms.txt
> Use this file to discover all available pages before exploring further.

# Crear transacción

> Crea una nueva transacción financiera para monitoreo y análisis — en la API de monitoreo transaccional gu1 para fraude y AML, con ejemplos para create.

## 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:

```bash theme={null}
Authorization: Bearer YOUR_API_KEY
```

## Parámetros de query

<ParamField query="asyncRules" type="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).
</ParamField>

## Cuerpo de la petición

### Campos requeridos

<ParamField body="externalId" type="string" required>
  Tu identificador único para esta transacción en tu sistema
</ParamField>

<ParamField body="type" 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
</ParamField>

<ParamField body="amount" type="number" required>
  Monto (debe ser cero o positivo)
</ParamField>

<ParamField body="currency" type="string" required>
  Código de moneda (3-4 caracteres, ej. "USD", "EUR", "BRL")
</ParamField>

<ParamField body="exchangeRate" type="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](#conversión-de-moneda).
</ParamField>

### Campos opcionales

<ParamField body="status" type="string" default="CREATED">
  Estado. Opciones:

  * `CREATED` - Creada (por defecto)
  * `PROCESSING` - En proceso
  * `SUSPENDED` - Suspendida
  * `SENT` - Enviada
  * `EXPIRED` - Expirada
  * `DECLINED` - Rechazada
  * `REFUNDED` - Reembolsada
  * `SUCCESSFUL` - Exitosa
</ParamField>

<ParamField body="paymentMethod" type="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`
</ParamField>

<ParamField body="description" type="string">
  Descripción o notas
</ParamField>

<ParamField body="category" type="string">
  Categoría de la transacción
</ParamField>

<ParamField body="transactedAt" type="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).
</ParamField>

<ParamField body="executeRules" type="boolean" default="true">
  Si se ejecuta el motor de reglas. Con `false` se omiten reglas por completo (sync y async).
</ParamField>

<ParamField body="asyncRules" type="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`.
</ParamField>

<ParamField body="configRulesExecution" type="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.

  | Campo           | Tipo      | Default                                                                                                                  |
  | --------------- | --------- | ------------------------------------------------------------------------------------------------------------------------ |
  | `notifications` | `boolean` | `true` 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`).
</ParamField>

<ParamField body="validateExistingEntity" type="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.
</ParamField>

### Matrices de riesgo (opcional)

<ParamField body="riskMatrixId" type="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.
</ParamField>

<ParamField body="riskMatrixIds" type="string[]">
  Forma preferida para **varias** matrices: lista ordenada de UUIDs. Tiene precedencia sobre `riskMatrixId` cuando viene informada y no vacía.
</ParamField>

### Origen (entidad)

<Info>
  **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ón | Origen en entidad      | ¿Se conserva lo que mandó el cliente?                          |
  | ----------------------- | ---------------------- | -------------------------------------------------------------- |
  | `originEntityId`        | `entities.id`          | Se setea en auto-link; no cambia si ya enviaste un UUID válido |
  | `originTaxId`           | `entities.tax_id`      | **No** — siempre se pisa con la entidad vinculada              |
  | `originExternalId`      | `entities.external_id` | **No** — 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: `originEntityId` → `originExternalId` → `originTaxId`). 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](/es/api-reference/events/create) (`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**.)
</Info>

<ParamField body="originEntityId" type="string">
  UUID de la entidad origen en gu1
</ParamField>

<ParamField body="originExternalId" type="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).
</ParamField>

<ParamField body="originTaxId" type="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.
</ParamField>

<ParamField body="originName" type="string">
  Nombre del origen
</ParamField>

<ParamField body="originCountry" type="string">
  Código de país ISO 2 del origen (ej. "US", "BR", "AR")
</ParamField>

<ParamField body="originDetails" type="object">
  Detalles del origen (dispositivo, cuenta, etc.). Ver [Esquema de payment details](/es/api-reference/transactions/payment-details-schema) 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).
</ParamField>

### Destino (entidad)

<Info>
  **Vinculación del destino:** misma precedencia que origen: `destinationEntityId` → `destinationExternalId` → `destinationTaxId`.

  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.
</Info>

<ParamField body="destinationEntityId" type="string">
  UUID de la entidad destino en gu1
</ParamField>

<ParamField body="destinationExternalId" type="string">
  Tu ID externo de la entidad destino. **Tras vincular**, se guarda como **`entities.external_id`**.
</ParamField>

<ParamField body="destinationTaxId" type="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.
</ParamField>

<ParamField body="destinationName" type="string">
  Nombre del destino
</ParamField>

<ParamField body="destinationCountry" type="string">
  Código de país ISO 2 del destino
</ParamField>

<ParamField body="destinationDetails" type="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](/es/api-reference/transactions/payment-details-schema)).
</ParamField>

### Ubicación y dispositivo

<ParamField body="locationDetails" type="object">
  Ubicación: `country`, `city`, `region`, `address`, `latitude`, `longitude`, `postalCode`, etc.
</ParamField>

<ParamField body="deviceDetails" type="object">
  Dispositivo: `deviceId`, `platform`, `osName`, `model`, `ipAddress`, `isVpn`, `isTor`, etc.
</ParamField>

<ParamField body="channel" type="string">
  Canal (máx. 50 caracteres): `mobile_app`, `web_browser`, `pos_terminal`, `api`, `atm`, etc.
</ParamField>

<ParamField body="reason" type="string">
  Motivo del resultado (opcional). Ver [Enum de motivos](/es/api-reference/transactions/reason-enum). Si se omite se usa `WITHOUT_REASON`.
</ParamField>

<ParamField body="timeZone" type="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](/es/api-reference/transactions/time-zone-enum).
</ParamField>

<ParamField body="metadata" type="object">
  Metadatos adicionales: `tags`, `purpose`, `enhanced_due_diligence`, `block_reason`, `compliance_alert`, etc. Campos custom permitidos.
</ParamField>

## Respuesta

<ResponseField name="transaction" type="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`.
</ResponseField>

<ResponseField name="rulesExecutionSummary" type="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](/es/api-reference/rules-execution-summary).
</ResponseField>

<ResponseField name="asyncRules" type="boolean">
  Presente y `true` solo cuando las reglas fueron **encoladas**. Omitido en el flujo síncrono por defecto.
</ResponseField>

<ResponseField name="rulesEvaluationStatus" type="string">
  En modo async: `"queued"`. Omitido cuando las reglas corrieron síncronamente.
</ResponseField>

## Ejemplo básico

<CodeGroup>
  ```bash cURL theme={null}
  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"
    }'
  ```

  ```javascript JavaScript theme={null}
  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);
  ```

  ```python Python theme={null}
  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)
  ```
</CodeGroup>

### 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.

```bash theme={null}
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"
  }'
```

```json theme={null}
{
  "transaction": { "id": "...", "externalId": "txn_nightly_001", "status": "SUCCESSFUL" },
  "rulesExecutionSummary": {
    "success": true,
    "rulesHit": [],
    "rulesNoHit": [],
    "totalScore": 0,
    "matchedRulesCount": 0,
    "executionTimeMs": 0
  },
  "asyncRules": true,
  "rulesEvaluationStatus": "queued"
}
```

<Warning>
  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.
</Warning>

## 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

| Paso | Condición                                         | Resultado                                                                  |
| ---- | ------------------------------------------------- | -------------------------------------------------------------------------- |
| 1    | `currency` igual a la base                        | `rateSource: no-conversion`, `exchangeRate: 1`                             |
| 2    | Conversión automática exitosa                     | Tasa del proveedor (`ms-provider`, caché, etc.)                            |
| 3    | Falla la conversión **y** enviaste `exchangeRate` | `rateSource: client-provided`                                              |
| 4    | Falla la conversión, sin `exchangeRate`           | `rateSource: 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`:

| Moneda    | Código | Notas                                                                |
| --------- | ------ | -------------------------------------------------------------------- |
| Worldcoin | `WLD`  | El cliente debe enviar `exchangeRate` para monto normalizado en base |
| Ethereum  | `ETH`  | El 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

```json theme={null}
{
  "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`):

```json theme={null}
{
  "transaction": {
    "amount": "26.16",
    "currency": "WLD",
    "exchangeRate": "2.41",
    "rateSource": "client-provided",
    "currenciesExchange": [
      { "currency": "USD", "exchangeRate": 2.41, "value": 63.05 }
    ]
  }
}
```

<Note>
  El batch (`POST /transactions/batch`, upload, JSON) acepta el mismo `exchangeRate` opcional en **cada fila**, con la misma semántica.
</Note>

<Note>
  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).
</Note>

## Ejemplo de respuesta

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

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

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

```json theme={null}
{
  "error": "Rate limit exceeded",
  "code": "RATE_LIMIT_EXCEEDED",
  "retryAfter": 3600
}
```

## Próximos pasos

* [Obtener transacción](/es/api-reference/transactions/get) - Por ID o external ID
* [Crear transacciones en lote](/es/api-reference/transactions/create-batch) - Carga masiva
* [Cambiar estado de transacción](/es/use-cases/transaction-monitoring/change-status-api) - Cambiar estado
* [Monitoreo de transacciones](/es/use-cases/transaction-monitoring/overview) - Detección de fraude
