Inyo

Transaction

The Transaction endpoint is the culmination of the remittance flow. It links together the sender, recipient, funding source, recipient account, and quote to execute a cross-border payment.


Creating a Transaction

Endpoint: POST /organizations/{tenant}/fx/transactions
Authentication: Agent-level (x-api-key + x-agent-id + x-agent-api-key)

Request Body

FieldTypeRequiredDescription
senderIdstring (UUID)YesThe participant ID of the sender (from POST /people)
recipientIdstring (UUID)YesThe participant ID of the recipient (from POST /people)
fundingAccountIdstring (UUID)YesThe sender's funding source (from POST /fundingAccounts)
recipientAccountIdstring (UUID)YesThe recipient's payout account (from POST /recipientAccounts/gateway)
quoteIdstring (UUID)YesThe locked FX quote (from POST /payout/quotes)
externalIdstringNoYour internal reference ID for reconciliation. Returned in webhooks as externalTransactionId.
deviceDataobjectConditionalDevice intelligence for fraud prevention (see below)
deviceData.userIpAddressstringConditionalThe end user's IP address. Required when fingerprinting is enabled for your tenant (the default).
deviceData.fingerprintstringConditionalThe requestId from the device fingerprint library. Required when fingerprinting is enabled (the default).
deviceData.visitorstringRecommendedThe visitorId from the device fingerprint library
additionalDataobjectNoLegacy location for fingerprint and visitor (both accepted), plus country-specific fields per the transaction schema
sendingReasonstringNoPurpose of the transfer (e.g., "Family support", "Payment for services")
senderReceiverRelationshipstringNoRelationship between sender and recipient (e.g., "Family member", "Business partner")
sourceOfFundsstringNoOrigin of funds (e.g., "Salary", "Savings", "Business income")

Device Fingerprint

Most tenants have device fingerprinting enabled (it's the platform default). When enabled, every transaction must include a fingerprint and the user's IP address, or the request is rejected. Integrate a device fingerprint library into your client application β€” Inyo provides the API keys for the fingerprint service during onboarding.

Before submitting a transaction, call the fingerprint library on the client side and pass the results:

  • deviceData.fingerprint β€” the requestId from the fingerprint library
  • deviceData.visitor β€” the visitorId from the fingerprint library
  • deviceData.userIpAddress β€” the end user's IP address

Send BOTH fingerprint and visitor. The payment gateway's server-side device-intelligence integration only runs when both values are present. Sending only one silently drops the device-intelligence block from the gateway call, which reduces fraud-scoring accuracy and can contribute to declines.

fingerprint and visitor are also accepted inside additionalData (legacy format). deviceData is the preferred location.

Sample Request

curl --request POST \
  --url https://{FQDN}/organizations/$TENANT/fx/transactions \
  --header 'Content-Type: application/json' \
  --header "x-api-key: $API_KEY" \
  --header "x-agent-id: $AGENT_ID" \
  --header "x-agent-api-key: $AGENT_KEY" \
  --data '{
  "externalId": "your-internal-reference-123",
  "senderId": "422a55b2-78c3-4509-8654-16e7375d3e40",
  "recipientId": "4b9dcb4e-2e2a-4e96-b2a9-fcd868b2f4ed",
  "fundingAccountId": "3fe5f23f-65d3-41b6-b34c-33db3c4d8ef9",
  "recipientAccountId": "67203d69-dc48-41d4-b2ac-52682d39b032",
  "quoteId": "30bf05d2-b416-4e71-b8e8-1ef158a2b414",
  "deviceData": {
    "userIpAddress": "200.123.131.112",
    "fingerprint": "1727364523875.mchLfB",
    "visitor": "jkS9xp4FHlqOSfGR"
  },
  "sendingReason": "Family support",
  "senderReceiverRelationship": "Family member",
  "sourceOfFunds": "Salary"
}'

Sample Response (HTTP 202)

