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

# Create Transaction

> Create a new financial transaction for monitoring and analysis — in the gu1 transaction monitoring API for fraud and AML, with examples for create use cases.

## Overview

Creates a new transaction. With `executeRules: true` (default), the rules engine runs **synchronously** and the response includes a populated `rulesExecutionSummary` at the root when rules finish in the same request.

Omitting `asyncRules` (default `false`) preserves this behavior — existing integrations are unchanged.

## Endpoint

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

## Authentication

Requires a valid API key in the Authorization header:

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

## Query Parameters

<ParamField query="asyncRules" type="boolean" default="false">
  When `true` **and** `executeRules` is not `false`, the transaction is created immediately and rules evaluation is **enqueued** for background processing. The HTTP response returns before rules finish. Query param takes precedence over the same field in the JSON body.

  Accepted truthy values: `true`, `1`, `"true"`, `"1"`, `"yes"`.

  **Not supported** on batch create endpoints — only `POST /transactions` (single create).
</ParamField>

## Request Body

### Required Fields

<ParamField body="externalId" type="string" required>
  Your unique identifier for this transaction in your system
</ParamField>

<ParamField body="type" type="string" required>
  Type of transaction. Options:

  * `PAYMENT` - Payment transaction
  * `TRANSFER` - Money transfer
  * `WITHDRAWAL` - Cash withdrawal
  * `DEPOSIT` - Cash or check deposit
  * `REFUND` - Refund transaction
  * `CHARGEBACK` - Chargeback
  * `REVERSAL` - Transaction reversal
  * `FEE` - Service fee
  * `ADJUSTMENT` - Balance adjustment
  * `OTHER` - Other transaction type
</ParamField>

<ParamField body="amount" type="number" required>
  Transaction amount (must be zero or positive)
</ParamField>

<ParamField body="currency" type="string" required>
  Currency code (3-4 characters, e.g., "USD", "EUR", "BRL")
</ParamField>

