# Transactions (Coinbax API)

> Source: https://developers.coinbax.com/reference/coinbax-api/transactions/
> Staging base URL: https://api-staging.coinbax.com

Payment operations and lifecycle management

## List transactions with filters

`GET /api/v1/transactions`

- Auth: X-API-Key header
- Operation ID: `TransactionController_findAll`

### Parameters

| Name | In | Type | Required | Description |
| --- | --- | --- | --- | --- |
| `page` | query | number | no | Page number |
| `limit` | query | number | no | Items per page |
| `status` | query | enum ("pending", "risk_review", "pending_user_action", "escrowed", "in_review", "disputed", "completed", "refunded", "failed", "rescinded") | no | Filter by status |
| `fromAddress` | query | string | no | Filter by sender blockchain address |
| `toAddress` | query | string | no | Filter by receiver blockchain address |
| `dateFrom` | query | string | no | Filter by date from (ISO 8601) |
| `dateTo` | query | string | no | Filter by date to (ISO 8601) |
| `customerId` | query | string | no | Filter by customer ID |
| `walletAddress` | query | string | no | Filter by wallet address (matches fromAddress OR toAddress) |

### Example request

```bash
curl https://api-staging.coinbax.com/api/v1/transactions \
  -H "X-API-Key: $COINBAX_API_KEY"
```

### Responses

**200** List of transactions

---

## Create a new transaction

`POST /api/v1/transactions`

- Auth: X-API-Key header
- Operation ID: `TransactionController_create`

### Request body

| Field | Type | Required | Description |
| --- | --- | --- | --- |
| `fromAddress` | string | yes | Sender blockchain address |
| `toAddress` | string | yes | Receiver blockchain address |
| `amount` | number | yes | Transaction amount (minimum 0.01, maximum 1,000,000) |
| `currency` | enum ("USDC", "USDT", "ETH", "MockUSDC", "USDG") | no | Currency type |
| `blockchainNetwork` | enum ("ethereum", "base", "solana", "optimism", "arbitrum", "base-sepolia", "ethereum-sepolia", "optimism-sepolia", "arbitrum-sepolia", "solana-devnet", "solana-testnet") | no | Blockchain network. Defaults to the environment's Base network when omitted ('base' on production, 'base-sepolia' on local/staging). Mainnet names sent to local/staging are coerced to their testnet counterpart (e.g. base → base-sepolia); testnet names sent to production are rejected with TESTNET_NOT_ALLOWED_IN_PRODUCTION. |
| `orchestrationType` | enum ("raw", "coinbax") | no | Orchestration type: raw (direct blockchain) or coinbax (smart contract escrow) |
| `templateId` | string (uuid) | no | Template ID from coinbax-library. The template will be fetched from the library service. |
| `contractTemplateId` | string (uuid) | no | Contract template ID (alias for templateId, for backward compatibility) |
| `template` | object | no | Complete template data embedded in request (optional, prefer using templateId instead) |
| `metadata` | object | no | Additional metadata about the transaction |
| `billingConfig` | object | no | Billing configuration from workspace (passed by coinbax-core) |
| `quoteId` | string (uuid) | no | FeeQuote id from a prior /transactions/quote call. Required when the workspace has BILLING_ENABLED=true (Phase 1 cutover). |
| `signerType` | enum ("custodial", "external_wallet") | no | Signer type: custodial (Coinbax signs) or external_wallet (User signs via MetaMask) |
| `customerId` | string | no | Customer ID for external wallet transactions (Transmitter users) |
| `idempotencyKey` | string | no | Idempotency key to prevent duplicate transaction creation. If provided, a transaction with the same key will return the existing transaction instead of creating a new one. |

```json
{
  "fromAddress": "0x742d35Cc6634C0532925a3b844Bc9e7595f0bEb",
  "toAddress": "0x742d35Cc6634C0532925a3b844Bc9e7595f0bEb",
  "amount": 100.5,
  "currency": "USDC",
  "blockchainNetwork": "base",
  "orchestrationType": "raw",
  "templateId": "550e8400-e29b-41d4-a716-446655440000",
  "contractTemplateId": "550e8400-e29b-41d4-a716-446655440000",
  "template": {
    "templateId": "550e8400-e29b-41d4-a716-446655440000",
    "templateName": "2FA Escrow",
    "templateVersion": "1.0.0",
    "controls": [
      {
        "id": "control-123",
        "type": "TwoFactorAuth",
        "name": "Two-Factor Authentication",
        "executionPhase": "PRE_ESCROW",
        "executionOrder": 1,
        "config": {
          "method": "sms",
          "phoneNumber": "+1234567890"
        }
      }
    ]
  },
  "metadata": {
    "orderId": "order_789",
    "productName": "Premium Widget"
  },
  "billingConfig": {
    "feeType": "percentage",
    "feePercentage": 30,
    "feeWalletAddress": "0x742d35Cc6634C0532925a3b844Bc9e7595f0bEb",
    "currency": "USDC",
    "isActive": true
  },
  "quoteId": "550e8400-e29b-41d4-a716-446655440000",
  "signerType": "custodial",
  "customerId": "cust_abc123",
  "idempotencyKey": "tx_unique_key_12345"
}
```

