Coinbax API

Transactions

Payment operations and lifecycle management

View as Markdown

List transactions with filters

GET/api/v2/transactionsBearer token

Query parameters

  • pagenumber

    Page number

  • limitnumber

    Items per page

  • statusenum

    Filter by status

    "pending" · "risk_review" · "pending_user_action" · "escrowed" · "in_review" · "disputed" · "completed" · "refunded" · "failed" · "rescinded"
  • fromAddressstring

    Filter by sender blockchain address

  • toAddressstring

    Filter by receiver blockchain address

  • dateFromstring

    Filter by date from (ISO 8601)

  • dateTostring

    Filter by date to (ISO 8601)

  • customerIdstring

    Filter by customer ID

  • walletAddressstring

    Filter by wallet address (matches fromAddress OR toAddress)

  • directionenum

    Filter by direction of money movement relative to the customer wallet. Rows that could not be classified are excluded from every direction.

    "deposit" · "withdrawal" · "internal"

Responses

200List of transactions

Response follows the unified success / data / meta / error envelope.

curl https://api-staging.coinbax.com/api/v2/transactions \
  -H "Authorization: Bearer $ACCESS_TOKEN"
const response = await fetch('https://api-staging.coinbax.com/api/v2/transactions', {
  headers: {
    'Authorization': `Bearer ${process.env.ACCESS_TOKEN}`,
  },
});
const result = await response.json();

Create a new transaction

POST/api/v2/transactionsBearer token

Request body

  • fromAddressstringrequired

    Sender blockchain address

  • toAddressstringrequired

    Receiver blockchain address

  • amountnumberrequired

    Transaction amount (minimum 0.01, maximum 1,000,000)

  • currencyenum"USDC" · "USDT" · "ETH" · "MockUSDC" · "USDG"

    Currency type

  • blockchainNetworkenum"ethereum" · "base" · "solana" · "optimism" · "arbitrum" · "base-sepolia" · "ethereum-sepolia" · "optimism-sepolia" · "arbitrum-sepolia" · "solana-devnet" · "solana-testnet"

    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.

  • orchestrationTypeenum"raw" · "coinbax"

    Orchestration type: raw (direct blockchain) or coinbax (smart contract escrow)

  • templateIdstring (uuid)

    Template ID from coinbax-library. The template will be fetched from the library service.

  • contractTemplateIdstring (uuid)

    Contract template ID (alias for templateId, for backward compatibility)

  • templateobject

    Complete template data embedded in request (optional, prefer using templateId instead)

    • TemplateDto
      • templateIdstringrequired

        Template ID from coinbax-library

      • templateNamestringrequired

        Template name

      • templateVersionstringrequired

        Template version

      • controlsarray of TemplateControlDtorequired

        Array of controls to execute

        array of TemplateControlDto
        • idstringrequired

          Control ID

        • typestringrequired

          Control type (e.g., TimeDelay, TwoFactorAuth, ComplianceCheck)

        • namestringrequired

          Control name

        • executionPhasestringrequired

          Execution phase (PRE_ESCROW, POST_ESCROW, POST_RELEASE)

        • executionOrdernumberrequired

          Execution order within the phase

        • configobjectrequired

          Control configuration

          object

          Control configuration

        • isRequiredboolean

          Whether this control is required for the transaction

        • isActiveboolean

          Whether this control is active

  • metadataobject

    Additional metadata about the transaction

    object

    Additional metadata about the transaction

  • billingConfigobject

    Billing configuration from workspace (passed by coinbax-core)

    • BillingConfigDto
      • feeTypeenumrequired"percentage" · "fixed" · "tiered"

        Type of fee calculation

      • feePercentagenumber

        Fee percentage in basis points (100 = 1%). Required for percentage type.

      • fixedFeeAmountnumber

        Fixed fee amount in smallest currency unit. Required for fixed type.

      • tieredFeesarray of TieredFeeDto

        Array of tiered fee configurations. Required for tiered type.

        array of TieredFeeDto
        • minAmountnumberrequired

          Minimum transaction amount for this tier

        • maxAmountnumberrequired

          Maximum transaction amount for this tier

        • feePercentagenumberrequired

          Fee percentage in basis points (100 = 1%)

      • feeWalletAddressstringrequired

        Coinbax fee collection wallet address

      • currencystringrequired

        Currency for fee collection

      • isActivebooleanrequired

        Whether billing is active for this workspace

  • quoteIdstring (uuid)

    FeeQuote id from a prior /transactions/quote call. Required when the workspace has BILLING_ENABLED=true (Phase 1 cutover).

  • signerTypestring

    Which wallet signs this transaction. Omit it and Coinbax signs with a wallet it operates on your behalf. Send `external_wallet` when the sender signs with their own wallet, which is also the mode SafeSend and other consumer flows use. Gas-sponsored variants exist for senders who hold their own keys but should not pay gas; contact support before using them.

  • customerIdstring

    Customer ID for external wallet transactions (Transmitter users)

  • idempotencyKeystring

    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.

Responses

201Transaction created successfully
  • rescindobject

    Public-safe summary of the TimeDelay rescind config. Lets clients gate the Cancel/Rescind affordance without a second round-trip. Null when the transaction has no TimeDelay control (RAW orchestration, templates without a hold period). Absent from response on the list endpoint and on legacy api revisions.

    • TransactionRescindInfo
      • allowedbooleanrequired

        Whether the sender is allowed to rescind/cancel the transaction during the hold window. Defaults to true when not explicitly set on the underlying control config.

      • deadlineobjectrequired

        ISO 8601 timestamp after which rescind is no longer allowed. Null when the rescind window matches the full hold period.

        object

        ISO 8601 timestamp after which rescind is no longer allowed. Null when the rescind window matches the full hold period.

      • requiresReasonbooleanrequired

        Whether the rescind endpoint requires a non-empty `reason` field in the request body.

      • notifyRecipientbooleanrequired

        Whether the recipient is notified (e.g. via SMS) when a rescind succeeds.

{
  "rescind": {
    "allowed": true,
    "deadline": "2026-06-15T12:00:00.000Z",
    "requiresReason": false,
    "notifyRecipient": true
  }
}
409Duplicate transaction detected within time window

Response follows the unified success / data / meta / error envelope.

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

Response follows the unified success / data / meta / error envelope.