<ParamField body="exchangeRate" type="number">
  Optional custom exchange rate, used **only** when automatic conversion to your organization's base currency is unavailable (provider error, timeout, or unsupported pair). **Omit this field to keep the existing behavior** — Gueno calls the currency service as today.

  **Semantics:** base-currency units per **1 unit** of `currency`. Normalized amount in your organization's base currency: `normalizedAmount = amount × exchangeRate`.

  Ignored when automatic conversion succeeds (provider rate wins).

  Required when automatic conversion is unavailable for **non-convertible currencies** (see below). See [Currency conversion](#currency-conversion).
</ParamField>

### Optional Fields

<ParamField body="status" type="string" default="CREATED">
  Transaction status. Options:

  * `CREATED` - Transaction created (default)
  * `PROCESSING` - Being processed
  * `SUSPENDED` - Suspended for review
  * `SENT` - Successfully sent
  * `EXPIRED` - Transaction expired
  * `DECLINED` - Declined/rejected
  * `REFUNDED` - Refunded
  * `SUCCESSFUL` - Completed successfully
</ParamField>

<ParamField body="paymentMethod" type="string">
  Payment method used. Options:

  * `CARD` - Credit/debit card
  * `ACH` - ACH transfer
  * `PIX` - Brazilian PIX
  * `TED` - Brazilian TED
  * `BOLETO` - Brazilian Boleto
  * `WALLET` - Digital wallet
  * `SWIFT` - SWIFT transfer
  * `IBAN` - IBAN transfer
  * `CBU` - Argentine CBU
  * `CVU` - Argentine CVU
  * `DEBIN` - Argentine DEBIN
  * `GENERIC_BANK_ACCOUNT` - Generic bank account
  * `MPESA` - M-Pesa
  * `UPI` - UPI (India)
  * `CHECK` - Check payment
  * `ECHECK` - Electronic check
  * `QR_CODE` - QR code payment
  * `ONLINE_PAYMENT` - Online payment
  * `WITHDRAWAL_ORDER` - Withdrawal order
</ParamField>

<ParamField body="description" type="string">
  Transaction description or notes
</ParamField>

<ParamField body="category" type="string">
  Transaction category for classification
</ParamField>

<ParamField body="transactedAt" type="string">
  ISO 8601 datetime when the transaction occurred (defaults to creation time). Stored as UTC. Use `Z` or `±HH:MM` in the string, **or** send a naive datetime together with `timeZone` (local wall time in that zone).
</ParamField>

<ParamField body="executeRules" type="boolean" default="true">
  Whether to run the rules engine for this transaction. Set to `false` to skip rules entirely (sync and async).
</ParamField>

<ParamField body="asyncRules" type="boolean" default="false">
  Same semantics as the query param `asyncRules`. Use for high-volume ingestion when you need the transaction persisted quickly and can review alerts later in gu1. Requires `executeRules: true` (default). Ignored when `executeRules` is `false`.
</ParamField>

<ParamField body="configRulesExecution" type="object">
  Optional tuning for how rules run after create (sync or async). Does **not** disable `createAlert` actions or investigation consolidation.

  | Field           | Type      | Default                                                                                                           |
  | --------------- | --------- | ----------------------------------------------------------------------------------------------------------------- |
  | `notifications` | `boolean` | `true` for most organizations; **`false` for Paytime prod** (`3bc1f621-27d4-423e-9d64-86680bec2388`) when omitted |

  When `notifications` is `false`, gu1 skips **in-app** notifications from rules evaluation (risk matrix / status change toasts). Alerts and investigations still behave normally.

  **Legacy KYT** `POST /legacy/kyt/verifyTransaction` accepts the same object on the Gu2 body as `configRulesExecution`; if omitted, Paytime prod gets `notifications: false` by default (same as `POST /transactions`).
</ParamField>

<ParamField body="validateExistingEntity" type="boolean" default="false">
  With **`false`** (default): if you send `originExternalId` / `destinationExternalId` (or root-level tax ids) and no entity matches, the transaction is **still created** (unlinked), same as before.

  With **`true`**: each side (origin / destination) is validated only when you send at least one identifier for that side (`originEntityId`, `originExternalId`, or `originTaxId`; same for destination). Every identifier you send must resolve to an existing person/company in your organization; otherwise the API returns **400** with `INVALID_ENTITY_REFERENCES` and **does not** create the transaction. If you send no origin (or destination) identifiers, that side is not validated.
</ParamField>

### Risk Matrices (Optional)

<ParamField body="riskMatrixId" type="string | string[]">
  Legacy-compatible: one UUID or an **array of UUIDs** of risk matrices owned by your organization. When non-empty, only rules assigned to those matrices run for this transaction (no mixing with “loose” trigger-only rules). Omit both `riskMatrixId` and `riskMatrixIds` to keep the historical trigger-based behavior.
</ParamField>

<ParamField body="riskMatrixIds" type="string[]">
  Preferred for **multiple** matrices: ordered UUID list. Takes precedence over `riskMatrixId` when provided and non-empty.
</ParamField>

### Origin Entity Fields

<Info>
  **How the origin is linked in gu1** (tried in order; stop at first success):

  1. **Direct ID** — if you send `originEntityId`, the transaction is tied to that entity (must exist in your org).
  2. **External ID** — if you did *not* send `originEntityId` but you send `originExternalId` and a **person/company** exists with the same `externalId`, the row is **auto-linked** to that entity.
  3. **Tax / document ID (third fallback)** — if the transaction is still *not* linked, but you send `root` **`originTaxId`**, the API looks up a person/company whose `taxId` in gu1 matches after **normalization** (only letters and digits, ignoring punctuation, spaces, and case for comparison).

  If step **2** or **3** succeeds, the API sets `originEntityId` and, when you did not provide them, **enriches** `originName` and `originCountry` from the matched entity.

  **Canonical denormalization (linked origin):** whenever the origin side is linked to a person/company — you sent `originEntityId`, or auto-link succeeded via `originExternalId` / `originTaxId` — gu1 **syncs denormalized columns from the entity row** before insert:

  | Transaction field  | Source on entity       | Client value kept?                                           |
  | ------------------ | ---------------------- | ------------------------------------------------------------ |
  | `originEntityId`   | `entities.id`          | Set on auto-link; unchanged if you already sent a valid UUID |
  | `originTaxId`      | `entities.tax_id`      | **No** — always overwritten from the linked entity           |
  | `originExternalId` | `entities.external_id` | **No** — always overwritten from the linked entity           |

  If the entity has no `taxId` or `externalId`, the corresponding transaction column is stored as **`null`**, even when you sent values in the request body.

  **Integrator note:** you can send any mix of identifiers to establish the link (precedence: `originEntityId` → `originExternalId` → `originTaxId`). After linking, **persisted** `originTaxId` / `originExternalId` reflect the **entity in gu1**, not necessarily what you typed. This keeps transaction monitoring rules aligned with [user events](/en/api-reference/events/create) (`entityId`, `entityExternalId`, `taxId` on the event row).

  If the tax/external value does **not** match any entity, the transaction is still **created**; the fields you sent are stored, but there is **no** entity link.\
  (For graph visualization only, you can also send a tax in `originDetails` — that path does *not* auto-link; use **`originTaxId` at the root** when you want a real link by document.)
</Info>

<ParamField body="originEntityId" type="string">
  UUID of the origin entity (sender) in gu1 system
</ParamField>

<ParamField body="originExternalId" type="string">
  Your external ID for the origin entity. Used as the **second** linking key when `originEntityId` is omitted. **After a successful link**, the stored value is **`entities.external_id`** (client value is not kept if it differs).
</ParamField>

<ParamField body="originTaxId" type="string">
  Tax or national document ID of the origin counterparty (e.g. CPF, CNPJ, CUIT), **at the root** of the transaction. Used only as the **third** way to find and link a person/company, after `originEntityId` and `originExternalId`. The value is compared to each entity’s `taxId` using a normalized form (alphanumeric only). If a match is found, `originEntityId` is set and name/country can be filled. **After a successful link**, the stored value is **`entities.tax_id`**. Max length: 50 characters. Optional.
</ParamField>

<ParamField body="originName" type="string">
  Name of the origin entity
</ParamField>

<ParamField body="originCountry" type="string">
  ISO 2-letter country code of origin (e.g., "US", "BR", "AR")
</ParamField>

<ParamField body="originDetails" type="object">
  Detailed information about the origin (sender/device). These fields match the API schema; you can also send additional custom fields and they will be stored.

  **Schema fields (all optional):**

  **Device/Technical:**

  * `deviceId` (string) - Unique device identifier
  * `deviceFingerprint` (string) - Device fingerprint hash
  * `deviceType` (enum) - 'mobile', 'desktop', 'tablet', 'pos', 'atm'
  * `userAgent` (string) - Browser user agent
  * `ipAddress` (string) - IP address (validated format)

  **Geolocation:**

  * `country` (string) - ISO 2-letter country code
  * `city` (string), `region` (string), `latitude` (number), `longitude` (number), `timezone` (string)

  **Account:**

  * `accountNumber` (string), `accountType` (enum: 'checking', 'savings', 'business', 'personal'), `bankCode` (string), `bankName` (string)

  **Security flags:**

  * `isVpn` (boolean), `isTor` (boolean), `isProxy` (boolean), `governmentAccount` (boolean)

  **paymentDetails (optional):** Nested object with payment-level data (card, PIX, bank, etc.). Not enforced — you can send any keys. See [Payment Details Schema](/en/api-reference/transactions/payment-details-schema) for suggested structures by payment method (card, PIX, CBU/CVU, SPEI, PSE, DEBIN, crypto, wallet, cash, check, etc.). Extra and custom fields are allowed.

  **Unknown origin (no entity):** When the origin party is **not** an entity in gu1 (you don't send `originEntityId` or `originExternalId`), you can still improve **network graph** visualization by sending identifying data in `originDetails.paymentDetails`. The graph groups pseudo nodes by the first match in this order: `taxId`, `cbu`, `cvu`, `pixKey`, `clabe`, `alias`, `iban`, `holderId`, `debinId`, `cardFingerprint`, then `accountNumber` (+ optional `bankCode`). See [Payment Details Schema — Unknown party](/en/api-reference/transactions/payment-details-schema#unknown-party-graph-visualization). Optional and internal only.
</ParamField>

### Destination Entity Fields

<Info>
  **Destination linking** follows the same precedence as origin: `destinationEntityId` → `destinationExternalId` → `destinationTaxId`.

  When the destination is linked, gu1 **always syncs** `destinationTaxId` and `destinationExternalId` from the matched entity (same rules as origin). Unlinked sides keep the values you sent.
</Info>

<ParamField body="destinationEntityId" type="string">
  UUID of the destination entity (recipient) in gu1 system
</ParamField>

<ParamField body="destinationExternalId" type="string">
  Your external ID for the destination entity. **After a successful link**, stored as **`entities.external_id`**.
</ParamField>

<ParamField body="destinationTaxId" type="string">
  Tax or document ID of the **destination** counterparty at the **root** of the request. **Third** linking fallback: used only if `destinationEntityId` and `destinationExternalId` did not resolve. Same matching rules as `originTaxId`. When a match is found, `destinationEntityId` is set and name/country can be enriched. **After a successful link**, stored as **`entities.tax_id`**. Max: 50 characters. Optional.
</ParamField>

<ParamField body="destinationName" type="string">
  Name of the destination entity
</ParamField>

<ParamField body="destinationCountry" type="string">
  ISO 2-letter country code of destination (e.g., "US", "BR", "AR")
</ParamField>

<ParamField body="destinationDetails" type="object">
  Detailed information about the destination (merchant/receiver). These fields match the API schema; you can also send additional custom fields and they will be stored.

  **Schema fields (all optional):**

  **Merchant:**

  * `mcc` (string) - Merchant Category Code (4 digits)
  * `mccDescription` (string), `merchantId` (string), `merchantName` (string), `merchantType` (string)

  **Device/Technical:**

  * `deviceId` (string), `deviceType` (enum: 'pos', 'online', 'mobile', 'atm'), `ipAddress` (string)

  **Geolocation:**

  * `country` (string, ISO 2), `city` (string), `region` (string)

  **Account:**

  * `accountNumber` (string), `accountType` (enum: 'checking', 'savings', 'business', 'merchant'), `bankCode` (string), `bankName` (string)

  **Risk flags:**

  * `cryptoExchange` (boolean), `highRisk` (boolean), `privateSector` (boolean)

  **paymentDetails (optional):** Nested object with payment-level data (merchant account, card, PIX, etc.). Not enforced — you can send any keys. See [Payment Details Schema](/en/api-reference/transactions/payment-details-schema) for suggested structures by payment method. Extra and custom fields are allowed.

  **Unknown destination (no entity):** When the destination party is **not** an entity in gu1 (you don't send `destinationEntityId` or `destinationExternalId`), you can still improve **network graph** visualization by sending identifying data in `destinationDetails.paymentDetails` (same priority order as origin: `taxId`, `cbu`, `cvu`, `pixKey`, `clabe`, `alias`, `iban`, `holderId`, `debinId`, `cardFingerprint`, then `accountNumber` + optional `bankCode`). See [Payment Details Schema — Unknown party](/en/api-reference/transactions/payment-details-schema#unknown-party-graph-visualization). Optional and internal only.
</ParamField>

### Location Details

<ParamField body="locationDetails" type="object">
  Physical location information for the transaction. Useful for fraud detection and geographic analysis.

  **Address Information:**

  * `country` (string) - ISO 2-letter country code
  * `countryName` (string) - Full country name
  * `city` (string) - City name
  * `region` (string) - State/Province
  * `address` (string) - Full address
  * `street` (string) - Street name
  * `streetNumber` (string) - Street number
  * `postalCode` (string) - Postal/ZIP code
  * `neighborhood` (string) - Neighborhood/district

  **Coordinates (for map visualization):**

  * `latitude` (number) - GPS latitude (-90 to 90)
  * `longitude` (number) - GPS longitude (-180 to 180)

  **Additional Info:**

  * `timezone` (string) - Timezone identifier
  * `placeId` (string) - Google Places ID or similar
</ParamField>

### Device Details

<ParamField body="deviceDetails" type="object">
  Detailed device information for fraud detection and device fingerprinting.

  **Device Identification:**

  * `deviceId` (string) - Unique device identifier
  * `externalId` (string) - Your external device ID

  **Platform and OS:**

  * `platform` (enum) - Platform: 'android', 'ios', 'web', 'desktop', 'mobile', 'tablet', 'pos', 'atm'
  * `osName` (string) - OS name (e.g., 'Android', 'iOS', 'Windows', 'macOS')
  * `osVersion` (string) - OS version

  **Device Info:**

  * `manufacturer` (string) - Device manufacturer
  * `model` (string) - Device model
  * `brand` (string) - Device brand
  * `deviceName` (string) - Device name/nickname

  **Browser Info (for web platform):**

  * `browser` (string) - Browser name
  * `browserVersion` (string) - Browser version
  * `userAgent` (string) - Full user agent string

  **Security Flags:**

  * `isEmulator` (boolean) - Device is an emulator
  * `isRooted` (boolean) - Android device is rooted
  * `isJailbroken` (boolean) - iOS device is jailbroken

  **Network Info:**

  * `ipAddress` (string) - IP address (validated format)
  * `isVpn` (boolean) - Connection via VPN
  * `isTor` (boolean) - Connection via Tor network
  * `isProxy` (boolean) - Connection via proxy

  **Device Fingerprint:**

  * `deviceFingerprint` (string) - Unique device fingerprint hash

  **Additional Details:**

  * `screenResolution` (string) - Screen resolution (e.g., "1920x1080")
  * `language` (string) - Device language
  * `timezone` (string) - Device timezone
</ParamField>

### Channel

<ParamField body="channel" type="string">
  Channel through which the transaction originated (max 50 characters).

  Common values:

  * `mobile_app` - Mobile application
  * `web_browser` - Web browser
  * `pos_terminal` - Point of sale terminal
  * `api` - Direct API integration
  * `atm` - ATM machine
  * `phone_banking` - Phone banking
  * `branch` - Physical branch
  * `chatbot` - Chatbot interface
  * `third_party` - Third-party integration
</ParamField>

### Reason

<ParamField body="reason" type="string">
  Optional reason for the transaction outcome (e.g. decline, failure, limit exceeded). Send any value from the **transaction\_reason\_type** enum. If omitted, the system uses `WITHOUT_REASON`. Not required — existing integrations remain valid.

  **Full list:** See [Transaction Reason Enum](/en/api-reference/transactions/reason-enum) for all 60+ allowed values.
</ParamField>

### Time Zone

<ParamField body="timeZone" type="string">
  Optional IANA time zone for transaction-local context (independent of entity `operationalHours`). Send a value from **transaction\_time\_zone** (same enum values as `operationalHours.timezone`). Omit for `null`.

  **`transactedAt` normalization:** If `transactedAt` includes `Z` (as required by this endpoint's datetime validator), that instant is stored in UTC; `timeZone` is optional metadata and is not used for parsing. If you also send `timeZone` together with a local datetime (internal/batch tooling), the API can interpret naive wall time in that zone. Existing integrations that omit `timeZone` behave as before. KYT operational-hours rules use the stored UTC instant plus the entity's `operationalHours.timezone`, not `transaction.timeZone`.

  **Full list:** See [Transaction Time Zone Enum](/en/api-reference/transactions/time-zone-enum).
</ParamField>

### Metadata

<ParamField body="metadata" type="object">
  Additional metadata for the transaction. Optional fields:

  **Tags:**

  * `tags` (object) - Key-value pairs for categorization (values can be string, number, or boolean)

  **Transaction Context:**

  * `purpose` (string) - Purpose of the transaction
  * `frequency` (string) - Transaction frequency
  * `contract_number` (string) - Associated contract number

  **Compliance:**

  * `enhanced_due_diligence` (boolean) - EDD flag
  * `block_reason` (string) - Reason for blocking
  * `compliance_alert` (boolean) - Compliance alert flag

  Custom fields are allowed via `passthrough` behavior.
</ParamField>

## Response

<ResponseField name="transaction" type="object">
  The created transaction object. Includes:

  * `id` - gu1's internal transaction ID
  * `externalId` - Your external ID
  * `organizationId` - Your organization ID
  * `type` - Transaction type
  * `amount` - Transaction amount (string)
  * `currency` - Currency code
  * `status` - Transaction status
  * `riskScore` - Calculated risk score 0-100 (string)
  * `flagged` - Whether transaction is flagged
  * `channel` - Channel information
  * `reason` - Outcome reason (e.g. WITHOUT\_REASON, INSUFFICIENT\_FUNDS)
  * `timeZone` - IANA time zone (`string | null`)
  * `originDetails` / `destinationDetails` - Origin/destination details
  * `locationDetails` - Location data
  * `deviceDetails` - Device information
  * `processingTimeMs`, `processedAt`, `transactedAt`, `createdAt`, `updatedAt` - Timestamps
</ResponseField>

<ResponseField name="rulesExecutionSummary" type="object">
  Present when **executeRules** is true.

  **Synchronous (default):** populated after rules finish in the same request (`rulesHit`, `rulesNoHit`, scores, etc.).

  **Async (`asyncRules=true`):** placeholder with `success: true`, empty `rulesHit` / `rulesNoHit`, and `matchedRulesCount: 0`. Alerts and risk updates appear after background processing completes.

  Omitted when `executeRules` is `false`.

  See [Rules Execution Summary](/en/api-reference/rules-execution-summary) for the full structure and a complete example.
</ResponseField>

<ResponseField name="asyncRules" type="boolean">
  Present and `true` only when rules were **queued** (`asyncRules=true`). Omitted in the default synchronous flow.
</ResponseField>

<ResponseField name="rulesEvaluationStatus" type="string">
  When async: `"queued"`. Omitted when rules ran synchronously.
</ResponseField>

## Examples

### Basic Transaction

<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": "Online purchase"
    }'
  ```

  ```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: 'Online purchase'
    })
  });

  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': 'Online purchase'
      }
  )

  transaction = response.json()['transaction']
  print(transaction)
  ```
</CodeGroup>

### Async Rules (High-Volume Ingestion)

Use when you need fast `201` responses and will review alerts in gu1 later. Rules run in the background; `rulesExecutionSummary.rulesHit` is empty in the HTTP response.

```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>
  Do not use the synchronous response to block or approve payments when `asyncRules=true`. There is no second webhook when background rules finish — monitor alerts in gu1 or poll transaction/investigation APIs.