### Example request

```bash
curl -X POST https://api-staging.coinbax.com/api/v1/transactions \
  -H "X-API-Key: $COINBAX_API_KEY" \
  -H "Content-Type: application/json" \
  -d '{
  "fromAddress": "0x742d35Cc6634C0532925a3b844Bc9e7595f0bEb",
  "toAddress": "0x742d35Cc6634C0532925a3b844Bc9e7595f0bEb",
  "amount": 100.5,
  "currency": "USDC",
  "blockchainNetwork": "base",
  "orchestrationType": "raw",
  "templateId": "550e8400-e29b-41d4-a716-446655440000",
  "contractTemplateId": "550e8400-e29b-41d4-a716-446655440000",
  "template": {
    "templateId": "550e8400-e29b-41d4-a716-446655440000",
    "templateName": "2FA Escrow",
    "templateVersion": "1.0.0",
    "controls": [
      {
        "id": "control-123",
        "type": "TwoFactorAuth",
        "name": "Two-Factor Authentication",
        "executionPhase": "PRE_ESCROW",
        "executionOrder": 1,
        "config": {
          "method": "sms",
          "phoneNumber": "+1234567890"
        }
      }
    ]
  },
  "metadata": {
    "orderId": "order_789",
    "productName": "Premium Widget"
  },
  "billingConfig": {
    "feeType": "percentage",
    "feePercentage": 30,
    "feeWalletAddress": "0x742d35Cc6634C0532925a3b844Bc9e7595f0bEb",
    "currency": "USDC",
    "isActive": true
  },
  "quoteId": "550e8400-e29b-41d4-a716-446655440000",
  "signerType": "custodial",
  "customerId": "cust_abc123",
  "idempotencyKey": "tx_unique_key_12345"
}'
```

### Responses

**201** Transaction created successfully

```json
{
  "rescind": {
    "allowed": true,
    "deadline": "2026-06-15T12:00:00.000Z",
    "requiresReason": false,
    "notifyRecipient": true
  }
}
```

**409** Duplicate transaction detected within time window

**429** Transaction rate limit exceeded (max 10/minute per sender)

---

## Money-at-risk: count + age of funds stuck in non-terminal states

`GET /api/v1/transactions/at-risk`

- Auth: X-API-Key header
- Operation ID: `TransactionController_getAtRiskStatistics`

### Example request

```bash
curl https://api-staging.coinbax.com/api/v1/transactions/at-risk \
  -H "X-API-Key: $COINBAX_API_KEY"
```

### Responses

**200** Per-status (escrowed/in_review/pending_user_action/disputed) count, total amount, oldest age, and >24h/>72h aged counts

---

## List transaction batches with filters

`GET /api/v1/transactions/batch`

- Auth: X-API-Key header
- Operation ID: `TransactionController_findAllBatches`

Returns a paginated list of multi-recipient batches scoped to the calling platform. Legs are not embedded — fetch a single batch via GET /transactions/batch/:id for that.

### Parameters

| Name | In | Type | Required | Description |
| --- | --- | --- | --- | --- |
| `page` | query | number | no | Page number |
| `limit` | query | number | no | Items per page |
| `status` | query | enum ("pending_user_action", "submitted", "completed", "failed") | no | Filter by batch status |
| `fromAddress` | query | string | no | Filter by sender blockchain address |
| `dateFrom` | query | string | no | Filter by date from (ISO 8601) |
| `dateTo` | query | string | no | Filter by date to (ISO 8601) |
| `customerId` | query | string | no | Filter by customer ID |

### Example request

```bash
curl https://api-staging.coinbax.com/api/v1/transactions/batch \
  -H "X-API-Key: $COINBAX_API_KEY"
```

### Responses

**200** Paginated list of batches

---

## Create a multi-recipient transaction batch

`POST /api/v1/transactions/batch`

- Auth: X-API-Key header
- Operation ID: `TransactionController_createBatch`

Creates a parent batch row plus one Transaction leg per recipient in a single DB transaction. The client (e.g., SafeSend) then signs and submits `WorkspaceEscrow.batchCreateEscrow` with each leg's UUID as the on-chain `transactionId`. Per-leg `EscrowCreated` events flow through the existing event monitor and transition each leg from PENDING_USER_ACTION to ESCROWED individually.

### Request body