curl -X POST https://api-staging.coinbax.com/api/v2/transactions \
  -H "Authorization: Bearer $ACCESS_TOKEN" \
  -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": "external_wallet",
  "customerId": "cust_abc123",
  "idempotencyKey": "tx_unique_key_12345"
}'
const response = await fetch('https://api-staging.coinbax.com/api/v2/transactions', {
  method: 'POST',
  headers: {
    'Authorization': `Bearer ${process.env.ACCESS_TOKEN}`,
    'Content-Type': 'application/json',
  },
  body: JSON.stringify({
    "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": "external_wallet",
    "customerId": "cust_abc123",
    "idempotencyKey": "tx_unique_key_12345"
  }),
});
const result = await response.json();

List transaction batches

GET/api/v2/transactions/batchesBearer token

Query parameters

  • pagenumber

    Page number

  • limitnumber

    Items per page

  • statusenum

    Filter by batch status

    "pending_user_action" · "submitted" · "completed" · "failed"
  • fromAddressstring

    Filter by sender blockchain address

  • dateFromstring

    Filter by date from (ISO 8601)

  • dateTostring

    Filter by date to (ISO 8601)

  • customerIdstring

    Filter by customer ID

Responses

200Paginated batches

Response follows the unified success / data / meta / error envelope.

curl https://api-staging.coinbax.com/api/v2/transactions/batches \
  -H "Authorization: Bearer $ACCESS_TOKEN"
const response = await fetch('https://api-staging.coinbax.com/api/v2/transactions/batches', {
  headers: {
    'Authorization': `Bearer ${process.env.ACCESS_TOKEN}`,
  },
});
const result = await response.json();

Create a multi-recipient transaction batch

POST/api/v2/transactions/batchesBearer token

v1 called this `batch`; v2 uses the plural collection name.

Request body

  • fromAddressstringrequired

    Sender blockchain address (one signer for the whole batch).

  • legsarray of CreateBatchLegDtorequired

    Per-recipient legs (1..MAX_BATCH_LEGS).

    array of CreateBatchLegDto
    • toAddressstringrequired

      Receiver blockchain address for this leg.

    • amountnumberrequired

      Per-leg amount (minimum 0.01, maximum 1,000,000).

    • metadataobject

      Per-leg metadata (e.g., recipient name from the address book). Distinct from batch-level metadata.

      object

      Per-leg metadata (e.g., recipient name from the address book). Distinct from batch-level metadata.

  • currencyenum"USDC" · "USDT" · "ETH" · "MockUSDC" · "USDG"

    Currency type — shared by every leg in the batch.

  • blockchainNetworkstring

    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.

  • orchestrationTypeenum"raw" · "coinbax"

    Orchestration type for every leg.

  • signerTypestring

    Which wallet signs this transaction. Omit it and Coinbax signs with a wallet it operates on your behalf. Send `external_wallet` when the sender signs with their own wallet, which is also the mode SafeSend and other consumer flows use. Gas-sponsored variants exist for senders who hold their own keys but should not pay gas; contact support before using them. Batches currently require the sender's own wallet.

  • templateIdstring (uuid)

    Template ID from coinbax-library. Stored on the batch in B1; control execution against the template is wired in B2.

  • customerIdstring

    Customer ID for external-wallet flows (Transmitter, SafeSend).

  • idempotencyKeystring

    Idempotency key — if provided, an existing batch with the same key is returned instead of creating a new one.

  • metadataobject

    Batch-level metadata (distinct from per-leg metadata).

    object

    Batch-level metadata (distinct from per-leg metadata).

  • templateobject

    Embedded template (alternative to `templateId`, deprecated for consistency with `CreateTransactionDto`).

    • TemplateDto
      • templateIdstringrequired

        Template ID from coinbax-library

      • templateNamestringrequired

        Template name

      • templateVersionstringrequired

        Template version

      • controlsarray of TemplateControlDtorequired

        Array of controls to execute

        array of TemplateControlDto
        • idstringrequired

          Control ID

        • typestringrequired

          Control type (e.g., TimeDelay, TwoFactorAuth, ComplianceCheck)

        • namestringrequired

          Control name

        • executionPhasestringrequired

          Execution phase (PRE_ESCROW, POST_ESCROW, POST_RELEASE)

        • executionOrdernumberrequired

          Execution order within the phase

        • configobjectrequired

          Control configuration

          object

          Control configuration

        • isRequiredboolean

          Whether this control is required for the transaction

        • isActiveboolean

          Whether this control is active

Responses

201
object
{}
400Invalid batch request

Response follows the unified success / data / meta / error envelope.

curl -X POST https://api-staging.coinbax.com/api/v2/transactions/batches \
  -H "Authorization: Bearer $ACCESS_TOKEN" \
  -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
      }
    ]
  }
}'
const response = await fetch('https://api-staging.coinbax.com/api/v2/transactions/batches', {
  method: 'POST',
  headers: {
    'Authorization': `Bearer ${process.env.ACCESS_TOKEN}`,
    'Content-Type': 'application/json',
  },
  body: JSON.stringify({
    "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
        }
      ]
    }
  }),
});
const result = await response.json();

Get a transaction batch by ID

GET/api/v2/transactions/batches/{id}Bearer token

Path parameters

  • idstringrequired

Responses

200
object
{}
404Batch not found

Response follows the unified success / data / meta / error envelope.

curl https://api-staging.coinbax.com/api/v2/transactions/batches/<id> \
  -H "Authorization: Bearer $ACCESS_TOKEN"
const response = await fetch('https://api-staging.coinbax.com/api/v2/transactions/batches/<id>', {
  headers: {
    'Authorization': `Bearer ${process.env.ACCESS_TOKEN}`,
  },
});
const result = await response.json();

Cancel an unsubmitted batch

POST/api/v2/transactions/batches/{id}/cancelBearer token

Requires `reverse:transactions` in v2. v1 required `write:transactions`, which let a create-only integration cancel a batch — the asymmetry v2 fixes.

Path parameters

  • idstringrequired

Request body

  • reasonstring

    Human-readable reason recorded on the batch metadata. Surfaces in audit logs and the cancel webhook payload. Truncated at 500 chars.

Responses

200
object
{}
400Batch cannot be cancelled

Response follows the unified success / data / meta / error envelope.

404Batch not found

Response follows the unified success / data / meta / error envelope.