{
  "id": "9035ee18-052b-4176-a940-ccfe599a1829",
  "sender": {
    "id": "ce06773d-6472-4c93-bdf5-8eb68ab01c7f",
    "type": "PERSON",
    "name": "Rob Dalas",
    "phoneNumber": "+1 123 456435"
  },
  "recipient": {
    "id": "156675d5-1d1b-4b4d-997e-5c15ae129b82",
    "type": "PERSON",
    "name": "Bob Danilo",
    "phoneNumber": "+55 71 91234-5678"
  },
  "tenantId": "master",
  "externalId": "your-internal-reference-123",
  "agentId": "41f55211-1eea-4747-9a03-68e99f07ede5",
  "createdAt": "2025-05-29T17:26:27.264312",
  "anchorCurrencyAmount": { "amount": "102.53", "currency": "USD" },
  "complianceStatus": "Pending",
  "payoutStatus": "Pending",
  "paymentStatus": null,
  "quoteId": "af2d55d8-1d40-426a-8d4a-7454ccdcbe8a",
  "fundingAccountId": "61bc14a1-b3a2-40b4-b395-0496e4f554a1",
  "recipientAccountId": "4fbc57a6-f673-44ba-88aa-c6e71eb9181b",
  "exchangeRate": "5.571062480000000",
  "totalAmount": { "amount": "102.53", "currency": "USD" },
  "receivingAmount": { "amount": "571.20", "currency": "BRL" },
  "fee": { "amount": "1.54", "currency": "USD" },
  "conversionAmount": { "amount": "102.53", "currency": "USD" },
  "type": "FX",
  "senderId": "ce06773d-6472-4c93-bdf5-8eb68ab01c7f",
  "recipientId": "156675d5-1d1b-4b4d-997e-5c15ae129b82",
  "sendingReason": "Family support",
  "receipt": { "..." : "regulatory disclosures" }
}

Response Fields

FieldDescription
idUnique transaction ID
complianceStatusCurrent compliance screening result (see lifecycle below)
payoutStatusCurrent payout delivery status (see lifecycle below)
paymentStatusActionRequired when the card requires a 3DS challenge, otherwise null
redirectAcsUrl3DS challenge URL β€” present only when paymentStatus is ActionRequired
status.messagesAudit trail of status transitions with timestamps
exchangeRateThe applied FX rate
totalAmountTotal amount charged to the sender (in source currency)
receivingAmountAmount to be received by the beneficiary (in destination currency)
feeTransaction fee in source currency
conversionAmountAmount that was converted (source currency)
receiptRegulatory disclosures that must be shown to the sender (see below)
typeTransaction type β€” FX

Validation Errors

Validation failures return HTTP 400 with a per-field breakdown:

{
  "errors": [
    { "fieldName": "fingerprint", "error": "A fingerprint is required (deviceData.fingerprint or additionalData.fingerprint)." },
    { "fieldName": "deviceData.userIpAddress", "error": "The device IP address is required." }
  ]
}

Handling 3D Secure (3DS)

When the sender's card requires 3DS authentication, the create response includes:

{
  "paymentStatus": "ActionRequired",
  "redirectAcsUrl": "https://..."
}

Flow:

  1. Open redirectAcsUrl in a WebView or popup.
  2. The user completes 3DS verification on the issuer's page.
  3. Listen for the postMessage event from the payment interface to determine whether the challenge succeeded.
  4. If successful: close the WebView and poll GET /fx/transactions/{id} β€” the transaction resumes automatically (payout integration, compliance review, etc.). paymentStatus returns to null once the challenge resolves.
  5. If failed: close the WebView and show an error β€” the transaction moves to PaymentDeclined β†’ Cancelled.

If the card is not challenged, the transaction proceeds immediately and redirectAcsUrl is absent. While the challenge is pending, the transaction sits in the WaitingChallenge3ds state.

Never skip the 3DS challenge. A transaction stuck in WaitingChallenge3ds will not progress until the challenge completes or the transaction is cancelled.


Transaction Status Lifecycle

Every transaction exposes two summary status dimensions on GET /fx/transactions/{id}, plus a fine-grained internal status delivered via webhooks.

Compliance Status

StatusDescription
PendingScreening not yet complete
ApprovedPassed compliance checks β€” transaction can proceed
RejectedFailed compliance review β€” transaction is blocked
CancelledTransaction was cancelled before completion
RefundedTransaction was refunded after payment capture
FailedPayment declined or an unrecoverable error occurred

Payout Status

StatusDescription
PendingTransaction accepted, payment leg still in progress
ProcessingHanded to the payout network (MSB), awaiting delivery
CompletedFunds delivered to the recipient
CancelledTransaction was cancelled
FailedPayout rejected by the network, payment declined, or error
VoidedPayout voided by the payout network
RefundedTransaction was refunded

Detailed Status Flows

The fine-grained statuses below are delivered in TransactionStatusChanged webhooks and in the status history endpoint.

Card (with 3DS):

Created β†’ PaymentProcessing β†’ WaitingChallenge3ds
  β†’ [user completes 3DS] β†’ PaymentAuthorized
  β†’ ProcessingPayout β†’ PayoutAccepted β†’ ManualReview β†’ ReviewApproved
  β†’ PaymentCaptured β†’ WaitingPayout
  β†’ [payout network confirms delivery] β†’ Paid β†’ Completed

Card (no 3DS):

Created β†’ PaymentProcessing β†’ PaymentAuthorized
  β†’ ProcessingPayout β†’ PayoutAccepted β†’ ManualReview β†’ ReviewApproved
  β†’ PaymentCaptured β†’ WaitingPayout
  β†’ [payout network confirms delivery] β†’ Paid β†’ Completed

