Transfer Requirements

Get the fields a transfer needs before you create it.

Each quote has its own transfer requirements: the fields you need to send when you create the transfer. They depend on the country, the payment method and the customer type, so fetch them with the quote id instead of hardcoding them.

The requirements are a JSON Schema (Draft 2020-12). Always validate your transfer request against them before you send it. A request that doesn't match is rejected. You can also use them to build your form.

⚠️

Requirements are dynamic
The required fields change with the country, currency, payment method and customer type, and can change over time. Don't hardcode them. Fetch the requirements for each quote and build your request from them.

ActionEndpoint
Get the transfer requirementsGET /api/v2/transfers/quotes/{quote_id}/requirements


Get Transfer Requirements

GET /api/v2/transfers/quotes/{quote_id}/requirements

curl --location --request GET 'https://harbor-sandbox.owlpay.com/api/v2/transfers/quotes/{{QUOTE_ID}}/requirements' \
--header 'Accept: application/json' \
--header 'X-API-KEY: {{API_KEY}}'

The response below is for an off-ramp quote from USDC to MXN in Mexico (SPEI), shortened. Long lists are cut with "...". Other routes return different fields.

{
    "$schema": "https://json-schema.org/draft/2020-12/schema",
    "type": "object",
    "additionalProperties": false,
    "required": ["quote_id", "source", "destination", "on_behalf_of", "application_transfer_uuid"],
    "properties": {
        "on_behalf_of": { "type": "string" },
        "quote_id": { "type": "string" },
        "application_transfer_uuid": { "type": "string" },
        "source": {
            "type": "object",
            "additionalProperties": false,
            "required": ["payment_instrument"],
            "properties": {
                "payment_instrument": {
                    "type": "object",
                    "additionalProperties": false,
                    "properties": {
                        "address": { "type": "string", "description": "Blockchain address." },
                        "wallet_uuid": {
                            "type": "string",
                            "description": "Wallet UUID. If provided, the wallet address will be used automatically. Required if address is not provided."
                        }
                    },
                    "oneOf": [
                        { "required": ["address"] },
                        { "required": ["wallet_uuid"] }
                    ]
                }
            }
        },
        "destination": {
            "type": "object",
            "additionalProperties": false,
            "required": ["beneficiary_info", "payout_instrument", "transfer_purpose", "is_self_transfer"],
            "properties": {
                "beneficiary_info": {
                    "type": "object",
                    "additionalProperties": false,
                    "required": ["beneficiary_name", "beneficiary_address", "beneficiary_id_doc_number", "beneficiary_dob"],
                    "properties": {
                        "beneficiary_name": { "type": "string", "title": "Recipient Full Name", "maxLength": 140 },
                        "beneficiary_address": {
                            "type": "object",
                            "title": "Address",
                            "required": ["street", "country"],
                            "properties": {
                                "street": { "type": "string", "maxLength": 200, "title": "Street" },
                                "city": { "type": ["string", "null"], "maxLength": 80, "title": "City" },
                                "state_province": { "type": ["string", "null"], "maxLength": 80, "title": "State / Province" },
                                "postal_code": { "type": ["string", "null"], "maxLength": 20, "title": "Postal Code" },
                                "country": { "type": "string", "minLength": 2, "maxLength": 2, "title": "Country", "enum": ["AD", "AE", "..."] }
                            },
                            "allOf": [
                                {
                                    "if": { "properties": { "country": { "const": "MX" } } },
                                    "then": {
                                        "required": ["city", "state_province", "postal_code"],
                                        "properties": {
                                            "city": { "type": "string" },
                                            "state_province": { "type": "string", "title": "State", "enum": ["AGU", "BCN", "..."] },
                                            "postal_code": { "type": "string" }
                                        }
                                    }
                                },
                                "..."
                            ]
                        },
                        "beneficiary_dob": { "type": "string", "title": "Recipient Date of Birth" },
                        "beneficiary_id_doc_number": { "type": "string", "title": "Recipient ID Number", "maxLength": 80 }
                    }
                },
                "payout_instrument": {
                    "type": "object",
                    "additionalProperties": false,
                    "required": ["mx_clabe"],
                    "properties": {
                        "mx_clabe": { "type": "string", "title": "CLABE", "pattern": "^[0-9]{18}$" }
                    }
                },
                "transfer_purpose": { "type": "string", "enum": ["TRANSFER_TO_OWN_ACCOUNT", "FAMILY_MAINTENANCE", "..."] },
                "is_self_transfer": { "type": "boolean" },
                "payment_reference": {
                    "type": "string",
                    "maxLength": 30,
                    "pattern": "^[A-Za-z0-9]([A-Za-z0-9 _-]*[A-Za-z0-9])?$",
                    "title": "Payment Reference"
                }
            }
        },
        "refund_address": {
            "type": "string",
            "description": "Optional blockchain address to refund to if this off-ramp or on-chain (swap) transfer later fails. When set, it pre-fills the refund address but never triggers an automatic refund by itself."
        }
    },
    "title": "MX_SPEI",
    "$comment": "{\"bank_title\":\"MX_SPEI\",\"mapping_version\":1}"
}