curl -X POST https://api-staging.coinbax.com/api/v2/transactions/batches/<id>/cancel \
  -H "Authorization: Bearer $ACCESS_TOKEN" \
  -H "Content-Type: application/json" \
  -d '{
  "reason": "Sender closed the tab before signing batchCreateEscrow"
}'
const response = await fetch('https://api-staging.coinbax.com/api/v2/transactions/batches/<id>/cancel', {
  method: 'POST',
  headers: {
    'Authorization': `Bearer ${process.env.ACCESS_TOKEN}`,
    'Content-Type': 'application/json',
  },
  body: JSON.stringify({
    "reason": "Sender closed the tab before signing batchCreateEscrow"
  }),
});
const result = await response.json();

Record the on-chain batchCreateEscrow transaction hash

POST/api/v2/transactions/batches/{id}/escrow-hashBearer token

v1 called this `batch/:id/submit-escrow-hash`.

Path parameters

  • idstringrequired

Request body

  • transactionHashstringrequired

    On-chain transaction hash for the `batchCreateEscrow` call. Shared by every leg in the batch (one signature, one tx).

Responses

200
object
{}
400Invalid batch state or hash

Response follows the unified success / data / meta / error envelope.

404Batch not found

Response follows the unified success / data / meta / error envelope.

curl -X POST https://api-staging.coinbax.com/api/v2/transactions/batches/<id>/escrow-hash \
  -H "Authorization: Bearer $ACCESS_TOKEN" \
  -H "Content-Type: application/json" \
  -d '{
  "transactionHash": "0x1234567890abcdef1234567890abcdef1234567890abcdef1234567890abcdef"
}'
const response = await fetch('https://api-staging.coinbax.com/api/v2/transactions/batches/<id>/escrow-hash', {
  method: 'POST',
  headers: {
    'Authorization': `Bearer ${process.env.ACCESS_TOKEN}`,
    'Content-Type': 'application/json',
  },
  body: JSON.stringify({
    "transactionHash": "0x1234567890abcdef1234567890abcdef1234567890abcdef1234567890abcdef"
  }),
});
const result = await response.json();

Create a fee quote for a pending transaction

POST/api/v2/transactions/quoteBearer token

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

  • amountUsdstringrequired

    USD principal amount

  • assetstringrequired
  • networkstringrequired

    Canonical network identifier

Responses

200Quote created successfully
  • quoteIdstring (uuid)required
  • expiresAtstring (date-time)required
  • amountUsdstringrequired
  • platformFeeUsdstringrequired
  • workspaceFeeUsdobjectrequired
    object
  • gasPassThroughUsdobjectrequired
    object
  • totalOnTopUsdstringrequired
  • totalChargedUsdstringrequired
  • gasCapExceededbooleanrequired
{
  "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
}
404Workspace has no ACTIVE PricingPlan / WorkspaceFeeSchedule. Operator intervention required.

Response follows the unified success / data / meta / error envelope.

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

Response follows the unified success / data / meta / error envelope.

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

Response follows the unified success / data / meta / error envelope.

curl -X POST https://api-staging.coinbax.com/api/v2/transactions/quote \
  -H "Authorization: Bearer $ACCESS_TOKEN" \
  -H "Content-Type: application/json" \
  -d '{
  "amountUsd": "1000.00",
  "asset": "USDC",
  "network": "base"
}'
const response = await fetch('https://api-staging.coinbax.com/api/v2/transactions/quote', {
  method: 'POST',
  headers: {
    'Authorization': `Bearer ${process.env.ACCESS_TOKEN}`,
    'Content-Type': 'application/json',
  },
  body: JSON.stringify({
    "amountUsd": "1000.00",
    "asset": "USDC",
    "network": "base"
  }),
});
const result = await response.json();

Get transaction statistics

GET/api/v2/transactions/statsBearer token

Optional dateFrom/dateTo scope the byStatus aggregate. The windows block (24h/7d/30d) is always computed over its own fixed spans.

Query parameters

  • dateFromstring

    Scope stats to transactions created at or after this time (ISO 8601)

  • dateTostring

    Scope stats to transactions created at or before this time (ISO 8601)

  • directionenum

    Scope stats to one direction of money movement. Applies to both the byStatus aggregate and the fixed 24h/7d/30d windows.

    "deposit" · "withdrawal" · "internal"

Responses

200Transaction statistics

Response follows the unified success / data / meta / error envelope.

curl https://api-staging.coinbax.com/api/v2/transactions/stats \
  -H "Authorization: Bearer $ACCESS_TOKEN"
const response = await fetch('https://api-staging.coinbax.com/api/v2/transactions/stats', {
  headers: {
    'Authorization': `Bearer ${process.env.ACCESS_TOKEN}`,
  },
});
const result = await response.json();

Get sanitized template + controls config for a template ID

GET/api/v2/transactions/template-configBearer token

Returns public-safe summaries of each active control on a template. Use it to render pre-confirmation screens (for example, "what protects this payment") 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.

Query parameters

  • templateIdstringrequired

Responses

200Template config retrieved
  • templateIdstringrequired

    Template UUID.

  • templateNamestringrequired

    Template name (e.g. "Transmitter P2P").

  • templateVersionstringrequired

    Template version string from the library (e.g. "1.0.0").

  • controlsarray of TemplateControlSummaryrequired

    Active controls on the template, sanitized to a small set of public-safe fields per type. See TemplateControlSummary.

    array of TemplateControlSummary
    • typestringrequired

      Control type. Tolerates the upstream `ChainlysisSanctions` typo alongside the corrected `ChainalysisSanctions` for now (see PR #74).

    • phasestringrequired

      Execution phase: PRE_ESCROW, POST_ESCROW, PRE_RELEASE, or POST_RELEASE.

    • namestringrequired

      Human-readable control name (from the template, e.g. "Sanctions Screening").

    • isRequiredbooleanrequired

      Whether this control is required (vs. optional — driven by `metadata.optional` on the template control).

    • minAmountnumber

      AmountLimit: minimum allowed amount per send (inclusive).

    • maxAmountnumber

      AmountLimit: maximum allowed amount per send (inclusive).

    • currencystring

      AmountLimit: currency the limits are denominated in (e.g. "USD", "USDC").

    • delayAmountnumber

      TimeDelay: hold-period duration amount.

    • delayUnitenum"seconds" · "minutes" · "hours" · "days"

      TimeDelay: unit for `delayAmount`.

    • allowSenderRescindboolean

      TimeDelay: whether the sender can rescind/cancel during the hold window.

    • userConfigurableboolean

      TimeDelay: whether the user can pick a delay value per transaction (vs. always the template default). When true, the frontend should render `options` as selectable presets. When false or absent, the frontend renders the template default as a read-only display.

    • optionsarray of string

      TimeDelay: allowed preset values (when `userConfigurable` is true) using compact duration notation: `Nm` minutes, `Nh` hours, `Nd` days. Frontend parses and renders these as picker chips. Empty / absent means no template-defined presets — frontend may fall back to its own defaults.

      array of string
      string
    • sanctionsListsarray of string

      ChainlysisSanctions / ChainalysisSanctions: which sanctions lists the screening checks (e.g. OFAC, SDN, EU).

      array of string
      string
    • checksSenderboolean

      ChainlysisSanctions / ChainalysisSanctions: whether the sender wallet is screened.

    • checksRecipientboolean

      ChainlysisSanctions / ChainalysisSanctions: whether the recipient wallet is screened.

    • verificationRequiredboolean

      TwilioSMS: whether the SMS-verification step is mandatory (vs. lookup-only).

{
  "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
    }
  ]
}
404Template not found