ACH:

Created β†’ PaymentProcessing β†’ PaymentAuthorized
  β†’ ProcessingPayout β†’ PayoutAccepted β†’ WaitingSettlement
  β†’ [ACH settles] β†’ PaymentSettled β†’ PaymentCaptured β†’ WaitingPayout
  β†’ [payout network confirms delivery] β†’ Paid β†’ Completed

Exception branches:

  • PayoutHold β€” the payout network placed a compliance hold; released automatically or by the compliance team (PayoutHold β†’ PayoutReleased β†’ flow resumes)
  • PayoutRejected β€” the payout network rejected the transaction; it is cancelled and the payment reversed
  • ManualReview β†’ ReviewRejected β€” the compliance team rejected the payment; it is voided
  • BlockedPendingReview β€” a risk rule or blocklist match paused the transaction for operator review before proceeding
  • PendingReversalApproval β€” the payout was voided downstream and the reversal is awaiting operator approval (tenant-configurable)
  • CancelRequested β€” you requested cancellation; the reversal is being processed

Depending on your tenant configuration, ManualReview may be auto-approved (all transactions, card-only, or ACH-only), in which case you'll see ManualReview β†’ ReviewApproved back-to-back.


Querying Transactions

Get Transaction by ID

curl --request GET \
  --url https://{FQDN}/organizations/$TENANT/fx/transactions/$TRANSACTION_ID \
  --header "x-api-key: $API_KEY" \
  --header "x-agent-id: $AGENT_ID" \
  --header "x-agent-api-key: $AGENT_KEY"

List Transactions

curl --request GET \
  --url "https://{FQDN}/organizations/$TENANT/fx/transactions?page=0&size=20&senderId=$SENDER_ID" \
  --header "x-api-key: $API_KEY" \
  --header "x-agent-id: $AGENT_ID" \
  --header "x-agent-api-key: $AGENT_KEY"

Query Parameters:

ParameterTypeDescription
pageintegerPage number (0-indexed), default 0
sizeintegerResults per page, default 20
senderIdstring (UUID)Filter by sender participant
statusstringFilter by compliance status (e.g., APPROVED)

Response: { "total": <count>, "transactions": [ ... ] }, most recent first.

Get Transaction Status History

Returns the full audit trail of status changes for a transaction, most recent first.

curl --request GET \
  --url https://{FQDN}/organizations/$TENANT/fx/transactions/$TRANSACTION_ID/status \
  --header "x-api-key: $API_KEY" \
  --header "x-agent-id: $AGENT_ID" \
  --header "x-agent-api-key: $AGENT_KEY"

Sample Response:

[
  {
    "id": "2f6b3f12-a63f-43f7-9bb4-4e98dfc75fe5",
    "complianceStatus": "Approved",
    "payoutStatus": "Pending",
    "transactionId": "46b4c393-3768-4ba4-b6d2-a6398e17dd78",
    "tenantId": "master",
    "externalId": "GMT000648512199",
    "createdAt": "2024-01-03T12:54:09.455",
    "messages": [
      {
        "id": "0fbbddb8-283d-466d-b2e6-42dcbb3e67de",
        "message": "STANDARD - PAYMENT_RECEIVED",
        "createdBy": "system",
        "createdAt": "2025-11-19T13:29:52.221882",
        "type": "SystemUpdate"
      }
    ]
  }
]

Cancelling / Reversing a Transaction

There is a single reversal endpoint. Inyo determines the correct reversal operation based on how far the transaction progressed:

  • Payment only pre-authorized β†’ the authorization is voided
  • Payment already captured (but funds not yet delivered) β†’ the payment is refunded

Endpoint: PUT /organizations/{tenant}/fx/transactions/{transactionId}/status/cancel
Authentication: Agent-level

curl --request PUT \
  --url https://{FQDN}/organizations/$TENANT/fx/transactions/$TRANSACTION_ID/status/cancel \
  --header "x-api-key: $API_KEY" \
  --header "x-agent-id: $AGENT_ID" \
  --header "x-agent-api-key: $AGENT_KEY"

The reversal is processed asynchronously: the transaction moves to CancelRequested, then to Cancelled (voided) or Refunded once the gateway confirms.

Responses:

StatusDescription
204Reversal accepted and queued
404Transaction not found
422Transaction is not in a reversible state

Once the payout network has delivered funds to the recipient (Paid / Completed), the transaction can no longer be reversed through the API β€” contact Inyo support.


Updating Transaction Metadata

Attach arbitrary key-value data to a transaction for your internal tracking. Returns 204.

