Skip to main content
The Transfer Token API enables you to trigger payouts from your treasury wallet to user wallets. The API automatically performs compliance checks and will return specific errors if the recipient has not provided the appropriate information or did not pass the necessary compliance checks.
You must include "transactionType": "regulated-transfer" in your transfer request to enable compliance checks. Without it, the transfer defaults to direct mode, which skips all compliance screening. See Supported Transfer Types for all options.
curl --request POST \
    --url 'https://staging.crossmint.com/api/2025-06-09/wallets/<your-treasury-wallet-locator>/tokens/base-sepolia:usdc/transfers' \
    --header 'X-API-KEY: <your-server-api-key>' \
    --header 'Content-Type: application/json' \
    --data '{
        "recipient": "<recipient-wallet-address>",
        "amount": "100",
        "transactionType": "regulated-transfer"
    }'
import { CrossmintWallets, createCrossmint } from "@crossmint/wallets-sdk";

const crossmint = createCrossmint({
    apiKey: "<your-server-api-key>",
});

const crossmintWallets = CrossmintWallets.from(crossmint);

const treasuryLocator = "<your-treasury-wallet-locator>";

const treasuryWallet = await crossmintWallets.getWallet(treasuryLocator, {
    chain: "base-sepolia",
});

await treasuryWallet.useSigner({ type: "server", secret: process.env.WALLET_SIGNER_SECRET! });

try {
    const { hash, explorerLink } = await treasuryWallet.send(
        "<recipient-wallet-address>",
        "usdc",
        "100",
        { transactionType: "regulated-transfer" }
    );

    console.log(`Transfer successful: ${hash}`);
    console.log(`Explorer: ${explorerLink}`);
} catch (error) {
    console.error("Transfer failed:", error.message);
    // Handle compliance errors - see error codes below
}
import requests

treasury_locator = "<your-treasury-wallet-locator>"
token_locator = "base-sepolia:usdc"

url = f"https://staging.crossmint.com/api/2025-06-09/wallets/{treasury_locator}/tokens/{token_locator}/transfers"

payload = {
    "recipient": "<recipient-wallet-address>",
    "amount": "100",
    "transactionType": "regulated-transfer"
}
headers = {
    "X-API-KEY": "<your-server-api-key>",
    "Content-Type": "application/json"
}

response = requests.post(url, json=payload, headers=headers)

print(response.json())
Signing transactions (REST API): The cURL and Python examples above use the simplified Transfer Token endpoint. If your integration requires custom transaction signing, see the full create → sign → approve flow in Send a Transaction (EVM) — REST tab.
Wallet locator options for a treasury wallet:
  • <walletAddress> (e.g., 0x1234...5678)
  • chainType[:<walletType>]:alias:<alias> (e.g., evm:smart:alias:treasury)
Transfers are irreversible once executed. Clients must ensure that all transaction details are correct before submitting a transfer request.

Compliance Errors

If a payout fails compliance checks, the API will return an error with a specific reason code. Here are the potential compliance-related errors:
Error Message: “Required personal data is missing to complete regulated transfer”Description: The recipient wallet has not provided all personal data required for compliance screening. The recipient must complete their profile with first name, last name, date of birth, and country of residence.Resolution: Ensure the recipient has attached their personal data using the user onboarding API.
// Example error response
{
    "error": {
        "reason": "recipient_personal_data_missing",
        "message": "Required personal data is missing to complete regulated transfer"
    }
}
Error Message: “Recipient wallet address is sanctioned and can’t receive assets”Description: The recipient’s wallet address failed Elliptic sanctions screening. This indicates the address has been flagged in sanctions lists or is associated with prohibited activities.Resolution: The payout cannot proceed. The recipient’s wallet address has been identified as high-risk and is blocked from receiving payouts.
// Example error response
{
    "error": {
        "reason": "recipient_wallet_sanctioned",
        "message": "Recipient wallet address is sanctioned and can't receive assets"
    }
}
Error Message: “User does not meet the verification requirements to proceed. Check the user’s verification status via GET /users/{userLocator}/identity-verification.”Description: The recipient did not pass the verification checks required to proceed. This is returned for any blocking KYC outcome, including sanctions/PEP screening matches, watchlist hits, banned users, restricted geographies, or failed full/light KYC.Resolution: The payout cannot proceed. Inspect the recipient’s verification status via GET /users/{userLocator}/identity-verification to determine the specific reason and any next steps.
// Example error response
{
    "error": {
        "reason": "recipient_person_sanctioned",
        "message": "User does not meet the verification requirements to proceed. Check the user's verification status via GET /users/{userLocator}/identity-verification."
    }
}
Error Message: “Regulated transfers can only be sent to Crossmint-managed wallets or external wallets linked to a Crossmint user.”Description: The destination address is not a Crossmint-managed wallet and is not linked to a Crossmint user. Payouts support transfers to Crossmint user wallets and to external wallets that a Crossmint user has linked to their account.Resolution: Ensure the recipient has a Crossmint wallet, or link the external wallet address to the recipient’s Crossmint user with the Link External Wallet API before initiating the payout. After linking, the recipient may also need to prove ownership by signing the verification challenge (see the recipient_ownership_unverified entry below):
curl --request PUT \
    --url https://staging.crossmint.com/api/2025-06-09/users/YOUR_USER_LOCATOR/linked-wallets/YOUR_WALLET_ADDRESS \
    --header 'X-API-KEY: YOUR_API_KEY' \
    --header 'Content-Type: application/json' \
    --data '{
        "chain": "base-sepolia"
    }'