Response follows the unified success / data / meta / error envelope.

curl https://api-staging.coinbax.com/api/v2/transactions/template-config?templateId=<templateId> \
  -H "Authorization: Bearer $ACCESS_TOKEN"
const response = await fetch('https://api-staging.coinbax.com/api/v2/transactions/template-config?templateId=<templateId>', {
  headers: {
    'Authorization': `Bearer ${process.env.ACCESS_TOKEN}`,
  },
});
const result = await response.json();

Get transaction by ID

GET/api/v2/transactions/{id}Bearer token

Path parameters

  • idstringrequired

Responses

200Transaction details
  • rescindobject

    Public-safe summary of the TimeDelay rescind config. Lets clients gate the Cancel/Rescind affordance without a second round-trip. Null when the transaction has no TimeDelay control (RAW orchestration, templates without a hold period). Absent from response on the list endpoint and on legacy api revisions.

    • TransactionRescindInfo
      • allowedbooleanrequired

        Whether the sender is allowed to rescind/cancel the transaction during the hold window. Defaults to true when not explicitly set on the underlying control config.

      • deadlineobjectrequired

        ISO 8601 timestamp after which rescind is no longer allowed. Null when the rescind window matches the full hold period.

        object

        ISO 8601 timestamp after which rescind is no longer allowed. Null when the rescind window matches the full hold period.

      • requiresReasonbooleanrequired

        Whether the rescind endpoint requires a non-empty `reason` field in the request body.

      • notifyRecipientbooleanrequired

        Whether the recipient is notified (e.g. via SMS) when a rescind succeeds.

{
  "rescind": {
    "allowed": true,
    "deadline": "2026-06-15T12:00:00.000Z",
    "requiresReason": false,
    "notifyRecipient": true
  }
}
404Transaction not found

Response follows the unified success / data / meta / error envelope.

curl https://api-staging.coinbax.com/api/v2/transactions/<id> \
  -H "Authorization: Bearer $ACCESS_TOKEN"
const response = await fetch('https://api-staging.coinbax.com/api/v2/transactions/<id>', {
  headers: {
    'Authorization': `Bearer ${process.env.ACCESS_TOKEN}`,
  },
});
const result = await response.json();

Cancel pre-escrow transaction the user never signed for

POST/api/v2/transactions/{id}/cancelBearer token

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. Requires `reverse:transactions` in v2.

Path parameters

  • idstringrequired

Request body

  • reasonstring

    Optional human-readable reason for the cancellation. Surfaced in the transaction.failed webhook and stored on `metadata.cancelReason`.

Responses

200Transaction cancelled successfully
  • rescindobject

    Public-safe summary of the TimeDelay rescind config. Lets clients gate the Cancel/Rescind affordance without a second round-trip. Null when the transaction has no TimeDelay control (RAW orchestration, templates without a hold period). Absent from response on the list endpoint and on legacy api revisions.

    • TransactionRescindInfo
      • allowedbooleanrequired

        Whether the sender is allowed to rescind/cancel the transaction during the hold window. Defaults to true when not explicitly set on the underlying control config.

      • deadlineobjectrequired

        ISO 8601 timestamp after which rescind is no longer allowed. Null when the rescind window matches the full hold period.

        object

        ISO 8601 timestamp after which rescind is no longer allowed. Null when the rescind window matches the full hold period.

      • requiresReasonbooleanrequired

        Whether the rescind endpoint requires a non-empty `reason` field in the request body.

      • notifyRecipientbooleanrequired

        Whether the recipient is notified (e.g. via SMS) when a rescind succeeds.

{
  "rescind": {
    "allowed": true,
    "deadline": "2026-06-15T12:00:00.000Z",
    "requiresReason": false,
    "notifyRecipient": true
  }
}
400Transaction not in PENDING_USER_ACTION, or already has an on-chain escrow address

Response follows the unified success / data / meta / error envelope.

403Token lacks `reverse:transactions`

Response follows the unified success / data / meta / error envelope.

404Transaction not found

Response follows the unified success / data / meta / error envelope.

curl -X POST https://api-staging.coinbax.com/api/v2/transactions/<id>/cancel \
  -H "Authorization: Bearer $ACCESS_TOKEN" \
  -H "Content-Type: application/json" \
  -d '{
  "reason": "User dismissed pending tx from SafeSend Active tab"
}'
const response = await fetch('https://api-staging.coinbax.com/api/v2/transactions/<id>/cancel', {
  method: 'POST',
  headers: {
    'Authorization': `Bearer ${process.env.ACCESS_TOKEN}`,
    'Content-Type': 'application/json',
  },
  body: JSON.stringify({
    "reason": "User dismissed pending tx from SafeSend Active tab"
  }),
});
const result = await response.json();

Record the on-chain escrow-creation transaction hash

POST/api/v2/transactions/{id}/escrow-hashBearer token

v1 called this `submit-escrow-hash`. Note coinbax-core proxies the v1 spelling and republishes it on the Workspace API, deliberately keeping that name rather than dragging a live consumer through a rename — so one operation has two spellings across the two surfaces.

Path parameters

  • idstringrequired

Request body

  • transactionHashstringrequired

    Blockchain transaction hash from user wallet escrow deposit

  • escrowIdstring

    Escrow ID returned by the smart contract (optional - will be detected from blockchain events if not provided)

Responses