If the quote doesn't exist, the request returns 404 with error code 2001.


Reading the Requirements

The requirements are meant to be read by your code, with a JSON Schema validator. Knowing the main keywords helps when you build a form or debug a rejected request:

  • required: the fields you must send at that level.
  • properties: the fields you can send, with their limits, such as enum (allowed values), maxLength and pattern (a regex the value must match). In the example, the mx_clabe pattern ^[0-9]{18}$ means 18 digits.
  • additionalProperties: false: any field not listed in properties is rejected.
  • allOf with if / then: fields that are only needed in some cases. In the example, city, state_province and postal_code are required when the address country is MX.
  • oneOf: send exactly one of the options. In the example, the sender's wallet is either an address or a wallet_uuid.
  • title: a readable name for the field. You can use it as a form label.

The top-level title identifies the route the requirements belong to. Each route has its own title and its own set of fields.


Example Request

This request body matches the requirements in the example above. Send it to POST /api/v2/transfers to create the transfer.

{
    "quote_id": "{{QUOTE_ID}}",
    "on_behalf_of": "{{CUSTOMER_UUID}}",
    "application_transfer_uuid": "{{APPLICATION_TRANSFER_UUID}}",
    "source": {
        "payment_instrument": {
            "address": "{{SENDER_WALLET_ADDRESS}}"
        }
    },
    "destination": {
        "beneficiary_info": {
            "beneficiary_name": "Sample Beneficiary",
            "beneficiary_address": {
                "street": "Av. Paseo de la Reforma 222",
                "city": "Ciudad de Mexico",
                "state_province": "CMX",
                "postal_code": "06600",
                "country": "MX"
            },
            "beneficiary_id_doc_number": "SABE900101HDFMPL09",
            "beneficiary_dob": "1990-01-01"
        },
        "payout_instrument": {
            "mx_clabe": "032180000118359719"
        },
        "transfer_purpose": "FAMILY_MAINTENANCE",
        "is_self_transfer": false
    }
}

How it follows the requirements:

  • Every field in a required list is included, such as beneficiary_id_doc_number and beneficiary_dob.
  • The address country is MX, so city, state_province and postal_code are also included, and state_province is one of the listed values.
  • mx_clabe is 18 digits, to match its pattern.
  • The sender's wallet uses address, one of the two oneOf options.
  • The optional fields payment_reference and refund_address are left out.
  • No other fields are sent, because of additionalProperties: false.

Validate Before You Submit

Validate your request against the requirements before you call POST /api/v2/transfers. If a request doesn't match, the transfer isn't created and the API returns 422 with error code 2005. errors lists each field that failed:

{
    "message": "The required properties (beneficiary_dob) are missing",
    "error": "The required properties (beneficiary_dob) are missing",
    "code": 2005,
    "error_type": "general.validation_error",
    "errors": {
        "/destination/beneficiary_info": [
            "The required properties (beneficiary_dob) are missing"
        ]
    }
}

Validating first catches these errors in your own code:

import Ajv from "ajv/dist/2020.js";

const ajv = new Ajv({ allErrors: true, strict: false });

// schema: the response from the requirements endpoint
// payload: the transfer request you will send
const validate = ajv.compile(schema);

if (!validate(payload)) {
  console.log(validate.errors);
  throw new Error("Transfer payload validation failed.");
}

Notes

  • Get the requirements again for each new quote. They can change by country, payment method and customer type.
  • Recipients use the same field names, so you can save these fields once and reuse them.
  • Unlike Transfer (V1), the Travel Rule fields are included in the requirements, such as the beneficiary's details, the transfer purpose and the wallet type. A request that matches the requirements already has them.

Did this page help you?