// Example error response
{
    "error": {
        "reason": "recipient_type_unsupported",
        "message": "Regulated transfers can only be sent to Crossmint-managed wallets or external wallets linked to a Crossmint user."
    }
}
Error Message: “Recipient external wallet ownership has not been verified. The recipient must sign the wallet ownership challenge before receiving regulated transfers.”Description: The destination address is an external wallet linked to a Crossmint user, but the user has not proven ownership of the wallet by signing the ownership verification challenge.Resolution: Have the recipient sign the verificationChallenge returned when the wallet was linked (or retrieve it anytime via the Get Linked Wallet endpoint), then submit the signature as proof using the same Link External Wallet API:
curl --request PUT \
    --url https://staging.crossmint.com/api/2025-06-09/users/YOUR_USER_LOCATOR/linked-wallets/YOUR_WALLET_ADDRESS \
    --header 'X-API-KEY: YOUR_API_KEY' \
    --header 'Content-Type: application/json' \
    --data '{
        "chain": "base-sepolia",
        "proof": "YOUR_SIGNATURE"
    }'
Once the response shows "ownership": { "verified": true }, retry the payout. The verification flow is the same one used for onramp — see Proof of Ownership for the full flow, including how to sign the challenge on EVM and Solana.
// Example error response
{
    "error": {
        "reason": "recipient_ownership_unverified",
        "message": "Recipient external wallet ownership has not been verified. The recipient must sign the wallet ownership challenge before receiving regulated transfers."
    }
}

Error Handling Best Practices

When implementing payouts, follow these best practices for error handling:
1

Validate recipient before transfer

Before initiating a payout, verify that the recipient has completed their onboarding and provided all required personal data. This can help prevent recipient_personal_data_missing errors.
2

Implement retry logic for transient errors

Some errors may be transient (e.g., temporary API issues). Implement appropriate retry logic with exponential backoff for non-compliance errors.
3

Log compliance failures

Log all compliance-related errors for audit purposes. These logs are important for regulatory reporting and investigating failed transfers.
4

Notify users of compliance issues

When a payout fails due to compliance issues, notify the affected users with clear instructions on how to resolve the issue (e.g., completing their profile, contacting support).

Supported Transfer Types

When calling the Transfer Token API, you must specify the transactionType field to indicate whether the transfer requires compliance checks:
TypeDescription
regulated-transferThe transaction is routed through Crossmint’s regulated infrastructure. This enables compliance with regulatory requirements and performs AML screening, sanctions checks, and travel rule validation. Required for payouts to customers.
onrampThe transaction is routed through Crossmint’s regulated infrastructure, similar to regulated-transfer. Used for fiat-to-crypto onramp flows that require compliance checks.
directStandard transfer with no compliance checks. Used for internal treasury-to-treasury transfers. This is the default if transactionType is omitted.
Omitting the transactionType field defaults to direct, which skips all compliance checks. Always set transactionType to regulated-transfer (or onramp for fiat-to-crypto flows) when sending payouts to customer wallets.
Compliant payouts work for transfers from Crossmint treasury wallets to Crossmint user wallets and to external wallets linked to a Crossmint user with verified ownership. Payouts to unlinked external wallets are not supported.

Additional Resources

API Reference

Deep dive into the transfer API reference

Talk to an expert

Contact the Crossmint sales team for support