</Warning>

## Currency Conversion

When `currency` differs from your organization's **base currency** (default USD), Gueno fetches an exchange rate automatically. Behavior is **unchanged** if you omit `exchangeRate`.

### Resolution Order

| Step | Condition                                                  | Result                                                                   |
| ---- | ---------------------------------------------------------- | ------------------------------------------------------------------------ |
| 1    | `currency` equals base currency                            | `rateSource: no-conversion`, `exchangeRate: 1`                           |
| 2    | Automatic conversion succeeds                              | Provider rate (`ms-provider`, cache, etc.)                               |
| 3    | Automatic conversion fails **and** you sent `exchangeRate` | `rateSource: client-provided`                                            |
| 4    | Automatic conversion fails, no `exchangeRate`              | `rateSource: conversion-unavailable`; no normalized base-currency amount |

### `exchangeRate` Semantics

* **Direction:** base-currency units per **1 unit** of `currency` (same as provider rates).
* **Formula:** `normalizedAmount = amount × exchangeRate` (stored on the transaction in your org base currency).
* **Not an override:** if step 2 succeeds, a client `exchangeRate` is ignored.

### Non-Convertible Currencies (Automatic Conversion)

Gueno **does not** obtain exchange rates automatically for these codes today. Automatic conversion returns unavailable unless you send `exchangeRate`:

