{
  "openapi": "3.1.0",
  "info": {
    "title": "Travel Rule Protocol (TRP) API",
    "version": "3.2.1",
    "summary": "Public TRP API for VASP-to-VASP Travel Rule data exchange.",
    "x-logo": {
      "url": "./assets/logo.png",
      "altText": "TRP",
      "href": "/",
      "backgroundColor": "#FFFFFF"
    },
    "contact": {
      "name": "KYCAID TRP Team",
      "url": "https://docs.travel-rule.com",
      "email": "support@travel-rule.com"
    },
    "license": {
      "name": "Proprietary",
      "url": "https://docs.travel-rule.com"
    },
    "description": "# Working with the documentation\n\nThis section describes the public endpoints of the **Travel Rule\nProtocol (TRP) Registry API**. These endpoints allow third-party\nsystems and Virtual Asset Service Providers (VASPs) to discover,\nidentify and register participants in the TRP network.\n\nThe API follows REST conventions and returns responses in JSON\nformat. All requests should be made over **HTTPS**.\n\n## Introduction: How the TRP Public API Works\n\nThe TRP API enables VASPs to:\n\n- Authenticate using **API keys**\n- Generate unique **Travel Addresses** for beneficiaries\n- Initiate **Travel Rule transfers**\n- Exchange originator and beneficiary data securely\n- Receive **callback** notifications from TRP during the verification process\n\nTRP follows global compliance standards (**FATF**, **IVMS101**) and\nensures secure, encrypted communication between VASPs.\n\nEvery integration follows one simple flow:\n\n1. **Request JWT token** → authenticate your VASP\n2. **Generate Travel Address** → unique identifier for the beneficiary VASP\n3. **Initiate Transfer** → send IVMS101-compliant data\n4. **Receive callback** → TRP sends status updates to your backend\n\n## Authentication Overview\n\nBefore calling any protected endpoint, your system must generate a\n**JWT access token** using your API Key. This ensures:\n\n- Secure API access\n- Verified VASP identity\n- Ability to track API usage\n- Authorization for initiating Travel Rule transfers\n\nThe access token is short-lived (**TTL ≈ 1 hour**) for security\nreasons. Pass it as `Authorization: Bearer <jwt>` on every protected\nendpoint.\n\n## Conventions\n\n- All bodies are `application/json` unless explicitly stated.\n- Successful responses are wrapped in `{ \"status\": true, \"data\": ... }`.\n- Errors are wrapped in `{ \"status\": false, \"errors\": [{ \"code\", \"message\" }] }`.\n- Timestamps use ISO-8601 in UTC.\n- Country codes follow ISO-3166-1 alpha-2.\n"
  },
  "servers": [
    {
      "url": "https://trp.travel-rule.com",
      "description": "Production public API host"
    },
    {
      "url": "http://localhost:3000"
    }
  ],
  "tags": [
    {
      "name": "Identity",
      "x-displayName": "Identity",
      "description": "Public identity endpoints. These return non-sensitive VASP\nmetadata (legal name, LEI, x509 public key) and server health.\nThey do **not** require authentication.\n"
    },
    {
      "name": "Auth",
      "x-displayName": "Auth",
      "description": "Authentication endpoints. Use your **API Key** (issued in the TRP\ndashboard) to obtain a short-lived **JWT** that authorises every\nprotected call.\n\n## Authentication header\n\nYou may pass the API Key in either of these headers:\n\n```http\nx-api-key: <YOUR_API_KEY>\n```\n\nor\n\n```http\nAuthorization: ApiKey <YOUR_API_KEY>\n```\n\nOnce you receive the JWT, all subsequent calls must include:\n\n```http\nAuthorization: Bearer <JWT>\n```\n"
    },
    {
      "name": "Address",
      "x-displayName": "Address",
      "description": "**Travel Address** endpoints. A Travel Address is an opaque,\nURL-safe encoding of a beneficiary VASP route. It is generated by\nthe beneficiary VASP and shared with the originator VASP so that\nIVMS101 data can be routed back.\n"
    },
    {
      "name": "Transfer",
      "x-displayName": "Transfer",
      "description": "High-level Travel Rule **transfer** operations from the originator\nVASP perspective. Transfers can flow two ways depending on what was\nprovided at initiation:\n\n- `OPEN_VASP` — beneficiary VASP is reachable via Travel Address.\n- `EMAIL` — beneficiary VASP is unknown; we send an email so the\n  recipient can self-attest.\n"
    },
    {
      "name": "Compliance",
      "x-displayName": "Compliance",
      "description": "Inter-VASP **compliance** endpoints called when TRP routes data\nbetween participating VASPs. They are typically invoked by other\nVASPs / TRP itself, **not** by your front-end.\n"
    },
    {
      "name": "Discovery",
      "x-displayName": "Discovery",
      "description": "Look up other VASPs in the TRP registry by domain, LEI, name,\nemail, or directory ID.\n"
    },
    {
      "name": "Callbacks",
      "x-displayName": "Callbacks",
      "description": "# Callbacks\n\nTRP delivers asynchronous updates to your `callback` URL via\nsigned `POST` requests. Each callback is queued and retried with\nexponential backoff.\n\n## Retry schedule\n\nThe KYCAID TRP backend retries failed callbacks at:\n\n```\n1m → 5m → 15m → 30m → 1h → 3h → 6h → 12h → 24h\n```\n\nAfter 9 unsuccessful attempts the callback is marked `FAILED` and\ndropped.\n\n## Signature verification\n\nEvery callback ships an `x-data-integrity` header computed as:\n\n```\nHMAC-SHA512( BASE64( request_body ), customer_signing_secret )\n```\n\nPseudocode:\n\n```js\nimport crypto from 'node:crypto';\n\nfunction verify(rawBody, signatureHeader, secret) {\n    const base64 = Buffer.from(rawBody, 'utf8').toString('base64');\n    const expected = crypto\n        .createHmac('sha512', secret)\n        .update(base64)\n        .digest('hex');\n    return crypto.timingSafeEqual(\n        Buffer.from(expected),\n        Buffer.from(signatureHeader),\n    );\n}\n```\n\n## Callback types\n\n| Type | When | Body |\n|------|------|------|\n| `INQUIRY` | Sent by originator VASP to beneficiary VASP after `transfers/initiate`. | `{ asset, amount, callback, IVMS101 }` |\n| `INQUIRY_RESOLUTION` | Sent by beneficiary VASP back to originator. | `{ approved, callback }` |\n| `TRANSFER_RESOLUTION` | Sent to the originator's `callback` once the beneficiary VASP approves. | `{ transferId, status, statusComment, callback }` |\n| `TRANSFER_CONFIRMATION` | Sent to the beneficiary's stored callback once `txId` is broadcast. | `{ txid }` |\n\nRespond with **2xx** to acknowledge. Any non-2xx response is\nretried per the schedule above.\n"
    }
  ],
  "components": {
    "securitySchemes": {
      "ApiKey": {
        "type": "apiKey",
        "in": "header",
        "name": "x-api-key",
        "description": "Customer API Key issued in the TRP dashboard. Used **only** to\nobtain a JWT through `POST /auth/token`.\n"
      },
      "ApiKeyAuth": {
        "type": "apiKey",
        "in": "header",
        "name": "Authorization",
        "description": "Alternative API Key header. Use the format\n`Authorization: ApiKey <YOUR_API_KEY>`.\n"
      },
      "BearerAuth": {
        "type": "http",
        "scheme": "bearer",
        "bearerFormat": "JWT",
        "description": "Short-lived JWT (TTL ≈ 1h) obtained from\n[`POST /auth/token`](#tag/Auth/operation/createToken). Pass as\n`Authorization: Bearer <jwt>` on every protected endpoint.\n"
      }
    },
    "parameters": {
      "TransferQueryId": {
        "name": "q",
        "in": "query",
        "required": true,
        "description": "Public ID of the transfer (UUID).",
        "schema": {
          "type": "string",
          "format": "uuid"
        }
      },
      "TransferPublicId": {
        "name": "transferPublicId",
        "in": "path",
        "required": true,
        "description": "Public ID of the transfer (UUID).",
        "schema": {
          "type": "string",
          "format": "uuid"
        }
      },
      "BeneficiaryPublicId": {
        "name": "beneficiaryPublicId",
        "in": "path",
        "required": true,
        "description": "Public ID of the beneficiary applicant (UUID).",
        "schema": {
          "type": "string",
          "format": "uuid"
        }
      },
      "CommentId": {
        "name": "commentId",
        "in": "path",
        "required": true,
        "description": "Numeric ID of the comment.",
        "schema": {
          "type": "integer",
          "format": "int64",
          "minimum": 1
        }
      },
      "ApiVersionHeader": {
        "name": "api-version",
        "in": "header",
        "required": false,
        "description": "TRP protocol version. Defaults to `3.2.1`.",
        "schema": {
          "type": "string",
          "example": "3.2.1"
        }
      },
      "RequestIdentifierHeader": {
        "name": "request-identifier",
        "in": "header",
        "required": false,
        "description": "UUID used to correlate the call with its asynchronous callback.",
        "schema": {
          "type": "string",
          "format": "uuid"
        }
      }
    },
    "schemas": {
      "SuccessEnvelope": {
        "type": "object",
        "properties": {
          "status": {
            "type": "boolean",
            "const": true
          },
          "data": {
            "type": "object"
          }
        },
        "required": [
          "status"
        ]
      },
      "ErrorItem": {
        "type": "object",
        "properties": {
          "code": {
            "type": "string",
            "description": "Machine-readable error code.",
            "example": "INVALID_REQUEST"
          },
          "message": {
            "type": "string",
            "description": "Human-readable error description.",
            "example": "Asset is required"
          }
        },
        "required": [
          "code",
          "message"
        ]
      },
      "ErrorEnvelope": {
        "type": "object",
        "properties": {
          "status": {
            "type": "boolean",
            "const": false
          },
          "errors": {
            "type": "array",
            "items": {
              "$ref": "#/components/schemas/ErrorItem"
            }
          }
        },
        "required": [
          "status",
          "errors"
        ]
      },
      "ApplicantCommon": {
        "type": "object",
        "description": "Shared fields available for both natural and legal applicants.\nThese properties are accepted on the `originator` and\n`beneficiary` objects of `POST /transfers/initiate`\nregardless of `personType`.\n",
        "properties": {
          "id": {
            "type": "integer",
            "readOnly": true,
            "description": "Internal database ID. Read-only."
          },
          "externalId": {
            "type": "string",
            "maxLength": 64,
            "description": "Your internal identifier for this applicant."
          },
          "middleName": {
            "type": "string",
            "maxLength": 100,
            "description": "Middle name (typically used for natural persons)."
          },
          "dateOfBirth": {
            "type": "string",
            "format": "date",
            "maxLength": 10,
            "example": "1990-01-15",
            "description": "Applicant date of birth. ISO-8601 date."
          },
          "placeOfBirth": {
            "type": "string",
            "maxLength": 70,
            "example": "London"
          },
          "addressType": {
            "type": "string",
            "enum": [
              "HOME",
              "BIZZ",
              "GEOG"
            ],
            "description": "IVMS101 address type — Home / Business / Geographic."
          },
          "streetName": {
            "type": "string",
            "maxLength": 70
          },
          "buildingNumber": {
            "type": "string",
            "maxLength": 16
          },
          "buildingName": {
            "type": "string",
            "maxLength": 35
          },
          "postCode": {
            "type": "string",
            "maxLength": 16
          },
          "townName": {
            "type": "string",
            "maxLength": 35
          },
          "country": {
            "type": "string",
            "minLength": 2,
            "maxLength": 2,
            "description": "ISO-3166-1 alpha-2 country code."
          },
          "countrySubDivision": {
            "type": "string",
            "maxLength": 35,
            "description": "State / province / region code."
          },
          "nationalId": {
            "type": "string",
            "maxLength": 35,
            "description": "National identifier value (passport number, LEI, etc.)."
          },
          "nationalIdType": {
            "type": "string",
            "maxLength": 10,
            "enum": [
              "ARNU",
              "CCPT",
              "RAID",
              "DRLC",
              "FIIN",
              "TXID",
              "SOCS",
              "IDCD",
              "LEIX",
              "MISC"
            ],
            "description": "IVMS101 national-identifier type. `LEIX` for legal\nentities (LEI), `CCPT` for passport, etc.\n"
          },
          "countryOfIssue": {
            "type": "string",
            "minLength": 2,
            "maxLength": 2,
            "description": "ISO-3166-1 alpha-2 country that issued `nationalId`."
          },
          "accountNumber": {
            "type": "string",
            "maxLength": 100,
            "description": "VASP-internal account identifier for this applicant."
          },
          "walletAddress": {
            "type": "string",
            "maxLength": 100,
            "description": "On-chain wallet address belonging to this applicant."
          },
          "email": {
            "type": "string",
            "format": "email",
            "maxLength": 255,
            "description": "Contact email. Not persisted on the `Applicant` record\nin the database — used at request time (e.g. EMAIL\nflow) and forwarded to email notifications.\n"
          },
          "isSelfHosted": {
            "type": "boolean",
            "description": "`true` if this applicant is a self-hosted\n(non-custodial) wallet owner.\n"
          },
          "vasp": {
            "$ref": "#/components/schemas/ApplicantVasp"
          }
        }
      },
      "ApplicantVasp": {
        "type": "object",
        "description": "VASP that holds (or self-hosts on behalf of) this applicant.\nUsed to identify the counterparty service provider when it\ncannot be resolved from a Travel Address.\n",
        "properties": {
          "name": {
            "type": "string",
            "maxLength": 100,
            "description": "Display name of the VASP.",
            "example": "Wonderland Exchange"
          }
        }
      },
      "NaturalApplicant": {
        "title": "Natural person",
        "description": "A **natural person** (физ. лицо) participating in a transfer.\nRequires `firstName` + `lastName`. All fields from\n`ApplicantCommon` are also available (address, KYC,\n`dateOfBirth`, `placeOfBirth`, etc.).\n",
        "allOf": [
          {
            "$ref": "#/components/schemas/ApplicantCommon"
          },
          {
            "type": "object",
            "required": [
              "personType",
              "firstName",
              "lastName"
            ],
            "properties": {
              "personType": {
                "type": "string",
                "enum": [
                  "natural"
                ],
                "default": "natural",
                "description": "Discriminator. Must be `natural`."
              },
              "firstName": {
                "type": "string",
                "maxLength": 100,
                "example": "Alice"
              },
              "lastName": {
                "type": "string",
                "maxLength": 100,
                "example": "Liddell"
              }
            }
          }
        ]
      },
      "LegalApplicant": {
        "title": "Legal entity",
        "description": "A **legal entity** (юр. лицо) participating in a transfer.\nRequires `legalName`. `firstName` / `lastName` are optional and,\nif omitted, are derived from `legalName` server-side.\n",
        "allOf": [
          {
            "$ref": "#/components/schemas/ApplicantCommon"
          },
          {
            "type": "object",
            "required": [
              "personType",
              "legalName"
            ],
            "properties": {
              "personType": {
                "type": "string",
                "enum": [
                  "legal"
                ],
                "description": "Discriminator. Must be `legal`."
              },
              "legalName": {
                "type": "string",
                "maxLength": 100,
                "example": "Wonderland Holdings Ltd"
              },
              "firstName": {
                "type": "string",
                "maxLength": 100,
                "description": "Optional contact-person first name."
              },
              "lastName": {
                "type": "string",
                "maxLength": 100,
                "description": "Optional contact-person last name."
              }
            }
          }
        ]
      },
      "Applicant": {
        "description": "A natural or legal person participating in a transfer\n(originator or beneficiary).\n\n**Use the `personType` switcher** to choose between\nNatural person (`natural`) and Legal entity (`legal`).\n",
        "oneOf": [
          {
            "$ref": "#/components/schemas/NaturalApplicant"
          },
          {
            "$ref": "#/components/schemas/LegalApplicant"
          }
        ],
        "discriminator": {
          "propertyName": "personType",
          "mapping": {
            "natural": "#/components/schemas/NaturalApplicant",
            "legal": "#/components/schemas/LegalApplicant"
          }
        }
      },
      "IVMS101Name": {
        "type": "object",
        "properties": {
          "primaryIdentifier": {
            "type": "string",
            "minLength": 1
          },
          "secondaryIdentifier": {
            "type": "string"
          },
          "nameIdentifierType": {
            "type": "string",
            "enum": [
              "LEGL",
              "ALIA",
              "BIRT",
              "MAID",
              "NICK",
              "TITL",
              "FOTH",
              "GIVN",
              "SURN"
            ]
          }
        },
        "required": [
          "primaryIdentifier",
          "nameIdentifierType"
        ]
      },
      "IVMS101Address": {
        "type": "object",
        "properties": {
          "addressType": {
            "type": "string",
            "enum": [
              "HOME",
              "BIZZ",
              "MLTO",
              "PBOX"
            ]
          },
          "addressLine": {
            "type": "array",
            "items": {
              "type": "string"
            }
          },
          "country": {
            "type": "string",
            "pattern": "^[A-Z]{2}$"
          }
        },
        "required": [
          "addressType",
          "country"
        ]
      },
      "IVMS101NationalIdentification": {
        "type": "object",
        "properties": {
          "nationalIdentifier": {
            "type": "string",
            "minLength": 1
          },
          "nationalIdentifierType": {
            "type": "string",
            "enum": [
              "ARNU",
              "CCPT",
              "RAID",
              "DRLC",
              "FIIN",
              "TXID",
              "SOCS",
              "IDCD",
              "LEIX",
              "MISC"
            ]
          },
          "countryOfIssue": {
            "type": "string",
            "pattern": "^[A-Z]{2}$"
          },
          "registrationAuthority": {
            "type": "string"
          }
        },
        "required": [
          "nationalIdentifier",
          "nationalIdentifierType"
        ]
      },
      "IVMS101NaturalPerson": {
        "type": "object",
        "description": "IVMS101 natural-person payload.",
        "properties": {
          "naturalPerson": {
            "type": "object",
            "properties": {
              "name": {
                "type": "object",
                "properties": {
                  "nameIdentifier": {
                    "type": "array",
                    "minItems": 1,
                    "items": {
                      "$ref": "#/components/schemas/IVMS101Name"
                    }
                  }
                },
                "required": [
                  "nameIdentifier"
                ]
              },
              "geographicAddress": {
                "type": "array",
                "items": {
                  "$ref": "#/components/schemas/IVMS101Address"
                }
              },
              "nationalIdentification": {
                "$ref": "#/components/schemas/IVMS101NationalIdentification"
              },
              "customerIdentification": {
                "type": "string"
              },
              "dateAndPlaceOfBirth": {
                "type": "object",
                "properties": {
                  "dateOfBirth": {
                    "type": "string",
                    "format": "date"
                  },
                  "placeOfBirth": {
                    "type": "string"
                  }
                },
                "required": [
                  "dateOfBirth",
                  "placeOfBirth"
                ]
              }
            },
            "required": [
              "name"
            ]
          }
        },
        "required": [
          "naturalPerson"
        ]
      },
      "IVMS101LegalPerson": {
        "type": "object",
        "description": "IVMS101 legal-person payload.",
        "properties": {
          "legalPerson": {
            "type": "object",
            "properties": {
              "name": {
                "type": "object",
                "properties": {
                  "nameIdentifier": {
                    "type": "array",
                    "minItems": 1,
                    "items": {
                      "type": "object",
                      "properties": {
                        "legalPersonName": {
                          "type": "string",
                          "minLength": 1
                        },
                        "legalPersonNameIdentifierType": {
                          "type": "string",
                          "enum": [
                            "LEGL",
                            "SHOR",
                            "TRAD",
                            "DBA"
                          ]
                        }
                      },
                      "required": [
                        "legalPersonName",
                        "legalPersonNameIdentifierType"
                      ]
                    }
                  }
                },
                "required": [
                  "nameIdentifier"
                ]
              },
              "geographicAddress": {
                "type": "array",
                "items": {
                  "$ref": "#/components/schemas/IVMS101Address"
                }
              },
              "customerNumber": {
                "type": "string"
              },
              "nationalIdentification": {
                "$ref": "#/components/schemas/IVMS101NationalIdentification"
              }
            },
            "required": [
              "name"
            ]
          }
        },
        "required": [
          "legalPerson"
        ]
      },
      "IVMS101Person": {
        "description": "An IVMS101 person — either natural or legal.",
        "oneOf": [
          {
            "$ref": "#/components/schemas/IVMS101NaturalPerson"
          },
          {
            "$ref": "#/components/schemas/IVMS101LegalPerson"
          }
        ]
      },
      "IVMS101Originator": {
        "type": "object",
        "description": "Full IVMS101 originator envelope sent in callbacks. Mirrors\nthe `originatingVASP` and `originator` fields used by the\nKYCAID TRP backend.\n",
        "properties": {
          "originator": {
            "type": "object",
            "properties": {
              "originatorPersons": {
                "type": "array",
                "items": {
                  "$ref": "#/components/schemas/IVMS101Person"
                }
              },
              "accountNumber": {
                "type": "array",
                "items": {
                  "type": "string"
                }
              }
            }
          },
          "beneficiary": {
            "type": "object",
            "properties": {
              "beneficiaryPersons": {
                "type": "array",
                "items": {
                  "$ref": "#/components/schemas/IVMS101Person"
                }
              },
              "accountNumber": {
                "type": "array",
                "items": {
                  "type": "string"
                }
              }
            }
          },
          "originatingVASP": {
            "type": "object",
            "properties": {
              "originatingVASP": {
                "$ref": "#/components/schemas/IVMS101Person"
              }
            }
          },
          "beneficiaryVASP": {
            "type": "object",
            "properties": {
              "beneficiaryVASP": {
                "$ref": "#/components/schemas/IVMS101Person"
              }
            }
          }
        }
      },
      "TransferStatus": {
        "type": "string",
        "description": "Lifecycle state of a Travel Rule transfer (Prisma\n`TransferStatus` enum).\n\n- `PENDING` — initial state after `transfers/initiate`,\n  waiting for the beneficiary VASP to resolve the inquiry.\n- `APPROVED` — beneficiary VASP confirmed the destination\n  wallet via `inquiryResolution`.\n- `REJECTED` — beneficiary VASP refused the transfer.\n- `COMPLETED` — the on-chain transaction was broadcast and\n  confirmed.\n- `CANCELLED` — the originator cancelled before completion.\n",
        "enum": [
          "PENDING",
          "APPROVED",
          "REJECTED",
          "COMPLETED",
          "CANCELLED"
        ]
      },
      "TransferInitiateRequest": {
        "type": "object",
        "required": [
          "asset",
          "amount",
          "originator",
          "beneficiary"
        ],
        "description": "**At least one** of `travelAddress`, top-level `email`, or\n`beneficiary.email` must be provided.\n\n- If `travelAddress` is set → `OPEN_VASP` flow.\n- Otherwise an `EMAIL` flow is used and TRP emails the\n  beneficiary.\n",
        "properties": {
          "travelAddress": {
            "type": "string",
            "minLength": 1,
            "maxLength": 200,
            "description": "Encoded Travel Address routing the request to the beneficiary VASP."
          },
          "email": {
            "type": "string",
            "format": "email",
            "maxLength": 255,
            "description": "Top-level recipient email (used in EMAIL flow if `beneficiary.email` is empty)."
          },
          "asset": {
            "type": "string",
            "minLength": 1,
            "maxLength": 20,
            "example": "BTC"
          },
          "amount": {
            "type": "string",
            "minLength": 1,
            "maxLength": 20,
            "example": "0.05",
            "description": "Asset amount as a decimal string (e.g. `0.05`). The\nbackend validates only the length range (`1..20`)\non this endpoint — pattern validation kicks in on\nthe `transfers/inquiry` callback.\n"
          },
          "callback": {
            "type": "string",
            "pattern": "^https?:\\/\\/.+$",
            "description": "HTTPS callback URL that will receive `TRANSFER_RESOLUTION` updates."
          },
          "dti": {
            "type": "string",
            "maxLength": 50,
            "description": "Digital Token Identifier (ISO 24165), e.g. `4H95J0R2X`."
          },
          "originator": {
            "$ref": "#/components/schemas/Applicant"
          },
          "beneficiary": {
            "$ref": "#/components/schemas/Applicant"
          }
        }
      },
      "TransferInitiateResponse": {
        "type": "object",
        "properties": {
          "status": {
            "type": "boolean",
            "const": true
          },
          "data": {
            "type": "object",
            "properties": {
              "transferId": {
                "type": "string",
                "format": "uuid"
              },
              "status": {
                "$ref": "#/components/schemas/TransferStatus"
              },
              "createdAt": {
                "type": "string",
                "format": "date-time"
              }
            }
          }
        }
      },
      "TravelAddressGenerateNatural": {
        "title": "Natural person",
        "description": "Generate a Travel Address for a **natural person** (физ. лицо).",
        "type": "object",
        "required": [
          "personType",
          "firstName",
          "lastName"
        ],
        "properties": {
          "personType": {
            "type": "string",
            "enum": [
              "natural"
            ],
            "default": "natural"
          },
          "firstName": {
            "type": "string",
            "maxLength": 100,
            "example": "Alice"
          },
          "lastName": {
            "type": "string",
            "maxLength": 100,
            "example": "Liddell"
          },
          "externalId": {
            "type": "string",
            "minLength": 1,
            "maxLength": 255
          },
          "walletAddress": {
            "type": "string",
            "minLength": 1,
            "maxLength": 100,
            "pattern": "[a-fA-F0-9]",
            "description": "Beneficiary wallet address. Must contain at least one\nhex character (validator enforces `/[a-fA-F0-9]/`).\n"
          },
          "isSelfHosted": {
            "type": "boolean"
          }
        }
      },
      "TravelAddressGenerateLegal": {
        "title": "Legal entity",
        "description": "Generate a Travel Address for a **legal entity** (юр. лицо).",
        "type": "object",
        "required": [
          "personType",
          "legalName"
        ],
        "properties": {
          "personType": {
            "type": "string",
            "enum": [
              "legal"
            ]
          },
          "legalName": {
            "type": "string",
            "maxLength": 100,
            "example": "Wonderland Holdings Ltd"
          },
          "firstName": {
            "type": "string",
            "maxLength": 100,
            "description": "Optional contact-person first name."
          },
          "lastName": {
            "type": "string",
            "maxLength": 100,
            "description": "Optional contact-person last name."
          },
          "externalId": {
            "type": "string",
            "minLength": 1,
            "maxLength": 255
          },
          "walletAddress": {
            "type": "string",
            "minLength": 1,
            "maxLength": 100,
            "pattern": "[a-fA-F0-9]",
            "description": "Beneficiary wallet address. Must contain at least one\nhex character (validator enforces `/[a-fA-F0-9]/`).\n"
          },
          "isSelfHosted": {
            "type": "boolean"
          }
        }
      },
      "TravelAddressGenerateRequest": {
        "description": "Provide either `firstName`/`lastName` for natural persons,\nor `legalName` for legal entities. **Use the `personType`\nswitcher** in the right panel to toggle between the two.\n",
        "oneOf": [
          {
            "$ref": "#/components/schemas/TravelAddressGenerateNatural"
          },
          {
            "$ref": "#/components/schemas/TravelAddressGenerateLegal"
          }
        ],
        "discriminator": {
          "propertyName": "personType",
          "mapping": {
            "natural": "#/components/schemas/TravelAddressGenerateNatural",
            "legal": "#/components/schemas/TravelAddressGenerateLegal"
          }
        }
      },
      "TravelAddressGenerateResponse": {
        "type": "object",
        "properties": {
          "status": {
            "type": "boolean",
            "const": true
          },
          "data": {
            "type": "object",
            "properties": {
              "travelAddress": {
                "type": "string",
                "description": "URL-safe encoded Travel Address (e.g. `ta1q…`)."
              }
            }
          }
        }
      },
      "TravelAddressDecodeRequest": {
        "type": "object",
        "required": [
          "travelAddress"
        ],
        "properties": {
          "travelAddress": {
            "type": "string",
            "minLength": 1,
            "maxLength": 200,
            "pattern": "[a-fA-F0-9]",
            "description": "Encoded Travel Address. Must contain at least one hex\ncharacter (validator enforces `/[a-fA-F0-9]/`).\n"
          }
        }
      },
      "TravelAddressDecodeResponse": {
        "type": "object",
        "properties": {
          "status": {
            "type": "boolean",
            "const": true
          },
          "data": {
            "type": "object",
            "properties": {
              "parsedUrl": {
                "type": "string",
                "description": "Decoded HTTP URL that TRP will route the inquiry to.",
                "example": "example.com/transfers/inquiry/0193abc4-1234-...?t=i"
              }
            }
          }
        }
      },
      "TransferInquiryRequest": {
        "type": "object",
        "required": [
          "asset",
          "amount",
          "IVMS101",
          "callback"
        ],
        "properties": {
          "asset": {
            "type": "string",
            "minLength": 1,
            "maxLength": 20
          },
          "amount": {
            "type": "string",
            "minLength": 1,
            "maxLength": 50,
            "pattern": "^\\d+(\\.\\d+)?$"
          },
          "IVMS101": {
            "$ref": "#/components/schemas/IVMS101Originator"
          },
          "callback": {
            "type": "string",
            "pattern": "^https?:\\/\\/.+$"
          }
        }
      },
      "InquiryResolutionRequest": {
        "type": "object",
        "required": [
          "approved"
        ],
        "properties": {
          "approved": {
            "description": "Either a plain wallet address string, or a structured\nobject with an `address` and an optional `callback`\noverride.\n",
            "oneOf": [
              {
                "type": "string",
                "maxLength": 100,
                "example": "bc1qxy2…"
              },
              {
                "type": "object",
                "required": [
                  "address"
                ],
                "properties": {
                  "address": {
                    "type": "string",
                    "maxLength": 100
                  },
                  "callback": {
                    "type": "string",
                    "maxLength": 100
                  }
                }
              }
            ]
          },
          "callback": {
            "type": "string",
            "pattern": "^https?:\\/\\/.+$",
            "description": "Optional override for the next callback URL."
          }
        }
      },
      "TransferTxIdRequest": {
        "type": "object",
        "required": [
          "txId"
        ],
        "properties": {
          "txId": {
            "type": "string",
            "minLength": 1,
            "maxLength": 255
          }
        }
      },
      "TransferConfirmationRequest": {
        "$ref": "#/components/schemas/TransferTxIdRequest"
      },
      "TransferComment": {
        "type": "object",
        "properties": {
          "id": {
            "type": "integer"
          },
          "text": {
            "type": "string"
          },
          "createdAt": {
            "type": "string",
            "format": "date-time"
          },
          "updatedAt": {
            "type": "string",
            "format": "date-time"
          },
          "author": {
            "type": "object",
            "properties": {
              "id": {
                "type": "integer"
              },
              "name": {
                "type": "string"
              },
              "domain": {
                "type": "string"
              }
            }
          },
          "canEdit": {
            "type": "boolean"
          },
          "attachments": {
            "type": "array",
            "items": {
              "$ref": "#/components/schemas/TransferCommentAttachment"
            }
          }
        }
      },
      "TransferCommentAttachment": {
        "type": "object",
        "properties": {
          "id": {
            "type": "string"
          },
          "originalName": {
            "type": "string"
          },
          "mimeType": {
            "type": "string"
          },
          "sizeBytes": {
            "type": "string"
          },
          "bucket": {
            "type": "string"
          },
          "objectKey": {
            "type": "string"
          },
          "downloadUrl": {
            "type": "string",
            "description": "Short-lived signed URL to download the attachment."
          }
        }
      },
      "TransferCommentCreateMultipart": {
        "type": "object",
        "required": [
          "text"
        ],
        "properties": {
          "text": {
            "type": "string",
            "minLength": 1,
            "maxLength": 2000
          },
          "files": {
            "type": "array",
            "maxItems": 5,
            "items": {
              "type": "string",
              "format": "binary"
            },
            "description": "Up to 5 images / PDF attachments (≤ 50 MB each)."
          }
        }
      },
      "TransferCommentUpdateRequest": {
        "type": "object",
        "required": [
          "text"
        ],
        "properties": {
          "text": {
            "type": "string",
            "minLength": 1,
            "maxLength": 2000
          }
        }
      },
      "Vasp": {
        "type": "object",
        "properties": {
          "id": {
            "type": "integer"
          },
          "directoryId": {
            "type": "string"
          },
          "name": {
            "type": "string"
          },
          "email": {
            "type": "string",
            "format": "email"
          },
          "commonName": {
            "type": "string"
          },
          "website": {
            "type": "string"
          },
          "endpoint": {
            "type": "string"
          },
          "country": {
            "type": "string"
          },
          "businessCategory": {
            "type": "string"
          },
          "vaspCategories": {
            "type": "array",
            "items": {
              "type": "string"
            }
          },
          "lei": {
            "type": "string"
          },
          "registeredDirectory": {
            "type": "string"
          },
          "protocol": {
            "type": "string"
          },
          "source": {
            "type": "string"
          },
          "createdAt": {
            "type": "string",
            "format": "date-time"
          },
          "updatedAt": {
            "type": "string",
            "format": "date-time"
          }
        }
      },
      "VaspSearchRequest": {
        "type": "object",
        "description": "At least one search parameter is required. `domain` matches\nagainst both `website` and `endpoint`; the rest combine with\nlogical **AND**.\n",
        "properties": {
          "domain": {
            "type": "string"
          },
          "name": {
            "type": "string"
          },
          "email": {
            "type": "string"
          },
          "commonName": {
            "type": "string"
          },
          "directoryId": {
            "type": "string"
          },
          "lei": {
            "type": "string"
          }
        }
      },
      "VaspIdentity": {
        "type": "object",
        "properties": {
          "name": {
            "type": "string"
          },
          "lei": {
            "type": "string"
          },
          "x509": {
            "type": "string",
            "description": "PEM-encoded x509 public key."
          }
        }
      },
      "HealthResponse": {
        "type": "object",
        "properties": {
          "status": {
            "type": "boolean",
            "const": true
          },
          "data": {
            "type": "object",
            "properties": {
              "version": {
                "type": "string",
                "example": "26.4.0"
              },
              "timestamp": {
                "type": "string",
                "format": "date-time"
              }
            }
          }
        }
      },
      "InquiryCallbackBody": {
        "type": "object",
        "description": "Body of an `INQUIRY` callback.",
        "properties": {
          "asset": {
            "type": "string"
          },
          "amount": {
            "type": "string"
          },
          "callback": {
            "type": "string",
            "description": "URL that the beneficiary VASP must call back with `inquiryResolution`."
          },
          "IVMS101": {
            "$ref": "#/components/schemas/IVMS101Originator"
          }
        }
      },
      "TransferResolutionCallbackBody": {
        "type": "object",
        "description": "Body of a `TRANSFER_RESOLUTION` callback delivered to the originator's `callback` URL.",
        "properties": {
          "transferId": {
            "type": "string",
            "format": "uuid"
          },
          "status": {
            "$ref": "#/components/schemas/TransferStatus"
          },
          "statusComment": {
            "type": [
              "string",
              "null"
            ]
          },
          "callback": {
            "type": "string",
            "description": "URL the originator must POST the `txId` to once broadcast."
          }
        }
      },
      "TransferConfirmationCallbackBody": {
        "type": "object",
        "properties": {
          "txid": {
            "type": "string",
            "description": "Final on-chain transaction id."
          }
        }
      }
    },
    "responses": {
      "BadRequest": {
        "description": "Validation error.",
        "content": {
          "application/json": {
            "schema": {
              "$ref": "#/components/schemas/ErrorEnvelope"
            },
            "examples": {
              "validation": {
                "value": {
                  "status": false,
                  "errors": [
                    {
                      "code": "VALIDATION_ERROR",
                      "message": "Asset is required"
                    }
                  ]
                }
              }
            }
          }
        }
      },
      "Unauthorized": {
        "description": "Missing or invalid API key / JWT.",
        "content": {
          "application/json": {
            "schema": {
              "$ref": "#/components/schemas/ErrorEnvelope"
            }
          }
        }
      },
      "Forbidden": {
        "description": "API key inactive, expired, or customer disabled.",
        "content": {
          "application/json": {
            "schema": {
              "$ref": "#/components/schemas/ErrorEnvelope"
            }
          }
        }
      },
      "NotFound": {
        "description": "Resource not found.",
        "content": {
          "application/json": {
            "schema": {
              "$ref": "#/components/schemas/ErrorEnvelope"
            },
            "examples": {
              "transferNotFound": {
                "value": {
                  "status": false,
                  "errors": [
                    {
                      "code": "TRANSFER_NOT_FOUND",
                      "message": "Transfer was not found"
                    }
                  ]
                }
              }
            }
          }
        }
      },
      "InternalError": {
        "description": "Internal server error.",
        "content": {
          "application/json": {
            "schema": {
              "$ref": "#/components/schemas/ErrorEnvelope"
            }
          }
        }
      }
    }
  },
  "security": [
    {
      "BearerAuth": []
    }
  ],
  "webhooks": {
    "inquiryCallback": {
      "post": {
        "tags": [
          "Callbacks"
        ],
        "operationId": "webhookInquiry",
        "summary": "INQUIRY callback",
        "description": "Delivered to the beneficiary VASP after a successful\n`transfers/initiate` in `OPEN_VASP` flow. Respond with **2xx**\nto acknowledge; non-2xx responses are retried per the\nschedule in [Callbacks](#tag/Callbacks).\n",
        "requestBody": {
          "required": true,
          "content": {
            "application/json": {
              "schema": {
                "$ref": "#/components/schemas/InquiryCallbackBody"
              }
            }
          }
        },
        "responses": {
          "200": {
            "description": "Acknowledged."
          }
        }
      }
    },
    "inquiryResolutionCallback": {
      "post": {
        "tags": [
          "Callbacks"
        ],
        "operationId": "webhookInquiryResolution",
        "summary": "INQUIRY_RESOLUTION callback",
        "description": "Delivered to the originator VASP once the beneficiary VASP resolves the inquiry.",
        "requestBody": {
          "required": true,
          "content": {
            "application/json": {
              "schema": {
                "$ref": "#/components/schemas/InquiryResolutionRequest"
              }
            }
          }
        },
        "responses": {
          "200": {
            "description": "Acknowledged."
          }
        }
      }
    },
    "transferResolutionCallback": {
      "post": {
        "tags": [
          "Callbacks"
        ],
        "operationId": "webhookTransferResolution",
        "summary": "TRANSFER_RESOLUTION callback",
        "description": "Delivered to the originator's `callback` URL once the\nbeneficiary VASP approves the transfer. The body includes a\n`callback` field — the URL to which the originator must\nlater `POST` the on-chain `txId`.\n",
        "requestBody": {
          "required": true,
          "content": {
            "application/json": {
              "schema": {
                "$ref": "#/components/schemas/TransferResolutionCallbackBody"
              }
            }
          }
        },
        "responses": {
          "200": {
            "description": "Acknowledged."
          }
        }
      }
    },
    "transferConfirmationCallback": {
      "post": {
        "tags": [
          "Callbacks"
        ],
        "operationId": "webhookTransferConfirmation",
        "summary": "TRANSFER_CONFIRMATION callback",
        "description": "Delivered to the beneficiary's stored confirmation URL once\nthe originator submits `txId`. Final stage of the Travel\nRule exchange.\n",
        "requestBody": {
          "required": true,
          "content": {
            "application/json": {
              "schema": {
                "$ref": "#/components/schemas/TransferConfirmationCallbackBody"
              }
            }
          }
        },
        "responses": {
          "200": {
            "description": "Acknowledged."
          }
        }
      }
    }
  },
  "paths": {
    "/identity": {
      "get": {
        "tags": [
          "Identity"
        ],
        "operationId": "getVaspIdentity",
        "summary": "Public VASP identity",
        "description": "Returns public identity attributes of the VASP that owns the\nrequested hostname. Used by counterparties to verify our\nlegal name, LEI and signing key.\n",
        "security": [],
        "responses": {
          "200": {
            "description": "VASP identity payload.",
            "content": {
              "application/json": {
                "schema": {
                  "$ref": "#/components/schemas/VaspIdentity"
                },
                "example": {
                  "name": "KYCAID TRP",
                  "lei": "529900T8BM49AURSDO55",
                  "x509": "-----BEGIN PUBLIC KEY-----\nMIIBIjANBgkqh…\n-----END PUBLIC KEY-----\n"
                }
              }
            }
          },
          "404": {
            "$ref": "#/components/responses/NotFound"
          }
        }
      }
    },
    "/health": {
      "get": {
        "tags": [
          "Identity"
        ],
        "operationId": "getHealth",
        "summary": "Health check",
        "description": "Liveness probe. Returns the running build version and the server timestamp.",
        "security": [],
        "responses": {
          "200": {
            "description": "Service is up.",
            "content": {
              "application/json": {
                "schema": {
                  "$ref": "#/components/schemas/HealthResponse"
                },
                "example": {
                  "status": true,
                  "data": {
                    "version": "26.4.0",
                    "timestamp": "2026-05-11T08:00:00.000Z"
                  }
                }
              }
            }
          },
          "500": {
            "$ref": "#/components/responses/InternalError"
          }
        }
      }
    },
    "/version.json": {
      "get": {
        "tags": [
          "Identity"
        ],
        "operationId": "getVersionJson",
        "summary": "Build manifest",
        "description": "Returns the contents of `version.json` if present (CI build manifest); otherwise an empty object.",
        "security": [],
        "responses": {
          "200": {
            "description": "Build manifest.",
            "content": {
              "application/json": {
                "schema": {
                  "type": "object",
                  "additionalProperties": true
                }
              }
            }
          },
          "500": {
            "$ref": "#/components/responses/InternalError"
          }
        }
      }
    },
    "/auth/token": {
      "post": {
        "tags": [
          "Auth"
        ],
        "operationId": "createToken",
        "summary": "Generate JWT access token",
        "description": "Exchanges your API Key for a short-lived **JWT access token**\n(`tokenType: Bearer`, TTL ≈ 1 hour).\n\nSend the API Key in **one** of these headers:\n\n- `x-api-key: <YOUR_API_KEY>`, or\n- `Authorization: ApiKey <YOUR_API_KEY>`.\n\nThe response includes both the absolute expiration timestamp\nand the relative TTL in seconds.\n",
        "security": [
          {
            "ApiKey": []
          },
          {
            "ApiKeyAuth": []
          }
        ],
        "responses": {
          "200": {
            "description": "A new JWT access token.",
            "content": {
              "application/json": {
                "schema": {
                  "type": "object",
                  "properties": {
                    "accessToken": {
                      "type": "string",
                      "description": "JWT signed with the TRP service key."
                    },
                    "expiresIn": {
                      "type": "integer",
                      "description": "TTL in seconds.",
                      "example": 3600
                    },
                    "expiresAt": {
                      "type": "string",
                      "format": "date-time"
                    },
                    "tokenType": {
                      "type": "string",
                      "const": "Bearer"
                    }
                  }
                },
                "example": {
                  "accessToken": "eyJhbGciOiJFZERTQSIsInR5cCI6IkpXVCJ9…",
                  "expiresIn": 3600,
                  "expiresAt": "2026-05-11T10:00:00.000Z",
                  "tokenType": "Bearer"
                }
              }
            }
          },
          "401": {
            "$ref": "#/components/responses/Unauthorized"
          },
          "403": {
            "$ref": "#/components/responses/Forbidden"
          }
        }
      }
    },
    "/travel-address/generate": {
      "post": {
        "tags": [
          "Address"
        ],
        "operationId": "generateTravelAddress",
        "summary": "Generate Travel Address",
        "description": "Creates (or upserts) a beneficiary applicant on **your** VASP\nand returns a routable **Travel Address** that other VASPs\ncan use to deliver IVMS101 beneficiary data back to you.\n\nThe Travel Address encodes:\n\n- your VASP `domain`,\n- the beneficiary's `publicId`,\n- the lookup path `/transfers/inquiry/{beneficiaryPublicId}`,\n- the `t=i` query (inquiry routing hint).\n\nIf an applicant with the same `externalId` or\n(`firstName`,`lastName`,`walletAddress`) tuple already\nexists, it is reused — Travel Addresses are stable per\nbeneficiary.\n",
        "requestBody": {
          "required": true,
          "content": {
            "application/json": {
              "schema": {
                "$ref": "#/components/schemas/TravelAddressGenerateRequest"
              },
              "examples": {
                "naturalPersonMinimal": {
                  "summary": "Natural person — minimal",
                  "value": {
                    "personType": "natural",
                    "firstName": "Alice",
                    "lastName": "Liddell"
                  }
                },
                "naturalPersonFull": {
                  "summary": "Natural person — all accepted fields",
                  "value": {
                    "personType": "natural",
                    "firstName": "Alice",
                    "lastName": "Liddell",
                    "externalId": "cust_123",
                    "walletAddress": "bc1qxy2kgdygjrsqtzq2n0yrf2493p83kkfjhx0wlh",
                    "isSelfHosted": false
                  }
                },
                "naturalSelfHosted": {
                  "summary": "Natural person — self-hosted wallet",
                  "value": {
                    "personType": "natural",
                    "firstName": "Bob",
                    "lastName": "Marley",
                    "externalId": "cust_124",
                    "walletAddress": "0xabcdef1234567890abcdef1234567890abcdef12",
                    "isSelfHosted": true
                  }
                },
                "legalEntityFull": {
                  "summary": "Legal entity — all accepted fields",
                  "value": {
                    "personType": "legal",
                    "legalName": "Wonderland Holdings Ltd",
                    "firstName": "Alice",
                    "lastName": "Liddell",
                    "externalId": "org_001",
                    "walletAddress": "0xfeedbeefcafebabe1234567890abcdef12345678",
                    "isSelfHosted": false
                  }
                }
              }
            }
          }
        },
        "responses": {
          "200": {
            "description": "A unique Travel Address.",
            "content": {
              "application/json": {
                "schema": {
                  "$ref": "#/components/schemas/TravelAddressGenerateResponse"
                },
                "example": {
                  "status": true,
                  "data": {
                    "travelAddress": "ta1qexamplebeneficiaryvasp1ahjk23"
                  }
                }
              }
            }
          },
          "400": {
            "$ref": "#/components/responses/BadRequest"
          },
          "401": {
            "$ref": "#/components/responses/Unauthorized"
          }
        }
      }
    },
    "/travel-address/decode": {
      "post": {
        "tags": [
          "Address"
        ],
        "operationId": "decodeTravelAddress",
        "summary": "Decode Travel Address",
        "description": "Verifies and decodes a Travel Address back into its target\nURL. Use this client-side to preview where a transfer will\nbe routed before initiating it.\n",
        "requestBody": {
          "required": true,
          "content": {
            "application/json": {
              "schema": {
                "$ref": "#/components/schemas/TravelAddressDecodeRequest"
              },
              "example": {
                "travelAddress": "ta1qexamplebeneficiaryvasp1ahjk23"
              }
            }
          }
        },
        "responses": {
          "200": {
            "description": "Decoded target URL.",
            "content": {
              "application/json": {
                "schema": {
                  "$ref": "#/components/schemas/TravelAddressDecodeResponse"
                },
                "example": {
                  "status": true,
                  "data": {
                    "parsedUrl": "example.com/transfers/inquiry/0193abc4-1234-7890-abcd-ef1234567890?t=i"
                  }
                }
              }
            }
          },
          "400": {
            "description": "Invalid Travel Address format.",
            "content": {
              "application/json": {
                "schema": {
                  "$ref": "#/components/schemas/ErrorEnvelope"
                },
                "example": {
                  "status": false,
                  "errors": [
                    {
                      "code": "INVALID_TRAVEL_ADDRESS",
                      "message": "Invalid travel address format"
                    }
                  ]
                }
              }
            }
          },
          "401": {
            "$ref": "#/components/responses/Unauthorized"
          }
        }
      }
    },
    "/transfers/initiate": {
      "post": {
        "tags": [
          "Transfer"
        ],
        "operationId": "initiateTransfer",
        "summary": "Initiate Travel Rule transfer",
        "description": "**Main Travel Rule operation.** Submits originator and\nbeneficiary applicants, asset, amount, and optional\nrouting info (`travelAddress` or `email`).\n\n### Flow selection\n\n| Provided fields | Resulting `flow` |\n|-----------------|------------------|\n| `travelAddress` | `OPEN_VASP` |\n| only `email` (top-level or `beneficiary.email`) | `EMAIL` |\n\n### What TRP does next\n\n- Persists the transfer with `APPROVED` originator status.\n- Resolves originator and beneficiary VASP IDs.\n- In `OPEN_VASP` flow, queues an `INQUIRY` callback to the\n  beneficiary VASP endpoint (extracted from the Travel\n  Address).\n- In `EMAIL` flow, sends a confirmation email and flips\n  `isEmailDelivered` once Mailjet acks.\n",
        "requestBody": {
          "required": true,
          "content": {
            "application/json": {
              "schema": {
                "$ref": "#/components/schemas/TransferInitiateRequest"
              },
              "examples": {
                "naturalToNaturalFull": {
                  "summary": "Natural → Natural — full payload (every accepted field)",
                  "description": "Demonstrates every field the backend accepts on\n`originator` and `beneficiary` for the natural-person\ncase. Pare this down to the minimum your flow needs.\n",
                  "value": {
                    "travelAddress": "ta1qexamplebeneficiaryvasp1ahjk23",
                    "asset": "BTC",
                    "amount": "0.05",
                    "callback": "https://my-vasp.com/trp/callbacks",
                    "dti": "4H95J0R2X",
                    "originator": {
                      "personType": "natural",
                      "externalId": "cust_42",
                      "firstName": "Bob",
                      "lastName": "Marley",
                      "middleName": "Robert",
                      "dateOfBirth": "1985-02-06",
                      "placeOfBirth": "Kingston",
                      "addressType": "HOME",
                      "streetName": "Hope Road",
                      "buildingNumber": "56",
                      "buildingName": "Tuff Gong House",
                      "postCode": "KIN 6",
                      "townName": "Kingston",
                      "country": "JM",
                      "countrySubDivision": "Saint Andrew",
                      "nationalId": "A12345678",
                      "nationalIdType": "CCPT",
                      "countryOfIssue": "JM",
                      "accountNumber": "ACC-ORIG-001",
                      "walletAddress": "bc1qoriginatoraddressxx",
                      "email": "bob@example.com",
                      "isSelfHosted": false,
                      "vasp": {
                        "name": "My VASP Inc."
                      }
                    },
                    "beneficiary": {
                      "personType": "natural",
                      "externalId": "cust_77",
                      "firstName": "Alice",
                      "lastName": "Liddell",
                      "middleName": "Pleasance",
                      "dateOfBirth": "1990-01-15",
                      "placeOfBirth": "London",
                      "addressType": "HOME",
                      "streetName": "Baker Street",
                      "buildingNumber": "221B",
                      "postCode": "NW1 6XE",
                      "townName": "London",
                      "country": "GB",
                      "countrySubDivision": "England",
                      "nationalId": "PB1234567",
                      "nationalIdType": "CCPT",
                      "countryOfIssue": "GB",
                      "walletAddress": "bc1qbeneficiaryaddressxx",
                      "email": "alice@example.com",
                      "isSelfHosted": false,
                      "vasp": {
                        "name": "Wonderland Exchange"
                      }
                    }
                  }
                },
                "legalToLegalFull": {
                  "summary": "Legal → Legal — full payload (юр → юр)",
                  "description": "Both parties are legal entities. `legalName` is the\nrequired field; LEI is conventionally carried in\n`nationalId` with `nationalIdType: LEIX`.\n",
                  "value": {
                    "travelAddress": "ta1qexamplebeneficiaryvasp1ahjk23",
                    "asset": "USDT",
                    "amount": "50000",
                    "callback": "https://my-vasp.com/trp/callbacks",
                    "dti": "4H95J0R2X",
                    "originator": {
                      "personType": "legal",
                      "externalId": "org_orig_1",
                      "legalName": "Originator Holdings Ltd",
                      "firstName": "Bob",
                      "lastName": "Marley",
                      "addressType": "BIZZ",
                      "streetName": "King William St",
                      "buildingNumber": "1",
                      "postCode": "EC4N 7AF",
                      "townName": "London",
                      "country": "GB",
                      "nationalId": "529900T8BM49AURSDO55",
                      "nationalIdType": "LEIX",
                      "countryOfIssue": "GB",
                      "accountNumber": "ACC-ORIG-CORP",
                      "walletAddress": "0xoriginatororgaddress01234567890abcdef0",
                      "email": "ops@originator.com",
                      "isSelfHosted": false,
                      "vasp": {
                        "name": "Originator VASP Ltd"
                      }
                    },
                    "beneficiary": {
                      "personType": "legal",
                      "externalId": "org_bene_1",
                      "legalName": "Wonderland Holdings Ltd",
                      "addressType": "BIZZ",
                      "streetName": "Marina Boulevard",
                      "buildingNumber": "10",
                      "postCode": "018983",
                      "townName": "Singapore",
                      "country": "SG",
                      "nationalId": "254900EXAMPLEBENE7GH7",
                      "nationalIdType": "LEIX",
                      "countryOfIssue": "SG",
                      "walletAddress": "0xbeneficiaryorgaddress01234567890abcde",
                      "isSelfHosted": false,
                      "vasp": {
                        "name": "Wonderland Exchange"
                      }
                    }
                  }
                },
                "naturalToLegal": {
                  "summary": "Natural → Legal (физ → юр)",
                  "description": "Mixed flow — individual sending funds to a corporate beneficiary.",
                  "value": {
                    "travelAddress": "ta1qexamplebeneficiaryvasp1ahjk23",
                    "asset": "ETH",
                    "amount": "12.5",
                    "callback": "https://my-vasp.com/trp/callbacks",
                    "originator": {
                      "personType": "natural",
                      "firstName": "Bob",
                      "lastName": "Marley",
                      "dateOfBirth": "1985-02-06",
                      "country": "JM",
                      "walletAddress": "0xoriginatoraddress01234567890abcdef0abc",
                      "email": "bob@example.com"
                    },
                    "beneficiary": {
                      "personType": "legal",
                      "legalName": "Wonderland Holdings Ltd",
                      "country": "SG",
                      "nationalId": "254900EXAMPLEBENE7GH7",
                      "nationalIdType": "LEIX",
                      "walletAddress": "0xbeneficiaryorgaddress01234567890abcde",
                      "vasp": {
                        "name": "Wonderland Exchange"
                      }
                    }
                  }
                },
                "emailFlowSelfHosted": {
                  "summary": "EMAIL flow (self-hosted natural beneficiary)",
                  "description": "No Travel Address — TRP emails the beneficiary for self-attestation.",
                  "value": {
                    "asset": "ETH",
                    "amount": "1.25",
                    "callback": "https://my-vasp.com/trp/callbacks",
                    "originator": {
                      "personType": "natural",
                      "firstName": "Bob",
                      "lastName": "Marley",
                      "country": "JM",
                      "walletAddress": "0xoriginatoraddress"
                    },
                    "beneficiary": {
                      "personType": "natural",
                      "firstName": "Alice",
                      "lastName": "Liddell",
                      "email": "alice@example.com",
                      "walletAddress": "0xbeneficiaryaddress",
                      "isSelfHosted": true
                    }
                  }
                }
              }
            }
          }
        },
        "responses": {
          "200": {
            "description": "Transfer accepted.",
            "content": {
              "application/json": {
                "schema": {
                  "$ref": "#/components/schemas/TransferInitiateResponse"
                },
                "example": {
                  "status": true,
                  "data": {
                    "transferId": "0193abc4-1234-7890-abcd-ef1234567890",
                    "status": "PENDING",
                    "createdAt": "2026-05-11T08:00:00.000Z"
                  }
                }
              }
            }
          },
          "400": {
            "$ref": "#/components/responses/BadRequest"
          },
          "401": {
            "$ref": "#/components/responses/Unauthorized"
          }
        }
      }
    },
    "/transfers/inquiry/{beneficiaryPublicId}": {
      "post": {
        "tags": [
          "Compliance"
        ],
        "operationId": "transferInquiry",
        "summary": "Receive inquiry from another VASP",
        "description": "**Inbound** endpoint. Called by an *originator* VASP (via\nTravel Address routing) to ask whether our VASP holds the\nbeneficiary identified by `beneficiaryPublicId`.\n\nIf the beneficiary exists, TRP:\n\n1. creates / approves a corresponding `Transfer` record,\n2. queues an `INQUIRY_RESOLUTION` callback to the\n   caller-provided `callback` URL with the beneficiary\n   wallet address.\n\nAuthentication is **not** required — request integrity is\nguaranteed by the originator's signed `x-data-integrity`\nheader and by the inclusion of the encoded Travel Address.\n",
        "security": [],
        "parameters": [
          {
            "$ref": "#/components/parameters/BeneficiaryPublicId"
          },
          {
            "$ref": "#/components/parameters/ApiVersionHeader"
          },
          {
            "$ref": "#/components/parameters/RequestIdentifierHeader"
          }
        ],
        "requestBody": {
          "required": true,
          "content": {
            "application/json": {
              "schema": {
                "$ref": "#/components/schemas/TransferInquiryRequest"
              },
              "example": {
                "asset": "BTC",
                "amount": "0.05",
                "IVMS101": {
                  "originator": {
                    "originatorPersons": [
                      {
                        "naturalPerson": {
                          "name": {
                            "nameIdentifier": [
                              {
                                "primaryIdentifier": "Marley",
                                "secondaryIdentifier": "Bob",
                                "nameIdentifierType": "LEGL"
                              }
                            ]
                          }
                        }
                      }
                    ]
                  },
                  "originatingVASP": {
                    "originatingVASP": {
                      "legalPerson": {
                        "name": {
                          "nameIdentifier": [
                            {
                              "legalPersonName": "Originator VASP Ltd",
                              "legalPersonNameIdentifierType": "LEGL"
                            }
                          ]
                        },
                        "nationalIdentification": {
                          "nationalIdentifier": "529900T8BM49AURSDO55",
                          "nationalIdentifierType": "LEIX"
                        }
                      }
                    }
                  }
                },
                "callback": "https://originator-vasp.com/trp/inquiry-resolution?q=0193abc4-…"
              }
            }
          }
        },
        "responses": {
          "200": {
            "description": "Inquiry accepted; an `INQUIRY_RESOLUTION` callback will follow.",
            "content": {
              "application/json": {
                "schema": {
                  "$ref": "#/components/schemas/SuccessEnvelope"
                },
                "example": {
                  "status": true
                }
              }
            }
          },
          "400": {
            "$ref": "#/components/responses/BadRequest"
          }
        }
      }
    },
    "/transfers/inquiryResolution": {
      "post": {
        "tags": [
          "Compliance"
        ],
        "operationId": "transferInquiryResolution",
        "summary": "Receive inquiry resolution",
        "description": "**Inbound** endpoint. Called by the *beneficiary* VASP to\nrespond to a previously issued `INQUIRY` callback.\n\nThe request locates the transfer by the `q` query\nparameter (transfer `publicId`), stores the approved\nwallet address against the beneficiary applicant, marks\nthe transfer as `APPROVED`, and queues a\n`TRANSFER_RESOLUTION` callback to the originator VASP.\n",
        "security": [],
        "parameters": [
          {
            "$ref": "#/components/parameters/TransferQueryId"
          }
        ],
        "requestBody": {
          "required": true,
          "content": {
            "application/json": {
              "schema": {
                "$ref": "#/components/schemas/InquiryResolutionRequest"
              },
              "examples": {
                "plainAddress": {
                  "summary": "Plain wallet string",
                  "value": {
                    "approved": "bc1qbeneficiaryaddressxx",
                    "callback": "https://originator-vasp.com/trp/transfer-resolution"
                  }
                },
                "structured": {
                  "summary": "Structured object with callback override",
                  "value": {
                    "approved": {
                      "address": "bc1qbeneficiaryaddressxx",
                      "callback": "bc1qbeneficiaryaddressxx"
                    },
                    "callback": "https://originator-vasp.com/trp/transfer-resolution"
                  }
                }
              }
            }
          }
        },
        "responses": {
          "200": {
            "description": "Resolution stored.",
            "content": {
              "application/json": {
                "schema": {
                  "$ref": "#/components/schemas/SuccessEnvelope"
                }
              }
            }
          },
          "404": {
            "$ref": "#/components/responses/NotFound"
          }
        }
      }
    },
    "/transfers/txId": {
      "post": {
        "tags": [
          "Compliance"
        ],
        "operationId": "transferTxId",
        "summary": "Broadcast on-chain txId",
        "description": "Called by the **originator** VASP once the on-chain\ntransaction has been broadcast. Stores `txId` on the\ntransfer and forwards a `TRANSFER_CONFIRMATION` callback to\nthe beneficiary's stored confirmation URL.\n",
        "parameters": [
          {
            "$ref": "#/components/parameters/TransferQueryId"
          }
        ],
        "requestBody": {
          "required": true,
          "content": {
            "application/json": {
              "schema": {
                "$ref": "#/components/schemas/TransferTxIdRequest"
              },
              "example": {
                "txId": "5b1c8f7d9a4e7c3b…"
              }
            }
          }
        },
        "responses": {
          "200": {
            "description": "TxId stored and confirmation queued.",
            "content": {
              "application/json": {
                "schema": {
                  "$ref": "#/components/schemas/SuccessEnvelope"
                }
              }
            }
          },
          "401": {
            "$ref": "#/components/responses/Unauthorized"
          },
          "404": {
            "description": "Transfer or confirmation callback not found.",
            "content": {
              "application/json": {
                "schema": {
                  "$ref": "#/components/schemas/ErrorEnvelope"
                },
                "examples": {
                  "transferMissing": {
                    "value": {
                      "status": false,
                      "errors": [
                        {
                          "code": "TRANSFER_NOT_FOUND",
                          "message": "Transfer was not found"
                        }
                      ]
                    }
                  },
                  "callbackMissing": {
                    "value": {
                      "status": false,
                      "errors": [
                        {
                          "code": "CALLBACK_NOT_FOUND",
                          "message": "Transfer confirmation callback was not found"
                        }
                      ]
                    }
                  }
                }
              }
            }
          }
        }
      }
    },
    "/transfers/confirmation": {
      "post": {
        "tags": [
          "Compliance"
        ],
        "operationId": "transferConfirmation",
        "summary": "Receive transfer confirmation",
        "description": "**Inbound** endpoint that receives the final\n`TRANSFER_CONFIRMATION` callback. Persists the on-chain\n`txId` against the transfer.\n\nAlso used as the landing endpoint for self-hosted (EMAIL\nflow) beneficiaries when they click the confirmation link.\n",
        "security": [],
        "parameters": [
          {
            "$ref": "#/components/parameters/TransferQueryId"
          }
        ],
        "requestBody": {
          "required": true,
          "content": {
            "application/json": {
              "schema": {
                "$ref": "#/components/schemas/TransferConfirmationRequest"
              },
              "example": {
                "txId": "5b1c8f7d9a4e7c3b…"
              }
            }
          }
        },
        "responses": {
          "200": {
            "description": "Confirmation stored.",
            "content": {
              "application/json": {
                "schema": {
                  "$ref": "#/components/schemas/SuccessEnvelope"
                }
              }
            }
          },
          "404": {
            "$ref": "#/components/responses/NotFound"
          }
        }
      }
    },
    "/transfers/{transferPublicId}/comments": {
      "get": {
        "tags": [
          "Transfer"
        ],
        "operationId": "listTransferComments",
        "summary": "List comments on a transfer",
        "description": "Returns every comment attached to the transfer (originator\nand beneficiary VASPs share the same thread). Attachment\ndownloads are signed, short-lived URLs.\n",
        "parameters": [
          {
            "$ref": "#/components/parameters/TransferPublicId"
          }
        ],
        "responses": {
          "200": {
            "description": "List of comments.",
            "content": {
              "application/json": {
                "schema": {
                  "type": "object",
                  "properties": {
                    "status": {
                      "type": "boolean",
                      "const": true
                    },
                    "data": {
                      "type": "array",
                      "items": {
                        "$ref": "#/components/schemas/TransferComment"
                      }
                    }
                  }
                }
              }
            }
          },
          "401": {
            "$ref": "#/components/responses/Unauthorized"
          },
          "404": {
            "$ref": "#/components/responses/NotFound"
          }
        }
      },
      "post": {
        "tags": [
          "Transfer"
        ],
        "operationId": "createTransferComment",
        "summary": "Add a comment (with optional attachments)",
        "description": "Adds a comment to a transfer thread. Up to **5 attachments**\nper call (each ≤ 50 MB). Only images and PDFs are accepted —\nother MIME types are rejected with `Only images and PDF files\nare allowed`.\n",
        "parameters": [
          {
            "$ref": "#/components/parameters/TransferPublicId"
          }
        ],
        "requestBody": {
          "required": true,
          "content": {
            "multipart/form-data": {
              "schema": {
                "$ref": "#/components/schemas/TransferCommentCreateMultipart"
              }
            }
          }
        },
        "responses": {
          "200": {
            "description": "Comment created.",
            "content": {
              "application/json": {
                "schema": {
                  "type": "object",
                  "properties": {
                    "status": {
                      "type": "boolean",
                      "const": true
                    },
                    "data": {
                      "$ref": "#/components/schemas/TransferComment"
                    }
                  }
                }
              }
            }
          },
          "400": {
            "$ref": "#/components/responses/BadRequest"
          },
          "401": {
            "$ref": "#/components/responses/Unauthorized"
          },
          "404": {
            "$ref": "#/components/responses/NotFound"
          }
        }
      }
    },
    "/transfers/{transferPublicId}/comments/{commentId}": {
      "patch": {
        "tags": [
          "Transfer"
        ],
        "operationId": "updateTransferComment",
        "summary": "Edit a comment",
        "description": "Edits a comment **you previously authored**. Other parties'\ncomments can be read but not modified.\n",
        "parameters": [
          {
            "$ref": "#/components/parameters/TransferPublicId"
          },
          {
            "$ref": "#/components/parameters/CommentId"
          }
        ],
        "requestBody": {
          "required": true,
          "content": {
            "application/json": {
              "schema": {
                "$ref": "#/components/schemas/TransferCommentUpdateRequest"
              },
              "example": {
                "text": "Updated comment text."
              }
            }
          }
        },
        "responses": {
          "200": {
            "description": "Comment updated.",
            "content": {
              "application/json": {
                "schema": {
                  "type": "object",
                  "properties": {
                    "status": {
                      "type": "boolean",
                      "const": true
                    },
                    "data": {
                      "$ref": "#/components/schemas/TransferComment"
                    }
                  }
                }
              }
            }
          },
          "400": {
            "$ref": "#/components/responses/BadRequest"
          },
          "401": {
            "$ref": "#/components/responses/Unauthorized"
          },
          "403": {
            "description": "Only the comment author can edit this comment.",
            "content": {
              "application/json": {
                "schema": {
                  "$ref": "#/components/schemas/ErrorEnvelope"
                },
                "example": {
                  "status": false,
                  "errors": [
                    {
                      "code": "FORBIDDEN",
                      "message": "Only the comment author can edit this comment"
                    }
                  ]
                }
              }
            }
          },
          "404": {
            "$ref": "#/components/responses/NotFound"
          }
        }
      }
    },
    "/discovery/vasp": {
      "get": {
        "tags": [
          "Discovery"
        ],
        "operationId": "searchVasp",
        "summary": "Search VASP directory",
        "description": "Returns up to **100** VASPs matching the supplied criteria\n(case-insensitive `contains` match, combined with **AND**).\nAt least one search parameter is required.\n\n> Although this is `GET`, the search payload is read from\n> the **request body** (compatibility with the original TRP\n> registry — bodies on `GET` are tolerated).\n",
        "security": [],
        "requestBody": {
          "required": true,
          "content": {
            "application/json": {
              "schema": {
                "$ref": "#/components/schemas/VaspSearchRequest"
              },
              "examples": {
                "byDomain": {
                  "summary": "By domain",
                  "value": {
                    "domain": "example.com"
                  }
                },
                "byLei": {
                  "summary": "By LEI",
                  "value": {
                    "lei": "529900T8BM49AURSDO55"
                  }
                }
              }
            }
          }
        },
        "responses": {
          "200": {
            "description": "Search results.",
            "content": {
              "application/json": {
                "schema": {
                  "type": "object",
                  "properties": {
                    "status": {
                      "type": "boolean",
                      "const": true
                    },
                    "data": {
                      "type": "object",
                      "properties": {
                        "count": {
                          "type": "integer"
                        },
                        "vasps": {
                          "type": "array",
                          "items": {
                            "$ref": "#/components/schemas/Vasp"
                          }
                        }
                      }
                    }
                  }
                }
              }
            }
          },
          "400": {
            "description": "No search parameters supplied.",
            "content": {
              "application/json": {
                "schema": {
                  "$ref": "#/components/schemas/ErrorEnvelope"
                },
                "example": {
                  "status": false,
                  "errors": [
                    {
                      "code": "INVALID_REQUEST",
                      "message": "At least one search parameter is required (domain, name, email, commonName, directoryId, or lei)"
                    }
                  ]
                }
              }
            }
          },
          "500": {
            "$ref": "#/components/responses/InternalError"
          }
        }
      }
    }
  }
}