Endpoint: PUT /organizations/{tenant}/fx/transactions/{transactionId}/metadata
Authentication: Agent-level

curl --request PUT \
  --url https://{FQDN}/organizations/$TENANT/fx/transactions/$TRANSACTION_ID/metadata \
  --header 'Content-Type: application/json' \
  --header "x-api-key: $API_KEY" \
  --header "x-agent-id: $AGENT_ID" \
  --header "x-agent-api-key: $AGENT_KEY" \
  --data '{
  "internalRef": "INV-2025-001",
  "department": "treasury"
}'

Batch Transactions

For high-volume use cases you can submit multiple transactions in one call. Each item uses the same shape as the single-create request.

OperationMethodEndpoint
Submit batchPOST/organizations/{tenant}/fx/transactions/batch
Retry a failed batch itemPOST/organizations/{tenant}/fx/transactions/batch/{transactionId}/retry
{ "transactions": [ { "senderId": "...", "recipientId": "...", "...": "..." } ] }

Both endpoints return 202 Accepted; individual transactions progress independently β€” track them via webhooks or polling.


Transaction Limits

Before executing a transaction, verify the sender has not exceeded their limits. The numbers reported here are computed with the same rules the transaction validator applies, so what shows as available is what a transaction can actually use.

Endpoint: GET /organizations/{tenant}/fx/participants/{participantId}/limits
Authentication: Agent-level

curl --request GET \
  --url https://{FQDN}/organizations/$TENANT/fx/participants/$SENDER_ID/limits \
  --header "x-api-key: $API_KEY" \
  --header "x-agent-id: $AGENT_ID" \
  --header "x-agent-api-key: $AGENT_KEY"

Sample Response:

{
  "oneDayLimit": {
    "limit": { "amount": "2999.00", "currency": "USD" },
    "used": { "amount": "0.00", "currency": "USD" },
    "available": { "amount": "2999.00", "currency": "USD" }
  },
  "thirtyDaysLimit": {
    "limit": { "amount": "6000.00", "currency": "USD" },
    "used": { "amount": "2959.99", "currency": "USD" },
    "available": { "amount": "3040.01", "currency": "USD" }
  },
  "oneHundredAndEightyDaysLimit": {
    "limit": { "amount": "9999.00", "currency": "USD" },
    "used": { "amount": "5000.00", "currency": "USD" },
    "available": { "amount": "4999.00", "currency": "USD" }
  }
}

To see which fields are needed to reach the next compliance level (and unlock higher limits), use GET /participants/{id}/complianceLevels β€” see Trust Level Limits.


Receipts (Regulatory Requirement)

As an agent of a licensed Money Service Business, you are legally required to issue a receipt to the sender immediately after transaction submission. The transaction response includes a receipt object with mandatory regulatory disclosures that must be displayed verbatim to the end user.

The receipt must include:

  1. Exchange rate β€” the locked-in rate from the quote
  2. Fees β€” total fees charged to the customer
  3. Total amount β€” full amount paid by the sender
  4. Receive amount β€” exact amount to be received by the beneficiary
  5. Regulatory disclosures β€” dynamic legal text specific to the corridor (e.g., right to refund, cancellation policy)

You must not alter the financial data or regulatory text returned by the API. Display it as-is.


Sandbox: Forcing Status Transitions

In sandbox/staging you can simulate external events (gateway callbacks, payout notifications) to drive a transaction through its lifecycle without real money movement. See Sandbox Testing.


Error Responses

HTTP StatusCause
400Missing required fields, invalid data format, missing fingerprint/IP, or expired quote
403Agent not approved, or sender doesn't meet compliance requirements
404Referenced transaction/participant/account not found in your tenant
422Business rule violation (e.g., limit exceeded, invalid corridor, non-reversible state)

All Endpoints

OperationMethodEndpoint
Create transactionPOST/organizations/{tenant}/fx/transactions
Get transactionGET/organizations/{tenant}/fx/transactions/{transactionId}
List transactionsGET/organizations/{tenant}/fx/transactions
Get status historyGET/organizations/{tenant}/fx/transactions/{transactionId}/status
Cancel / reverse transactionPUT/organizations/{tenant}/fx/transactions/{transactionId}/status/cancel
Update metadataPUT/organizations/{tenant}/fx/transactions/{transactionId}/metadata
Submit batchPOST/organizations/{tenant}/fx/transactions/batch
Retry batch itemPOST/organizations/{tenant}/fx/transactions/batch/{transactionId}/retry
Check limitsGET/organizations/{tenant}/fx/participants/{participantId}/limits
Get required/optional fieldsGET/organizations/{tenant}/fx/transactions/schema?countryCode={iso2}
Force status change (sandbox)POST/organizations/{tenant}/fx/transactions/{transactionId}/events