Getting Started
This guide walks you through sending your first cross-border transaction on the Inyo sandbox. By the end, you'll have a working remittance from a US sender to an international recipient.
Prerequisites
Before you begin, ensure you have:
- Sandbox credentials provided by Inyo during onboarding:
tenantβ Your organization identifierx-api-keyβ Primary API key (Tenant-level)x-agent-idβ Your agent UUIDx-agent-api-keyβ Agent-level API key
- Network access configured β a mutual TLS client certificate and an allowlisted egress IP. See Connectivity; without both, requests fail before reaching the API.
- Node.js v18+ (if running the sample project)
- A REST client (cURL, Postman, or similar)
Don't have credentials yet? Get in touch with our sales team to request sandbox access.
Sandbox Environment
| Resource | URL |
|---|---|
| Core API (sandbox) | https://{FQDN} |
| Developer portal | https://dev.inyoglobal.com |
Quick Start: Your First Transaction
This walkthrough covers the minimum steps to execute a USD β BRL remittance in the sandbox.
Step 1: Create the Sender
Register the person who will send money. This triggers KYC/AML screening.
curl --request POST \
--url https://{FQDN}/organizations/$TENANT/people \
--header 'Content-Type: application/json' \
--header "x-api-key: $API_KEY" \
--data '{
"firstName": "John",
"lastName": "Doe",
"email": "[email protected]",
"birthDate": "1990-01-15",
"phoneNumber": "+15551234567",
"gender": "Male",
"address": {
"countryCode": "US",
"stateCode": "CA",
"city": "San Francisco",
"line1": "123 Market St",
"zipcode": "94105"
},
"documents": [
{
"type": "SSN",
"document": "123456789",
"countryCode": "US"
}
]
}'
Save the returned
idβ this is thesenderIdused throughout the transaction lifecycle.
Step 2: Verify Compliance Level
Confirm the sender has reached at least Level 1 before proceeding.
curl --request GET \
--url https://{FQDN}/organizations/$TENANT/participants/$SENDER_ID/complianceLevels \
--header "x-api-key: $API_KEY"
If currentComplianceLevel.level is LEVEL_0, the sender is missing required fields. Check the missingFieldsForNextComplianceLevel array to see what's needed.
Step 3: Get Available Destinations
curl --request GET \
--url https://{FQDN}/organizations/$TENANT/payout/us/destinations \
--header "x-agent-id: $AGENT_ID" \
--header "x-agent-api-key: $AGENT_KEY"
This returns the list of countries your tenant is enabled to send to, along with their currencies.
Step 4: Lock in an FX Quote
curl --request POST \
--url https://{FQDN}/organizations/$TENANT/payout/quotes \
--header 'Content-Type: application/json' \
--header "x-agent-id: $AGENT_ID" \
--header "x-agent-api-key: $AGENT_KEY" \
--data '{
"fromCurrency": "USD",
"toCurrency": "BRL",
"amount": 100.00
}'
Save the
quoteIdfrom the response and check itsexpireAt. Quotes are valid for 30 minutes by default (configurable per tenant).
Step 5: Create the Recipient
First, fetch the required schema for the destination country:
curl --request GET \
--url https://{FQDN}/organizations/$TENANT/payout/recipients/schema/br \
--header "x-agent-id: $AGENT_ID" \
--header "x-agent-api-key: $AGENT_KEY"
Then create the recipient using the schema-required fields:
curl --request POST \
--url https://{FQDN}/organizations/$TENANT/people \
--header 'Content-Type: application/json' \
--header "x-api-key: $API_KEY" \
--data '{
"firstName": "Maria",
"lastName": "Silva",
"phoneNumber": "+5511999998888",
"documents": [
{
"type": "CPF",
"document": "12345678901",
"countryCode": "BR"
}
]
}'
Save the returned
idβ this is therecipientId.
Step 6: Link the Recipient's Bank Account
Fetch the account schema, then link the bank account:
curl --request POST \
--url https://{FQDN}/organizations/$TENANT/payout/participants/$RECIPIENT_ID/recipientAccounts/gateway \
--header 'Content-Type: application/json' \
--header "x-agent-id: $AGENT_ID" \
--header "x-agent-api-key: $AGENT_KEY" \
--data '{
"externalId": "recipient-acct-001",
"asset": "BRL",
"payoutMethod": {
"type": "BANK_DEPOSIT",
"countryCode": "BR",
"bankCode": "001",
"routingNumber": "0001",
"accountNumber": "123456",
"accountType": "CHECKING"
}
}'
Save the returned
idβ this is therecipientAccountId.
Step 7: Register the Sender's Funding Source
You can fund transactions with either a debit card or an ACH bank account (via Plaid).
Option A: Debit Card
curl --request POST \
--url https://{FQDN}/organizations/$TENANT/payout/participants/$SENDER_ID/fundingAccounts \
--header 'Content-Type: application/json' \
--header "x-agent-id: $AGENT_ID" \
--header "x-agent-api-key: $AGENT_KEY" \
--data '{
"externalId": "funding-001",
"asset": "USD",
"nickname": "My Debit Card",
"paymentMethod": {
"type": "CARD",
"ipAddress": "127.0.0.1",
"token": "tok_sandbox_test_token",
"bin": "541333",
"schemeId": "MASTERCARD",
"lastFourDigits": "3303",
"billingAddress": {
"countryCode": "US",
"stateCode": "CA",
"city": "San Francisco",
"line1": "123 Market St",
"zipcode": "94105"
}
}
}'
Option B: ACH (via Plaid)
First, create a Plaid link token, then complete the Plaid Link flow on the client to get the accountCheckId and accountCheckToken. See Sender Funding Account for the full ACH flow.
curl --request POST \
--url https://{FQDN}/organizations/$TENANT/payout/participants/$SENDER_ID/fundingAccounts \
--header 'Content-Type: application/json' \
--header "x-agent-id: $AGENT_ID" \
--header "x-agent-api-key: $AGENT_KEY" \
--data '{
"externalId": "funding-002",
"asset": "USD",
"nickname": "My Checking Account",
"paymentMethod": {
"type": "ACH",
"countryCode": "US",
"accountCheckId": "PLAID_ACCOUNT_ID",
"accountCheckToken": "PLAID_PUBLIC_TOKEN"
}
}'
Save the returned
idβ this is thefundingAccountId.
Step 8: Execute the Transaction
Link all the IDs together and submit:
curl --request POST \
--url https://{FQDN}/organizations/$TENANT/fx/transactions \
--header 'Content-Type: application/json' \
--header "x-agent-id: $AGENT_ID" \
--header "x-agent-api-key: $AGENT_KEY" \
--data '{
"senderId": "'$SENDER_ID'",
"recipientId": "'$RECIPIENT_ID'",
"fundingAccountId": "'$FUNDING_ACCOUNT_ID'",
"recipientAccountId": "'$RECIPIENT_ACCOUNT_ID'",
"quoteId": "'$QUOTE_ID'",
"deviceData": {
"userIpAddress": "127.0.0.1",
"fingerprint": "<fingerprint-requestId>",
"visitor": "<fingerprint-visitorId>"
}
}'
Most tenants have device fingerprinting enabled (the default) β
deviceData.fingerprintanddeviceData.userIpAddressare then required, and you should always sendvisitoralongside the fingerprint. See Transaction β Device Fingerprint.
A successful response returns HTTP 202 with the full transaction object, including complianceStatus, payoutStatus, exchange rate details, and the receipt object containing legally required disclosures. If the card requires 3D Secure, the response also carries paymentStatus: "ActionRequired" and a redirectAcsUrl β see Handling 3D Secure.
What's Next?
- Set up webhooks to receive real-time transaction status updates
- Review the transaction lifecycle to understand status transitions
- Explore compliance levels to build progressive KYC flows
- Use sandbox testing to force status transitions and simulate compliance scenarios
Common Issues
| Symptom | Cause | Fix |
|---|---|---|
| TLS handshake failure | Missing/expired mTLS client certificate | See Connectivity |
403 with empty/HTML body | Egress IP not on the allowlist | See Connectivity |
401 Unauthorized | Invalid API keys | Verify x-api-key, x-agent-id, and x-agent-api-key in your .env |
403 Forbidden | Agent not approved, or sender below compliance Level 1 | Check agent approval status; verify sender's compliance level |
400 Missing fields | Incomplete sender/recipient profile | Check schema endpoints for required fields per country |
422 Unprocessable | Quote expired, limit exceeded, or invalid references | Refresh the quote; check limits via GET /fx/participants/{id}/limits |
429 Too Many Requests | Rate limit exceeded | Implement caching for destinations, banks, and schemas (24h TTL) |
Feedback & Support
We're continuously improving these docs. For feedback, questions, or technical issues:
- Get in touch with our sales team
- Sample project on GitHub