| Field | Type | Required | Description |
| --- | --- | --- | --- |
| `fromAddress` | string | yes | Sender blockchain address (one signer for the whole batch). |
| `legs` | array of CreateBatchLegDto | yes | Per-recipient legs (1..MAX_BATCH_LEGS). |
| `currency` | enum ("USDC", "USDT", "ETH", "MockUSDC", "USDG") | no | Currency type — shared by every leg in the batch. |
| `blockchainNetwork` | string | no | Blockchain network. The set of accepted values mirrors `CreateTransactionDto.blockchainNetwork`, including the env-aware default ('base' on production, 'base-sepolia' on local/staging), non-prod coercion of mainnet names, and production rejection of testnet names. |
| `orchestrationType` | enum ("raw", "coinbax") | no | Orchestration type for every leg. |
| `signerType` | enum ("custodial", "external_wallet") | no | Signer type. Must be `external_wallet` in B1 — custodial batch sends are not supported in this phase. |
| `templateId` | string (uuid) | no | Template ID from coinbax-library. Stored on the batch in B1; control execution against the template is wired in B2. |
| `customerId` | string | no | Customer ID for external-wallet flows (Transmitter, SafeSend). |
| `idempotencyKey` | string | no | Idempotency key — if provided, an existing batch with the same key is returned instead of creating a new one. |
| `metadata` | object | no | Batch-level metadata (distinct from per-leg metadata). |
| `template` | object | no | Embedded template (alternative to `templateId`, deprecated for consistency with `CreateTransactionDto`). |

```json
{
  "fromAddress": "0x742d35Cc6634C0532925a3b844Bc9e7595f0bEb",
  "legs": [
    {
      "toAddress": "0x742d35Cc6634C0532925a3b844Bc9e7595f0bEb",
      "amount": 100.5,
      "metadata": {
        "recipientLabel": "Alice",
        "orderId": "order_789"
      }
    }
  ],
  "currency": "USDC",
  "blockchainNetwork": "base",
  "orchestrationType": "raw",
  "signerType": "external_wallet",
  "templateId": "550e8400-e29b-41d4-a716-446655440000",
  "customerId": "cust_abc123",
  "idempotencyKey": "batch_unique_key_12345",
  "metadata": {
    "source": "safesend",
    "uiVersion": "1.2.3"
  },
  "template": {
    "templateId": "550e8400-e29b-41d4-a716-446655440000",
    "templateName": "2FA Escrow",
    "templateVersion": "1.0.0",
    "controls": [
      {
        "id": "control-123",
        "type": "TimeDelay",
        "name": "Payment Hold Period",
        "executionPhase": "PRE_ESCROW",
        "executionOrder": 1,
        "config": {
          "delayMinutes": 5
        },
        "isRequired": true,
        "isActive": true
      }
    ]
  }
}
```

### Example request

```bash
curl -X POST https://api-staging.coinbax.com/api/v1/transactions/batch \
  -H "X-API-Key: $COINBAX_API_KEY" \
  -H "Content-Type: application/json" \
  -d '{
  "fromAddress": "0x742d35Cc6634C0532925a3b844Bc9e7595f0bEb",
  "legs": [
    {
      "toAddress": "0x742d35Cc6634C0532925a3b844Bc9e7595f0bEb",
      "amount": 100.5,
      "metadata": {
        "recipientLabel": "Alice",
        "orderId": "order_789"
      }
    }
  ],
  "currency": "USDC",
  "blockchainNetwork": "base",
  "orchestrationType": "raw",
  "signerType": "external_wallet",
  "templateId": "550e8400-e29b-41d4-a716-446655440000",
  "customerId": "cust_abc123",
  "idempotencyKey": "batch_unique_key_12345",
  "metadata": {
    "source": "safesend",
    "uiVersion": "1.2.3"
  },
  "template": {
    "templateId": "550e8400-e29b-41d4-a716-446655440000",
    "templateName": "2FA Escrow",
    "templateVersion": "1.0.0",
    "controls": [
      {
        "id": "control-123",
        "type": "TimeDelay",
        "name": "Payment Hold Period",
        "executionPhase": "PRE_ESCROW",
        "executionOrder": 1,
        "config": {
          "delayMinutes": 5
        },
        "isRequired": true,
        "isActive": true
      }
    ]
  }
}'
```

### Responses

**201** Batch created successfully (legs included in `legs`)

```json
{}
```

**400** Invalid request — empty/oversized `legs`, malformed addresses, unsupported `signerType`, or template validation failure

**403** Customer approval required or sender not approved

**429** Transaction rate limit exceeded

---

## Get a transaction batch by ID

`GET /api/v1/transactions/batch/{id}`

- Auth: X-API-Key header
- Operation ID: `TransactionController_findBatch`

Returns the parent batch row plus all child Transaction legs. Each leg carries its own status, escrowId, and per-recipient metadata; per-leg listing of just the legs is also reachable via GET /transactions?batchId=…

### Parameters

| Name | In | Type | Required | Description |
| --- | --- | --- | --- | --- |
| `id` | path | string | yes |  |

### Example request

```bash
curl https://api-staging.coinbax.com/api/v1/transactions/batch/<id> \
  -H "X-API-Key: $COINBAX_API_KEY"
```

### Responses

**200** Batch details with legs

```json
{}
```

**404** Batch not found

---

## Manually cancel an unsubmitted batch

`POST /api/v1/transactions/batch/{id}/cancel`

- Auth: X-API-Key header
- Operation ID: `TransactionController_cancelBatch`