200
  • rescindobject

    Public-safe summary of the TimeDelay rescind config. Lets clients gate the Cancel/Rescind affordance without a second round-trip. Null when the transaction has no TimeDelay control (RAW orchestration, templates without a hold period). Absent from response on the list endpoint and on legacy api revisions.

    • TransactionRescindInfo
      • allowedbooleanrequired

        Whether the sender is allowed to rescind/cancel the transaction during the hold window. Defaults to true when not explicitly set on the underlying control config.

      • deadlineobjectrequired

        ISO 8601 timestamp after which rescind is no longer allowed. Null when the rescind window matches the full hold period.

        object

        ISO 8601 timestamp after which rescind is no longer allowed. Null when the rescind window matches the full hold period.

      • requiresReasonbooleanrequired

        Whether the rescind endpoint requires a non-empty `reason` field in the request body.

      • notifyRecipientbooleanrequired

        Whether the recipient is notified (e.g. via SMS) when a rescind succeeds.

{
  "rescind": {
    "allowed": true,
    "deadline": "2026-06-15T12:00:00.000Z",
    "requiresReason": false,
    "notifyRecipient": true
  }
}
400Invalid transaction state

Response follows the unified success / data / meta / error envelope.

404Transaction not found

Response follows the unified success / data / meta / error envelope.

curl -X POST https://api-staging.coinbax.com/api/v2/transactions/<id>/escrow-hash \
  -H "Authorization: Bearer $ACCESS_TOKEN" \
  -H "Content-Type: application/json" \
  -d '{
  "transactionHash": "0x1234567890abcdef1234567890abcdef1234567890abcdef1234567890abcdef",
  "escrowId": "0x9876543210abcdef9876543210abcdef9876543210abcdef9876543210abcdef"
}'
const response = await fetch('https://api-staging.coinbax.com/api/v2/transactions/<id>/escrow-hash', {
  method: 'POST',
  headers: {
    'Authorization': `Bearer ${process.env.ACCESS_TOKEN}`,
    'Content-Type': 'application/json',
  },
  body: JSON.stringify({
    "transactionHash": "0x1234567890abcdef1234567890abcdef1234567890abcdef1234567890abcdef",
    "escrowId": "0x9876543210abcdef9876543210abcdef9876543210abcdef9876543210abcdef"
  }),
});
const result = await response.json();

Record the on-chain transaction hash for a RAW transfer

POST/api/v2/transactions/{id}/hashBearer token

v1 called this `submit-hash`; the verb already says you are submitting. Pairs with `escrow-hash` for COINBAX escrows.

Path parameters

  • idstringrequired

Request body

  • transactionHashstringrequired

    Blockchain transaction hash from user wallet

Responses

200
  • rescindobject

    Public-safe summary of the TimeDelay rescind config. Lets clients gate the Cancel/Rescind affordance without a second round-trip. Null when the transaction has no TimeDelay control (RAW orchestration, templates without a hold period). Absent from response on the list endpoint and on legacy api revisions.

    • TransactionRescindInfo
      • allowedbooleanrequired

        Whether the sender is allowed to rescind/cancel the transaction during the hold window. Defaults to true when not explicitly set on the underlying control config.

      • deadlineobjectrequired

        ISO 8601 timestamp after which rescind is no longer allowed. Null when the rescind window matches the full hold period.

        object

        ISO 8601 timestamp after which rescind is no longer allowed. Null when the rescind window matches the full hold period.

      • requiresReasonbooleanrequired

        Whether the rescind endpoint requires a non-empty `reason` field in the request body.

      • notifyRecipientbooleanrequired

        Whether the recipient is notified (e.g. via SMS) when a rescind succeeds.

{
  "rescind": {
    "allowed": true,
    "deadline": "2026-06-15T12:00:00.000Z",
    "requiresReason": false,
    "notifyRecipient": true
  }
}
400Invalid transaction state

Response follows the unified success / data / meta / error envelope.

404Transaction not found

Response follows the unified success / data / meta / error envelope.

curl -X POST https://api-staging.coinbax.com/api/v2/transactions/<id>/hash \
  -H "Authorization: Bearer $ACCESS_TOKEN" \
  -H "Content-Type: application/json" \
  -d '{
  "transactionHash": "0x1234567890abcdef1234567890abcdef1234567890abcdef1234567890abcdef"
}'
const response = await fetch('https://api-staging.coinbax.com/api/v2/transactions/<id>/hash', {
  method: 'POST',
  headers: {
    'Authorization': `Bearer ${process.env.ACCESS_TOKEN}`,
    'Content-Type': 'application/json',
  },
  body: JSON.stringify({
    "transactionHash": "0x1234567890abcdef1234567890abcdef1234567890abcdef1234567890abcdef"
  }),
});
const result = await response.json();

Refund transaction

POST/api/v2/transactions/{id}/refundBearer token

Returns escrowed funds to the sender. Requires `reverse:transactions` in v2; v1 required `cancel:transactions`, which is not accepted here. `reverse:` does not inherit from the v1 scopes — they are siblings in the hierarchy — so a token minted for v1 must be re-issued.

Path parameters

  • idstringrequired

Request body

  • reasonstringrequired

    Reason for refund

Responses

200Transaction refunded successfully
  • rescindobject

    Public-safe summary of the TimeDelay rescind config. Lets clients gate the Cancel/Rescind affordance without a second round-trip. Null when the transaction has no TimeDelay control (RAW orchestration, templates without a hold period). Absent from response on the list endpoint and on legacy api revisions.

    • TransactionRescindInfo
      • allowedbooleanrequired

        Whether the sender is allowed to rescind/cancel the transaction during the hold window. Defaults to true when not explicitly set on the underlying control config.

      • deadlineobjectrequired

        ISO 8601 timestamp after which rescind is no longer allowed. Null when the rescind window matches the full hold period.

        object

        ISO 8601 timestamp after which rescind is no longer allowed. Null when the rescind window matches the full hold period.

      • requiresReasonbooleanrequired

        Whether the rescind endpoint requires a non-empty `reason` field in the request body.

      • notifyRecipientbooleanrequired

        Whether the recipient is notified (e.g. via SMS) when a rescind succeeds.

{
  "rescind": {
    "allowed": true,
    "deadline": "2026-06-15T12:00:00.000Z",
    "requiresReason": false,
    "notifyRecipient": true
  }
}
400Invalid transaction state for refund

Response follows the unified success / data / meta / error envelope.

403Token lacks `reverse:transactions`

Response follows the unified success / data / meta / error envelope.

404Transaction not found

Response follows the unified success / data / meta / error envelope.