| Currency  | Code  | Notes                                                        |
| --------- | ----- | ------------------------------------------------------------ |
| Worldcoin | `WLD` | Client must supply `exchangeRate` for normalized base amount |
| Ethereum  | `ETH` | Client must supply `exchangeRate` for normalized base amount |

All other ISO codes follow the normal automatic conversion flow (provider or historical rate when `transactedAt` is set).

Send `exchangeRate` when you need a normalized amount in base currency for rules and reporting.

### Example — WLD with Client Rate

```json theme={null}
{
  "externalId": "txn-wld-001",
  "type": "PAYMENT",
  "amount": 26.16,
  "currency": "WLD",
  "exchangeRate": 2.41,
  "destinationExternalId": "merchant_001"
}
```

Response (org base currency USD — normalized amount `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>
  Batch create (`POST /transactions/batch`, upload, JSON) accepts the same optional `exchangeRate` on **each row** with identical semantics.
</Note>

<Note>
  When **Redis** is configured, jobs go to the shared `transaction-rules-eval` queue (dedicated workers if `DISABLE_INPROCESS_BULL_WORKERS=true`). If Redis is missing or enqueue fails, the API still returns **200** with `asyncRules: true` and runs rules **in-process** on that server (best for single-instance or dev; high-volume multi-instance deployments should use Redis + workers).
</Note>

## Response Example

```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": {},
    "locationDetails": {},
    "deviceDetails": {},
    "processingTimeMs": "120",
    "processedAt": "2024-10-03T14:30:00.000Z",
    "transactedAt": "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
  }
}
```

(For **batch** create, see [Create Batch](/en/api-reference/transactions/create-batch); that endpoint returns an array of transactions.)

## Error Responses

### 400 Bad Request - Invalid Data

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

### 400 Bad Request - Invalid IP Address

```json theme={null}
{
  "success": false,
  "error": {
    "code": "VALIDATION_ERROR",
    "message": "Invalid deviceDetails structure",
    "details": {
      "path": ["deviceDetails", "ipAddress"],
      "message": "Invalid IP address format"
    }
  }
}
```

### 409 Conflict - Duplicate Transaction

```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 Too Many Requests

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

## Next Steps

* [Get transaction](/en/api-reference/transactions/get) - Fetch by ID or external ID
* [Create Batch Transactions](/en/api-reference/transactions/create-batch) - Bulk transaction creation
* [Change transaction status](/en/use-cases/transaction-monitoring/change-status-api) - Change status
* [Transaction Monitoring](/en/use-cases/transaction-monitoring/overview) - Learn about fraud detection