Marks a batch FAILED and propagates the same status to every leg. Allowed only while the batch is in `pending_user_action` (the user hasn't signed `batchCreateEscrow` yet, or is parked awaiting verification). Once the on-chain tx is in flight, use per-leg refund/dispute endpoints instead. Idempotent on already-FAILED batches. Fires a `transaction.batch.failed` webhook on success.

### Parameters

| Name | In | Type | Required | Description |
| --- | --- | --- | --- | --- |
| `id` | path | string | yes |  |

### Request body

| Field | Type | Required | Description |
| --- | --- | --- | --- |
| `reason` | string | no | Human-readable reason recorded on the batch metadata. Surfaces in audit logs and the cancel webhook payload. Truncated at 500 chars. |

```json
{
  "reason": "Sender closed the tab before signing batchCreateEscrow"
}
```

### Example request

```bash
curl -X POST https://api-staging.coinbax.com/api/v1/transactions/batch/<id>/cancel \
  -H "X-API-Key: $COINBAX_API_KEY" \
  -H "Content-Type: application/json" \
  -d '{
  "reason": "Sender closed the tab before signing batchCreateEscrow"
}'
```

### Responses

**200** Batch cancelled (or was already FAILED — idempotent)

```json
{}
```

**400** Batch is in a state that cannot be cancelled (submitted / completed)

**404** Batch not found

---

## Record the on-chain batchCreateEscrow transaction hash

`POST /api/v1/transactions/batch/{id}/submit-escrow-hash`

- Auth: X-API-Key header
- Operation ID: `TransactionController_submitBatchEscrowHash`

Stores the shared on-chain transaction hash on the batch + every leg, and advances the batch to `submitted`. Per-leg `EscrowCreated` events drive each leg from `pending_user_action` to `escrowed` independently via the existing event monitor.

### Parameters

| Name | In | Type | Required | Description |
| --- | --- | --- | --- | --- |
| `id` | path | string | yes |  |

### Request body

| Field | Type | Required | Description |
| --- | --- | --- | --- |
| `transactionHash` | string | yes | On-chain transaction hash for the `batchCreateEscrow` call. Shared by every leg in the batch (one signature, one tx). |

```json
{
  "transactionHash": "0x1234567890abcdef1234567890abcdef1234567890abcdef1234567890abcdef"
}
```

### Example request

```bash
curl -X POST https://api-staging.coinbax.com/api/v1/transactions/batch/<id>/submit-escrow-hash \
  -H "X-API-Key: $COINBAX_API_KEY" \
  -H "Content-Type: application/json" \
  -d '{
  "transactionHash": "0x1234567890abcdef1234567890abcdef1234567890abcdef1234567890abcdef"
}'
```

### Responses

**200** Hash recorded, batch advanced to submitted

```json
{}
```

**400** Batch is in a non-submittable state (already terminal, or still awaiting verification), or the request is for a custodial batch

**404** Batch not found

---

## Verify a 2FA code against a pending batch control

`POST /api/v1/transactions/batch/{id}/verify-2fa`

- Auth: X-API-Key header
- Operation ID: `TransactionController_verifyBatch2FA`

Accepts the verification code the sender received and clears the batch's `awaitingControlVerification` flag once every PRE_ESCROW verification control resolves.

### Parameters

| Name | In | Type | Required | Description |
| --- | --- | --- | --- | --- |
| `id` | path | string | yes |  |

### Request body

| Field | Type | Required | Description |
| --- | --- | --- | --- |
| `code` | string | yes | 6-digit verification code sent via SMS |

```json
{
  "code": "123456"
}
```

### Example request

```bash
curl -X POST https://api-staging.coinbax.com/api/v1/transactions/batch/<id>/verify-2fa \
  -H "X-API-Key: $COINBAX_API_KEY" \
  -H "Content-Type: application/json" \
  -d '{
  "code": "123456"
}'
```

### Responses

**200** Code accepted, batch updated

```json
{}
```

**400** Batch not awaiting verification, no pending 2FA control, or invalid/expired code

**404** Batch not found

---

## Preview platform fee for a transaction

`POST /api/v1/transactions/fee-preview`

- Auth: X-API-Key header
- Operation ID: `TransactionController_previewFee`

Calculate and preview the platform fee before creating a transaction

### Request body

| Field | Type | Required | Description |
| --- | --- | --- | --- |
| `amount` | number | yes | The proposed transaction amount |
| `billingConfig` | object | yes | Billing configuration from workspace |

```json
{
  "amount": 100.5,
  "billingConfig": {
    "feeType": "percentage",
    "feePercentage": 30,
    "fixedFeeAmount": 1,
    "tieredFees": [
      {
        "minAmount": 0,
        "maxAmount": 1000,
        "feePercentage": 30
      }
    ],
    "feeWalletAddress": "0x742d35Cc6634C0532925a3b844Bc9e7595f0bEb",
    "currency": "USDC",
    "isActive": true
  }
}
```

### Example request

```bash
curl -X POST https://api-staging.coinbax.com/api/v1/transactions/fee-preview \
  -H "X-API-Key: $COINBAX_API_KEY" \
  -H "Content-Type: application/json" \
  -d '{
  "amount": 100.5,
  "billingConfig": {
    "feeType": "percentage",
    "feePercentage": 30,
    "fixedFeeAmount": 1,
    "tieredFees": [
      {
        "minAmount": 0,
        "maxAmount": 1000,
        "feePercentage": 30
      }
    ],
    "feeWalletAddress": "0x742d35Cc6634C0532925a3b844Bc9e7595f0bEb",
    "currency": "USDC",
    "isActive": true
  }
}'
```

### Responses

**200** Fee preview calculated successfully

```json
{
  "originalAmount": 100.5,
  "feeAmount": 0.3015,
  "netAmount": 100.1985,
  "feeType": "percentage",
  "feeBasisPoints": 30,
  "feePercentageDisplay": "0.30%",
  "feeWalletAddress": "0x742d35Cc6634C0532925a3b844Bc9e7595f0bEb",
  "billingActive": true
}
```

---

## Create a fee quote for a pending transaction

`POST /api/v1/transactions/quote`

- Auth: X-API-Key header
- Operation ID: `TransactionController_createQuote`

Returns a 120-second-TTL quote with platform fee, workspace fee, gas pass-through (if applicable), and total charged. Pass the returned `quoteId` to `POST /transactions` to lock the rate.

### Request body

| Field | Type | Required | Description |
| --- | --- | --- | --- |
| `amountUsd` | string | yes | USD principal amount |
| `asset` | string | yes |  |
| `network` | string | yes | Canonical network identifier |

```json
{
  "amountUsd": "1000.00",
  "asset": "USDC",
  "network": "base"
}
```

### Example request

```bash
curl -X POST https://api-staging.coinbax.com/api/v1/transactions/quote \
  -H "X-API-Key: $COINBAX_API_KEY" \
  -H "Content-Type: application/json" \
  -d '{
  "amountUsd": "1000.00",
  "asset": "USDC",
  "network": "base"
}'
```

### Responses

**200** Quote created successfully

```json
{
  "quoteId": "9f8b7c6d-5e4f-4a3b-2c1d-0e9f8a7b6c5d",
  "expiresAt": "2026-01-15T12:00:00.000Z",
  "amountUsd": "1000.00",
  "platformFeeUsd": "2.500000",
  "workspaceFeeUsd": "5.000000",
  "gasPassThroughUsd": null,
  "totalOnTopUsd": "7.500000",
  "totalChargedUsd": "1007.500000",
  "gasCapExceeded": false
}
```

**404** Workspace has no ACTIVE PricingPlan / WorkspaceFeeSchedule. Operator intervention required.

**422** Gas-cap exceeded and PricingPlan.onGasCapBreach=BLOCK. The caller can retry later if gas drops.

**503** Transient: gas oracle / FX / coinbax-core unreachable. Retry.

---

## Get transaction statistics

`GET /api/v1/transactions/stats`

- Auth: X-API-Key header
- Operation ID: `TransactionController_getStatistics`

### Example request

```bash
curl https://api-staging.coinbax.com/api/v1/transactions/stats \
  -H "X-API-Key: $COINBAX_API_KEY"
```

### Responses

**200** Transaction statistics

---

## Get sanitized template + controls config for a template ID

`GET /api/v1/transactions/template-config`

- Auth: X-API-Key header
- Operation ID: `TransactionController_getTemplateConfig`

Returns public-safe summaries of each active control on a template. Used by pre-confirmation surfaces (e.g. SafeSend) to render "What protects your send" panels and to enforce client-side AmountLimit min/max. Sanitized: only a hand-picked subset of each control config is returned — policy/implementation fields are NOT exposed.

### Parameters

| Name | In | Type | Required | Description |
| --- | --- | --- | --- | --- |
| `templateId` | query | string | yes |  |

### Example request

```bash
curl https://api-staging.coinbax.com/api/v1/transactions/template-config?templateId=<templateId> \
  -H "X-API-Key: $COINBAX_API_KEY"
```

### Responses

**200** Template config retrieved

```json
{
  "templateId": "a1b2c3d4-e5f6-4a5b-8c9d-1234567890ab",
  "templateName": "Transmitter P2P",
  "templateVersion": "1.0.0",
  "controls": [
    {
      "type": "AmountLimit",
      "phase": "PRE_ESCROW",
      "name": "Amount Limit Check",
      "isRequired": true,
      "minAmount": 1,
      "maxAmount": 1000000,
      "currency": "USD",
      "delayAmount": 24,
      "delayUnit": "hours",
      "allowSenderRescind": true,
      "userConfigurable": true,
      "options": [
        "5m",
        "30m",
        "24h",
        "48h",
        "7d"
      ],
      "sanctionsLists": [
        "OFAC",
        "SDN",
        "EU"
      ],
      "checksSender": true,
      "checksRecipient": true,
      "verificationRequired": true
    }
  ]
}
```

**404** Template not found

---

## Get transaction by ID

`GET /api/v1/transactions/{id}`

- Auth: X-API-Key header
- Operation ID: `TransactionController_findOne`

### Parameters

| Name | In | Type | Required | Description |
| --- | --- | --- | --- | --- |
| `id` | path | string | yes |  |

### Example request

```bash
curl https://api-staging.coinbax.com/api/v1/transactions/<id> \
  -H "X-API-Key: $COINBAX_API_KEY"
```

### Responses

**200** Transaction details

```json
{
  "rescind": {
    "allowed": true,
    "deadline": "2026-06-15T12:00:00.000Z",
    "requiresReason": false,
    "notifyRecipient": true
  }
}
```

**404** Transaction not found

---

## Authorize release of an external-wallet escrow

`POST /api/v1/transactions/{id}/authorize-release`

- Auth: X-API-Key header
- Operation ID: `TransactionController_authorizeRelease`

Runs the PRE_RELEASE controls and records the aggregate result on-chain for an external-wallet (SafeSend) COINBAX escrow, so the sender wallet (releaseEarly / releaseAfterDelay) can release without the escrow's PRE_RELEASE gate reverting. Call this immediately BEFORE the on-chain release write. An early (pre-window) release waives the time-delay hold; a required non-time control failure auto-rejects the escrow and refunds the sender (do not then attempt an on-chain release).

### Parameters

| Name | In | Type | Required | Description |
| --- | --- | --- | --- | --- |
| `id` | path | string | yes |  |

### Request body

| Field | Type | Required | Description |
| --- | --- | --- | --- |
| `early` | boolean | no | Advisory hint that the sender intends an early (pre-window) release. The server authoritatively infers early vs after-delay from the escrow release time and ignores this value for the decision. |

```json
{
  "early": true
}
```

### Example request

```bash
curl -X POST https://api-staging.coinbax.com/api/v1/transactions/<id>/authorize-release \
  -H "X-API-Key: $COINBAX_API_KEY" \
  -H "Content-Type: application/json" \
  -d '{
  "early": true
}'
```

### Responses

**200** Release authorized (PRE_RELEASE attested Passed), or the escrow was rejected + refunded because a required control failed (see `rejected`).

**400** Not an external-wallet COINBAX escrow, not in ESCROWED status, or missing on-chain escrow id

**404** Transaction not found

---

## Get bit execution status for transaction

`GET /api/v1/transactions/{id}/bit-status`

- Auth: X-API-Key header
- Operation ID: `TransactionController_getBitStatus`

### Parameters

| Name | In | Type | Required | Description |
| --- | --- | --- | --- | --- |
| `id` | path | string | yes |  |

### Example request

```bash
curl https://api-staging.coinbax.com/api/v1/transactions/<id>/bit-status \
  -H "X-API-Key: $COINBAX_API_KEY"
```

### Responses

**200** Bit execution status retrieved successfully

```json
{
  "transactionId": "123e4567-e89b-12d3-a456-426614174000",
  "awaitingVerification": false,
  "controls": [
    {
      "controlId": "control-uuid",
      "controlType": "TimeDelay",
      "phase": "PRE_ESCROW",
      "success": true,
      "isRequired": true,
      "durationMs": 125,
      "data": {},
      "executedAt": "2026-01-15T12:00:00.000Z"
    }
  ],
  "success": true,
  "nextAction": "verify_2fa",
  "message": "All controls executed successfully"
}
```

**404** Transaction not found

---

## Cancel pre-escrow transaction the user never signed for

`POST /api/v1/transactions/{id}/cancel`

- Auth: X-API-Key header
- Operation ID: `TransactionController_cancelPending`

Marks a transaction in PENDING_USER_ACTION as FAILED with userCancelled=true. No on-chain action — funds were never escrowed. Use this when the user rejects the wallet popup and does not intend to retry; rejects with 400 if the transaction has already advanced to ESCROWED (use /refund or /rescind instead) or is already terminal.

### Parameters

| Name | In | Type | Required | Description |
| --- | --- | --- | --- | --- |
| `id` | path | string | yes |  |

### Request body

| Field | Type | Required | Description |
| --- | --- | --- | --- |
| `reason` | string | no | Optional human-readable reason for the cancellation. Surfaced in the transaction.failed webhook and stored on `metadata.cancelReason`. |

```json
{
  "reason": "User dismissed pending tx from SafeSend Active tab"
}
```

### Example request

```bash
curl -X POST https://api-staging.coinbax.com/api/v1/transactions/<id>/cancel \
  -H "X-API-Key: $COINBAX_API_KEY" \
  -H "Content-Type: application/json" \
  -d '{
  "reason": "User dismissed pending tx from SafeSend Active tab"
}'
```

### Responses

**200** Transaction cancelled successfully

```json
{
  "rescind": {
    "allowed": true,
    "deadline": "2026-06-15T12:00:00.000Z",
    "requiresReason": false,
    "notifyRecipient": true
  }
}
```

**400** Transaction not in PENDING_USER_ACTION, or already has an on-chain escrow address

**404** Transaction not found

---

## Complete transaction and release funds

`POST /api/v1/transactions/{id}/complete`

- Auth: X-API-Key header
- Operation ID: `TransactionController_complete`

### Parameters

| Name | In | Type | Required | Description |
| --- | --- | --- | --- | --- |
| `id` | path | string | yes |  |

### Request body

| Field | Type | Required | Description |
| --- | --- | --- | --- |
| `note` | string | no | Optional completion note |

```json
{
  "note": "Service delivered successfully"
}
```

### Example request

```bash
curl -X POST https://api-staging.coinbax.com/api/v1/transactions/<id>/complete \
  -H "X-API-Key: $COINBAX_API_KEY" \
  -H "Content-Type: application/json" \
  -d '{
  "note": "Service delivered successfully"
}'
```

### Responses

**200** Transaction completed successfully

```json
{
  "rescind": {
    "allowed": true,
    "deadline": "2026-06-15T12:00:00.000Z",
    "requiresReason": false,
    "notifyRecipient": true
  }
}
```

**400** Invalid transaction state for completion

---

## Refund transaction

`POST /api/v1/transactions/{id}/refund`

- Auth: X-API-Key header
- Operation ID: `TransactionController_refund`

### Parameters

| Name | In | Type | Required | Description |
| --- | --- | --- | --- | --- |
| `id` | path | string | yes |  |

### Request body

| Field | Type | Required | Description |
| --- | --- | --- | --- |
| `reason` | string | yes | Reason for refund |

```json
{
  "reason": "Customer request - item not as described"
}
```

### Example request

```bash
curl -X POST https://api-staging.coinbax.com/api/v1/transactions/<id>/refund \
  -H "X-API-Key: $COINBAX_API_KEY" \
  -H "Content-Type: application/json" \
  -d '{
  "reason": "Customer request - item not as described"
}'
```

### Responses

**200** Transaction refunded successfully

```json
{
  "rescind": {
    "allowed": true,
    "deadline": "2026-06-15T12:00:00.000Z",
    "requiresReason": false,
    "notifyRecipient": true
  }
}
```

**400** Invalid transaction state for refund

---

## Rescind transaction during hold period (sender only)

`POST /api/v1/transactions/{id}/rescind`

- Auth: X-API-Key header
- Operation ID: `TransactionController_rescind`

Allows the sender to cancel a transaction during the Payment Hold Period and have funds returned to their wallet. Only available if the TimeDelay control has allowSenderRescind enabled.

### Parameters

| Name | In | Type | Required | Description |
| --- | --- | --- | --- | --- |
| `id` | path | string | yes |  |

### Request body

| Field | Type | Required | Description |
| --- | --- | --- | --- |
| `reason` | string | no | Reason for rescinding the transaction (required if control config has rescindRequiresReason: true) |
| `senderWallet` | string | yes | Sender wallet address for authorization verification |

```json
{
  "reason": "Changed my mind about the purchase",
  "senderWallet": "0x1234567890abcdef1234567890abcdef12345678"
}
```

### Example request

```bash
curl -X POST https://api-staging.coinbax.com/api/v1/transactions/<id>/rescind \
  -H "X-API-Key: $COINBAX_API_KEY" \
  -H "Content-Type: application/json" \
  -d '{
  "reason": "Changed my mind about the purchase",
  "senderWallet": "0x1234567890abcdef1234567890abcdef12345678"
}'
```

### Responses

**200** Transaction rescinded successfully, funds returned to sender

```json
{
  "rescind": {
    "allowed": true,
    "deadline": "2026-06-15T12:00:00.000Z",
    "requiresReason": false,
    "notifyRecipient": true
  }
}
```

**400** Transaction cannot be rescinded (not in ESCROWED status, rescind not enabled, or rescind window expired)

**403** Only the sender can rescind a transaction

**404** Transaction not found

---

## Resend 2FA verification code

`POST /api/v1/transactions/{id}/resend-2fa`

- Auth: X-API-Key header
- Operation ID: `TransactionController_resend2FA`

### Parameters

| Name | In | Type | Required | Description |
| --- | --- | --- | --- | --- |
| `id` | path | string | yes |  |

### Example request

```bash
curl -X POST https://api-staging.coinbax.com/api/v1/transactions/<id>/resend-2fa \
  -H "X-API-Key: $COINBAX_API_KEY"
```

### Responses

**200** 2FA code resent successfully

```json
{
  "message": "2FA code resent successfully",
  "expiresIn": 300
}
```

**400** Transaction not awaiting 2FA verification

**404** Transaction not found

---

## Retry a failed bit execution

`POST /api/v1/transactions/{id}/retry-bit/{bitId}`

- Auth: X-API-Key header
- Operation ID: `TransactionController_retryBit`

### Parameters

| Name | In | Type | Required | Description |
| --- | --- | --- | --- | --- |
| `id` | path | string | yes |  |
| `bitId` | path | string | yes |  |

### Example request

```bash
curl -X POST https://api-staging.coinbax.com/api/v1/transactions/<id>/retry-bit/<bitId> \
  -H "X-API-Key: $COINBAX_API_KEY"
```

### Responses

**200** Bit execution retried successfully

```json
{
  "rescind": {
    "allowed": true,
    "deadline": "2026-06-15T12:00:00.000Z",
    "requiresReason": false,
    "notifyRecipient": true
  }
}
```

**400** Invalid bit ID or bit cannot be retried

**404** Transaction or bit not found

---

## Submit escrow transaction hash for external wallet (Transmitter) transactions

`POST /api/v1/transactions/{id}/submit-escrow-hash`

- Auth: X-API-Key header
- Operation ID: `TransactionController_submitEscrowHash`

After the user deposits USDC to the escrow contract via MetaMask, submit the transaction hash and escrow ID to confirm the escrow was created

### Parameters

| Name | In | Type | Required | Description |
| --- | --- | --- | --- | --- |
| `id` | path | string | yes |  |

### Request body

| Field | Type | Required | Description |
| --- | --- | --- | --- |
| `transactionHash` | string | yes | Blockchain transaction hash from user wallet escrow deposit |
| `escrowId` | string | no | Escrow ID returned by the smart contract (optional - will be detected from blockchain events if not provided) |

```json
{
  "transactionHash": "0x1234567890abcdef1234567890abcdef1234567890abcdef1234567890abcdef",
  "escrowId": "0x9876543210abcdef9876543210abcdef9876543210abcdef9876543210abcdef"
}
```

### Example request

```bash
curl -X POST https://api-staging.coinbax.com/api/v1/transactions/<id>/submit-escrow-hash \
  -H "X-API-Key: $COINBAX_API_KEY" \
  -H "Content-Type: application/json" \
  -d '{
  "transactionHash": "0x1234567890abcdef1234567890abcdef1234567890abcdef1234567890abcdef",
  "escrowId": "0x9876543210abcdef9876543210abcdef9876543210abcdef9876543210abcdef"
}'
```

### Responses

**200** Escrow hash submitted and verified successfully

```json
{
  "rescind": {
    "allowed": true,
    "deadline": "2026-06-15T12:00:00.000Z",
    "requiresReason": false,
    "notifyRecipient": true
  }
}
```

**400** Invalid transaction state or escrow verification failed

**404** Transaction not found

---

## Submit blockchain transaction hash for RAW transaction

`POST /api/v1/transactions/{id}/submit-hash`

- Auth: X-API-Key header
- Operation ID: `TransactionController_submitTransactionHash`

After sending USDC from your wallet, submit the transaction hash to complete the transaction

### Parameters

| Name | In | Type | Required | Description |
| --- | --- | --- | --- | --- |
| `id` | path | string | yes |  |

### Request body

| Field | Type | Required | Description |
| --- | --- | --- | --- |
| `transactionHash` | string | yes | Blockchain transaction hash from user wallet |

```json
{
  "transactionHash": "0x1234567890abcdef1234567890abcdef1234567890abcdef1234567890abcdef"
}
```

### Example request

```bash
curl -X POST https://api-staging.coinbax.com/api/v1/transactions/<id>/submit-hash \
  -H "X-API-Key: $COINBAX_API_KEY" \
  -H "Content-Type: application/json" \
  -d '{
  "transactionHash": "0x1234567890abcdef1234567890abcdef1234567890abcdef1234567890abcdef"
}'
```

### Responses

**200** Transaction hash submitted and verified successfully

```json
{
  "rescind": {
    "allowed": true,
    "deadline": "2026-06-15T12:00:00.000Z",
    "requiresReason": false,
    "notifyRecipient": true
  }
}
```

**400** Invalid transaction state or hash verification failed

**404** Transaction not found

---

## Verify 2FA code for transaction

`POST /api/v1/transactions/{id}/verify-2fa`

- Auth: X-API-Key header
- Operation ID: `TransactionController_verify2FA`

### Parameters

| Name | In | Type | Required | Description |
| --- | --- | --- | --- | --- |
| `id` | path | string | yes |  |

### Request body

| Field | Type | Required | Description |
| --- | --- | --- | --- |
| `code` | string | yes | 6-digit verification code sent via SMS |

```json
{
  "code": "123456"
}
```

### Example request

```bash
curl -X POST https://api-staging.coinbax.com/api/v1/transactions/<id>/verify-2fa \
  -H "X-API-Key: $COINBAX_API_KEY" \
  -H "Content-Type: application/json" \
  -d '{
  "code": "123456"
}'
```

### Responses

**200** 2FA verified successfully, transaction can proceed

```json
{
  "rescind": {
    "allowed": true,
    "deadline": "2026-06-15T12:00:00.000Z",
    "requiresReason": false,
    "notifyRecipient": true
  }
}
```

**400** Invalid or expired verification code

**404** Transaction not found