curl -X POST https://api-staging.coinbax.com/api/v2/transactions/<id>/refund \
  -H "Authorization: Bearer $ACCESS_TOKEN" \
  -H "Content-Type: application/json" \
  -d '{
  "reason": "Customer request - item not as described"
}'
const response = await fetch('https://api-staging.coinbax.com/api/v2/transactions/<id>/refund', {
  method: 'POST',
  headers: {
    'Authorization': `Bearer ${process.env.ACCESS_TOKEN}`,
    'Content-Type': 'application/json',
  },
  body: JSON.stringify({
    "reason": "Customer request - item not as described"
  }),
});
const result = await response.json();

Release escrowed funds to the recipient (Coinbax broadcasts)

POST/api/v2/transactions/{id}/releaseBearer token

Coinbax submits the on-chain release for you. Absorbs v1 `complete` — same intent, different signer; the platform picks the on-chain path from which wallet signs and whether the hold window has elapsed, so you never name a contract function. Call `POST /transactions/{id}/release/attest` instead if you want to broadcast yourself. **Preconditions** — each mirrors an on-chain revert, so a failure here is the chain refusing, not a Coinbax policy: - the escrow is still active (a released, refunded or rejected escrow reverts `EscrowNotActive` — one revert covers all three, so check `GET /transactions/{id}` rather than inferring the cause) - the hold window has elapsed, i.e. `[releaseTime, ∞)`. Release opens **at** `releaseTime`; before that the chain reverts `ReleaseTimeNotReached`. Use `release-early` to waive the remainder. - every required release control has passed. A failed required control auto-rejects the escrow and refunds the sender on-chain — do not then attempt a release.

Path parameters

  • idstringrequired

Request body

  • notestring

    Optional completion note

Responses

200Released. Returns the full transaction, including its terminal status and the broadcast hash.
  • rescindobject

    Public-safe summary of the TimeDelay rescind config. Lets clients gate the Cancel/Rescind affordance without a second round-trip. Null when the transaction has no TimeDelay control (RAW orchestration, templates without a hold period). Absent from response on the list endpoint and on legacy api revisions.

    • TransactionRescindInfo
      • allowedbooleanrequired

        Whether the sender is allowed to rescind/cancel the transaction during the hold window. Defaults to true when not explicitly set on the underlying control config.

      • deadlineobjectrequired

        ISO 8601 timestamp after which rescind is no longer allowed. Null when the rescind window matches the full hold period.

        object

        ISO 8601 timestamp after which rescind is no longer allowed. Null when the rescind window matches the full hold period.

      • requiresReasonbooleanrequired

        Whether the rescind endpoint requires a non-empty `reason` field in the request body.

      • notifyRecipientbooleanrequired

        Whether the recipient is notified (e.g. via SMS) when a rescind succeeds.

{
  "rescind": {
    "allowed": true,
    "deadline": "2026-06-15T12:00:00.000Z",
    "requiresReason": false,
    "notifyRecipient": true
  }
}
400Not an eligible escrow, wrong status, no on-chain escrow id, or still inside the hold window.

Response follows the unified success / data / meta / error envelope.

404Transaction not found

Response follows the unified success / data / meta / error envelope.

curl -X POST https://api-staging.coinbax.com/api/v2/transactions/<id>/release \
  -H "Authorization: Bearer $ACCESS_TOKEN" \
  -H "Content-Type: application/json" \
  -d '{
  "note": "Service delivered successfully"
}'
const response = await fetch('https://api-staging.coinbax.com/api/v2/transactions/<id>/release', {
  method: 'POST',
  headers: {
    'Authorization': `Bearer ${process.env.ACCESS_TOKEN}`,
    'Content-Type': 'application/json',
  },
  body: JSON.stringify({
    "note": "Service delivered successfully"
  }),
});
const result = await response.json();

Release early with a sender signature (Coinbax broadcasts)

POST/api/v2/transactions/{id}/release-earlyBearer token

Submits a sender-signed early release through the Coinbax relayer, which pays the gas. Get the payload from `POST /transactions/{id}/release-early/attest`, have the sender wallet sign it, then post the signature here. The signature is submitted **split** as `deadline`, `v`, `r`, `s` — not as a packed 65-byte hex string. Most wallet libraries return the packed form, so split it before calling. **Preconditions** — each mirrors an on-chain revert: - the escrow is still active (`EscrowNotActive`) - the signature has not expired: valid while `block.timestamp <= deadline`, so equal is still valid (`ReleaseAuthExpired`) - the recovered signer is the escrow sender (`OnlySenderCanRelease`) - the workspace escrow has a relayer configured; without one the chain reverts `NotAuthorizedRelayer` and no funds move Releasing early is final and forfeits the remaining hold window.

Path parameters

  • idstringrequired

Request body

  • deadlinestringrequired

    Unix-seconds after which the authorization is expired.

  • vnumberrequired

    y-parity of the signature (27 or 28).

  • rstringrequired

    r component of the signature.

  • sstringrequired

    s component of the signature.

Responses

200Released early. Returns the full transaction.
  • rescindobject

    Public-safe summary of the TimeDelay rescind config. Lets clients gate the Cancel/Rescind affordance without a second round-trip. Null when the transaction has no TimeDelay control (RAW orchestration, templates without a hold period). Absent from response on the list endpoint and on legacy api revisions.

    • TransactionRescindInfo
      • allowedbooleanrequired

        Whether the sender is allowed to rescind/cancel the transaction during the hold window. Defaults to true when not explicitly set on the underlying control config.

      • deadlineobjectrequired

        ISO 8601 timestamp after which rescind is no longer allowed. Null when the rescind window matches the full hold period.

        object

        ISO 8601 timestamp after which rescind is no longer allowed. Null when the rescind window matches the full hold period.

      • requiresReasonbooleanrequired

        Whether the rescind endpoint requires a non-empty `reason` field in the request body.

      • notifyRecipientbooleanrequired

        Whether the recipient is notified (e.g. via SMS) when a rescind succeeds.

{
  "rescind": {
    "allowed": true,
    "deadline": "2026-06-15T12:00:00.000Z",
    "requiresReason": false,
    "notifyRecipient": true
  }
}
400Not an eligible escrow, wrong status, expired or malformed signature, or the signer is not the sender.

Response follows the unified success / data / meta / error envelope.

403Early release is not enabled for this workspace.

Response follows the unified success / data / meta / error envelope.

404Transaction not found

Response follows the unified success / data / meta / error envelope.

422ESCROW_NOT_VERIFIED: the platform could not confirm the escrow supports early release (a transient probe/RPC failure, or a non-WorkspaceEscrow contract). Retry shortly; if it persists, release after the hold window instead.

Response follows the unified success / data / meta / error envelope.

curl -X POST https://api-staging.coinbax.com/api/v2/transactions/<id>/release-early \
  -H "Authorization: Bearer $ACCESS_TOKEN" \
  -H "Content-Type: application/json" \
  -d '{
  "deadline": "1999999999",
  "v": 100.5,
  "r": "string",
  "s": "string"
}'
const response = await fetch('https://api-staging.coinbax.com/api/v2/transactions/<id>/release-early', {
  method: 'POST',
  headers: {
    'Authorization': `Bearer ${process.env.ACCESS_TOKEN}`,
    'Content-Type': 'application/json',
  },
  body: JSON.stringify({
    "deadline": "1999999999",
    "v": 100.5,
    "r": "string",
    "s": "string"
  }),
});
const result = await response.json();

Get the payload for the sender to sign for an early release

POST/api/v2/transactions/{id}/release-early/attestBearer token

Runs the release controls and, only if they pass outright, returns the EIP-712 payload for the sender wallet to sign. No transaction, no gas, no funds moved. Always returns **200** with a `mode`. `signature_required` carries `typedData`; the other three carry none, and none of them is an error condition in itself. **The payload has no nonce, deliberately.** Replay protection is structural, not counter-based — see the response schema. Do not add your own replay guard.

Path parameters

  • idstringrequired

Responses

200`signature_required` with the payload to sign, or `permissionless` / `under_review` / `rejected` when no payload should be emitted.
  • modeobjectrequired

    What to do next. Branch on this rather than on transaction state.

    • ReleaseAttestationMode
      enum"signature_required" · "permissionless" · "under_review" · "rejected"

      What to do next. Branch on this rather than on transaction state.

  • transactionIdstring (uuid)required
  • typedDataobject

    Present only when `mode` is `signature_required`. Sign this with the sender wallet and submit the signature to the matching non-attest route. **There is no nonce field, and that is deliberate.** Replay protection is structural rather than counter-based: `escrowId` is inside the signed struct, `verifyingContract` is this workspace's clone, `chainId` is in the domain, and a successful release clears the escrow's active flag so re-submitting the same signature reverts. Do not build your own replay guard, and do not look for a nonce to increment. `deadline` is the only time bound the signer controls.

    • Eip712ReleasePayloadDto
      • domainEip712DomainDtorequired
        • namestringrequired
        • versionstringrequired
        • chainIdnumberrequired

          Chain the signature is bound to.

        • verifyingContractstringrequired

          The escrow CLONE address for this workspace — not a shared implementation or factory address. Each workspace has its own clone and the contract recomputes its domain separator from its own address, so a signature cannot be replayed against another clone. Do not cache one value across workspaces.

      • typesobjectrequired

        EIP-712 type definitions. Exactly one struct: `ReleaseEarly(bytes32 escrowId,uint256 deadline)`.

        object

        EIP-712 type definitions. Exactly one struct: `ReleaseEarly(bytes32 escrowId,uint256 deadline)`.

      • primaryTypestringrequired
      • messageEip712MessageDtorequired
        • escrowIdstringrequired

          On-chain escrow id (bytes32).

        • deadlinestringrequired

          Expiry as unix seconds, sent as a DECIMAL STRING. `uint256` exceeds Number.MAX_SAFE_INTEGER, so a JSON number would risk precision loss. The signature is valid while `block.timestamp <= deadline` — equal is still valid.

  • consequencestring

    Present with `signature_required` on an early release. Show this to the sender before they sign — the hold window is a protection they are waiving.

  • releasableAtstring

    Present when `mode` is `permissionless`: the hold window elapsed at this time, so the release needs no signature from anyone.

{
  "mode": {},
  "transactionId": "9f8b7c6d-5e4f-4a3b-2c1d-0e9f8a7b6c5d",
  "typedData": {
    "domain": {
      "name": "WorkspaceEscrow",
      "version": "1",
      "chainId": 8453,
      "verifyingContract": "0xd80cd47516B4f592eEAac19bCf424bE30a088819"
    },
    "types": {
      "ReleaseEarly": [
        {
          "name": "escrowId",
          "type": "bytes32"
        },
        {
          "name": "deadline",
          "type": "uint256"
        }
      ]
    },
    "primaryType": "ReleaseEarly",
    "message": {
      "escrowId": "0x9c22ff5f21f0b81b113e63f7db6da94fedef11b2119b4088b89664fb9a3cb658",
      "deadline": "1788284400"
    }
  },
  "consequence": "Releasing now is final; you forfeit the remaining hold window.",
  "releasableAt": "2026-09-04T18:22:00.000Z"
}
400Not an eligible escrow, wrong status, or not active on-chain.

Response follows the unified success / data / meta / error envelope.

403Early release is not enabled for this workspace.

Response follows the unified success / data / meta / error envelope.

404Transaction not found

Response follows the unified success / data / meta / error envelope.

curl -X POST https://api-staging.coinbax.com/api/v2/transactions/<id>/release-early/attest \
  -H "Authorization: Bearer $ACCESS_TOKEN"
const response = await fetch('https://api-staging.coinbax.com/api/v2/transactions/<id>/release-early/attest', {
  method: 'POST',
  headers: {
    'Authorization': `Bearer ${process.env.ACCESS_TOKEN}`,
  },
});
const result = await response.json();

Attest a release so you can broadcast it yourself

POST/api/v2/transactions/{id}/release/attestBearer token

Runs the release controls and records the aggregate result on-chain, so your own release write does not revert on the escrow's control gate. **Never moves funds itself.** Always returns **200** with a `mode` telling you what to do next — including `permissionless`, when the hold window has elapsed and the on-chain release needs no signature from anyone. That is not an error: asking for a payload you do not need is a well-formed question. A 400 here means the request itself was invalid (wrong status, no escrow), never "you did not need a signature".

Path parameters

  • idstringrequired

Responses

200One of four modes: `signature_required` (sign `typedData`), `permissionless` (no signature needed), `under_review` (a control has not resolved), `rejected` (a required control failed; the escrow was refunded on-chain).
  • modeobjectrequired

    What to do next. Branch on this rather than on transaction state.

    • ReleaseAttestationMode
      enum"signature_required" · "permissionless" · "under_review" · "rejected"

      What to do next. Branch on this rather than on transaction state.

  • transactionIdstring (uuid)required
  • typedDataobject

    Present only when `mode` is `signature_required`. Sign this with the sender wallet and submit the signature to the matching non-attest route. **There is no nonce field, and that is deliberate.** Replay protection is structural rather than counter-based: `escrowId` is inside the signed struct, `verifyingContract` is this workspace's clone, `chainId` is in the domain, and a successful release clears the escrow's active flag so re-submitting the same signature reverts. Do not build your own replay guard, and do not look for a nonce to increment. `deadline` is the only time bound the signer controls.

    • Eip712ReleasePayloadDto
      • domainEip712DomainDtorequired
        • namestringrequired
        • versionstringrequired
        • chainIdnumberrequired

          Chain the signature is bound to.

        • verifyingContractstringrequired

          The escrow CLONE address for this workspace — not a shared implementation or factory address. Each workspace has its own clone and the contract recomputes its domain separator from its own address, so a signature cannot be replayed against another clone. Do not cache one value across workspaces.

      • typesobjectrequired

        EIP-712 type definitions. Exactly one struct: `ReleaseEarly(bytes32 escrowId,uint256 deadline)`.

        object

        EIP-712 type definitions. Exactly one struct: `ReleaseEarly(bytes32 escrowId,uint256 deadline)`.

      • primaryTypestringrequired
      • messageEip712MessageDtorequired
        • escrowIdstringrequired

          On-chain escrow id (bytes32).

        • deadlinestringrequired

          Expiry as unix seconds, sent as a DECIMAL STRING. `uint256` exceeds Number.MAX_SAFE_INTEGER, so a JSON number would risk precision loss. The signature is valid while `block.timestamp <= deadline` — equal is still valid.

  • consequencestring

    Present with `signature_required` on an early release. Show this to the sender before they sign — the hold window is a protection they are waiving.

  • releasableAtstring

    Present when `mode` is `permissionless`: the hold window elapsed at this time, so the release needs no signature from anyone.

{
  "mode": {},
  "transactionId": "9f8b7c6d-5e4f-4a3b-2c1d-0e9f8a7b6c5d",
  "typedData": {
    "domain": {
      "name": "WorkspaceEscrow",
      "version": "1",
      "chainId": 8453,
      "verifyingContract": "0xd80cd47516B4f592eEAac19bCf424bE30a088819"
    },
    "types": {
      "ReleaseEarly": [
        {
          "name": "escrowId",
          "type": "bytes32"
        },
        {
          "name": "deadline",
          "type": "uint256"
        }
      ]
    },
    "primaryType": "ReleaseEarly",
    "message": {
      "escrowId": "0x9c22ff5f21f0b81b113e63f7db6da94fedef11b2119b4088b89664fb9a3cb658",
      "deadline": "1788284400"
    }
  },
  "consequence": "Releasing now is final; you forfeit the remaining hold window.",
  "releasableAt": "2026-09-04T18:22:00.000Z"
}
400Not an eligible escrow, wrong status, or no on-chain escrow.

Response follows the unified success / data / meta / error envelope.

404Transaction not found

Response follows the unified success / data / meta / error envelope.

422RELEASE_WINDOW_NOT_ELAPSED: the escrow is still inside its hold window, so releasing now requires the sender to waive the remainder. Use `release-early/attest` to get the payload for them to sign, or wait for the window to elapse.

Response follows the unified success / data / meta / error envelope.

curl -X POST https://api-staging.coinbax.com/api/v2/transactions/<id>/release/attest \
  -H "Authorization: Bearer $ACCESS_TOKEN"
const response = await fetch('https://api-staging.coinbax.com/api/v2/transactions/<id>/release/attest', {
  method: 'POST',
  headers: {
    'Authorization': `Bearer ${process.env.ACCESS_TOKEN}`,
  },
});
const result = await response.json();

Rescind transaction during hold period (sender only)

POST/api/v2/transactions/{id}/rescindBearer token

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. Requires `reverse:transactions` in v2; v1 required `rescind:transactions`.

Path parameters

  • idstringrequired

Request body

  • reasonstring

    Reason for rescinding the transaction (required if control config has rescindRequiresReason: true)

  • senderWalletstringrequired

    Sender wallet address for authorization verification

Responses

200Transaction rescinded successfully, funds returned to sender
  • rescindobject

    Public-safe summary of the TimeDelay rescind config. Lets clients gate the Cancel/Rescind affordance without a second round-trip. Null when the transaction has no TimeDelay control (RAW orchestration, templates without a hold period). Absent from response on the list endpoint and on legacy api revisions.

    • TransactionRescindInfo
      • allowedbooleanrequired

        Whether the sender is allowed to rescind/cancel the transaction during the hold window. Defaults to true when not explicitly set on the underlying control config.

      • deadlineobjectrequired

        ISO 8601 timestamp after which rescind is no longer allowed. Null when the rescind window matches the full hold period.

        object

        ISO 8601 timestamp after which rescind is no longer allowed. Null when the rescind window matches the full hold period.

      • requiresReasonbooleanrequired

        Whether the rescind endpoint requires a non-empty `reason` field in the request body.

      • notifyRecipientbooleanrequired

        Whether the recipient is notified (e.g. via SMS) when a rescind succeeds.

{
  "rescind": {
    "allowed": true,
    "deadline": "2026-06-15T12:00:00.000Z",
    "requiresReason": false,
    "notifyRecipient": true
  }
}
400Transaction cannot be rescinded (not in ESCROWED status, rescind not enabled, or rescind window expired)

Response follows the unified success / data / meta / error envelope.

403Only the sender can rescind a transaction, or the token lacks `reverse:transactions`

Response follows the unified success / data / meta / error envelope.

404Transaction not found

Response follows the unified success / data / meta / error envelope.

curl -X POST https://api-staging.coinbax.com/api/v2/transactions/<id>/rescind \
  -H "Authorization: Bearer $ACCESS_TOKEN" \
  -H "Content-Type: application/json" \
  -d '{
  "reason": "Changed my mind about the purchase",
  "senderWallet": "0x1234567890abcdef1234567890abcdef12345678"
}'
const response = await fetch('https://api-staging.coinbax.com/api/v2/transactions/<id>/rescind', {
  method: 'POST',
  headers: {
    'Authorization': `Bearer ${process.env.ACCESS_TOKEN}`,
    'Content-Type': 'application/json',
  },
  body: JSON.stringify({
    "reason": "Changed my mind about the purchase",
    "senderWallet": "0x1234567890abcdef1234567890abcdef12345678"
  }),
});
const result = await response.json();