openapi: 3.0.3 info: title: Unigox API Gateway description: | Build fiat on/off-ramp flows in your app with Unigox partner APIs. This documentation contains the generated API reference only. For onboarding guides, tutorials, tools, and support pages, use the guide pages in the main sidebar. ## Welcome Unigox is a global P2P fiat on/off-ramp gateway for B2B partners. We focus on local rails coverage, fast execution, and simple partner integration. Key partner value: - coverage across Africa, LatAm, Asia, Middle East, Europe, and Australia - support for local rails and institutions relevant in emerging markets - compliance flows for KYC, verification, and webhook integrity ## API Reference Endpoint groups are ordered for partner integration flow: `Health` → `Supported Resources` → `Exchange Pairs` → `User Management` → `On-Ramp` → `Off-Ramp` → `Orders` → `Webhooks`. version: 1.0.0 contact: name: Unigox API Support email: support@unigox.com externalDocs: description: Unigox Python SDK url: https://github.com/Unigox/sdk_python/blob/main/partner_authorize_crypto_transfer_sdk.py servers: - url: https://api-staging.unigox.com description: Sandbox server - url: https://api.unigox.com description: Production server tags: - name: Health description: Service health and status endpoints x-page-icon: hand - name: Supported Resources description: Discover payment supported resources including cryptocurrencies, blockchains, fiat currencies, and rails x-page-icon: list-check - name: Exchange Pairs description: Get information about supported trading pairs x-page-icon: shuffle - name: User Management description: Endpoints to create and manage user accounts x-page-icon: user - name: On-Ramp description: On-ramp operations (fiat to crypto) — partner initiates a buy order, vendor provides liquidity, end-user sends fiat x-page-icon: arrow-down-to-bracket - name: Liquidity description: Real-time aggregated liquidity data — available trading pairs, amount ranges, and payment methods x-page-icon: chart-bar - name: Off-Ramp description: Off-ramp operations (crypto to fiat) — sell crypto and receive fiat x-page-icon: arrow-right-arrow-left - name: Orders description: Order management, status tracking, and lifecycle actions x-page-icon: receipt - name: Webhooks description: | Real-time notifications for order and KYC status changes. The system sends a signed HTTP POST to your configured webhook URL on every relevant event. **Event model** Two event types are delivered: | Event type | Fired when | |---|---| | `order.status.changed` | An order transitions to a new status | | `user.kyc.updated` | A user's KYC verification is approved or declined | All events share the same envelope (`event_id`, `event_type`, `created_at`, `data`) and the same signature scheme. The structure of the `data` object differs per event type. **Payload shape — `order.status.changed`** ```json { "event_id": "evt_a1b2c3d4-e5f6-7890-abcd-ef1234567890", "event_type": "order.status.changed", "created_at": "2026-02-01T12:00:05Z", "data": { "order_id": "b2c3d4e5-f6a7-8901-bcde-f12345678901", "status": "crypto_received", "user_uuid": "550e8400-e29b-41d4-a716-446655440000", "crypto_amount": "101.500000", "crypto_currency": "USDT", "fiat_amount": "152880.00", "fiat_currency": "NGN", "provider": "p2p", "payment_details_id": "987" } } ``` **Payload shape — `user.kyc.updated`** Fired when a user's KYC status transitions to `verified` or `rejected`. Use this event to unlock order creation for the user on your side. ```json { "event_id": "evt_2d8418dd-e3a8-4463-a11e-6b54f354ca90", "event_type": "user.kyc.updated", "created_at": "2026-03-10T11:37:42Z", "data": { "user_uuid": "550e8400-e29b-41d4-a716-446655440000", "kyc_status": "VERIFIED" } } ``` When KYC is declined, `rejection_reasons` is included: ```json { "event_id": "evt_fc75484d-b374-4d05-b54e-820f3dd80e6d", "event_type": "user.kyc.updated", "created_at": "2026-03-10T12:00:00Z", "data": { "user_uuid": "550e8400-e29b-41d4-a716-446655440000", "kyc_status": "REJECTED", "rejection_reasons": ["Document expired", "Photo quality low"] } } ``` `kyc_status` values: `VERIFIED` | `REJECTED` **Signature verification** Every delivery includes two headers for HMAC-SHA256 verification: - `X-Unigox-Signature: sha256=` — HMAC of `"."` - `X-Unigox-Timestamp: ` Verification pseudocode: ``` expected = HMAC-SHA256(webhook_secret, ".") valid = constant_time_compare(expected, signature_from_header) ``` **Delivery guarantees** - At-least-once delivery. Your endpoint may receive the same event more than once. - De-duplicate by `event_id`. - Respond with any 2xx status within 10 seconds to acknowledge receipt. **Retry policy** Failed deliveries (non-2xx or timeout) are retried with exponential backoff: 1 min → 5 min → 15 min → 1 h → 6 h → 12 h → 24 h (max 10 attempts over ~3.5 days). **Partner-facing statuses** (`order.status.changed`) | Status | Description | |---|---| | `created` | Order created | | `awaiting_liquidity_provider` | Waiting for vendor match | | `awaiting_crypto_transfer_authorization` | Partner must authorize crypto transfer | | `crypto_received` | Crypto received in escrow | | `fiat_payment_started` | Buyer submitted fiat payment proof | | `fiat_payment_review_started` | Payment proof under admin review | | `awaiting_fiat_received_confirmation` | Proof approved, awaiting seller confirmation | | `completed` | Order completed, crypto released | | `cancelled` | Order cancelled or payment declined | | `failed` | Escrow error, or fiat-anchored safety bound exceeded (see below) | | `dispute_started` | Dispute opened | **Fiat-anchored safety bound failure** For fiat-anchored off-ramp orders (`anchor_type: "fiat"`), if Switch quotes a crypto cost exceeding 1.20× the expected amount at the time of settlement, the order moves to `failed` status asynchronously (not during `/initiate`). The deposit is automatically refunded from escrow back to the partner wallet. Create a new quote to retry the trade flow. x-page-icon: bell paths: # ============================================ # 1. HEALTH CHECK # ============================================ /health: get: tags: - Health summary: Health check description: Returns the health status of the API gateway operationId: healthCheck responses: '200': description: Service is healthy content: application/json: schema: type: object properties: status: type: string example: ok service: type: string example: api time: type: string format: date-time example: "2024-01-01T12:00:00Z" # ============================================ # 2. DISCOVERY - CAPABILITIES # ============================================ # Step 1: Discover supported cryptocurrencies /api/v1/supported/crypto-currencies: get: tags: - Supported Resources summary: Cryptocurrencies description: Get a list of all supported cryptocurrencies with their blockchains and contract addresses. Useful for discovering available crypto assets for trading. operationId: getCryptoCurrencies responses: '200': description: Successfully retrieved cryptocurrencies content: application/json: schema: $ref: '#/components/schemas/CryptoCurrenciesResponse' # Step 2: Discover supported blockchains /api/v1/supported/blockchains: get: tags: - Supported Resources summary: Blockchains description: Get a list of all supported blockchains with their associated cryptocurrencies. Useful for discovering which blockchains are available and what tokens they support. operationId: getBlockchains responses: '200': description: Successfully retrieved blockchains content: application/json: schema: $ref: '#/components/schemas/BlockchainsResponse' # Step 3: Discover supported fiat currencies /api/v1/supported/fiat-currencies: get: tags: - Supported Resources summary: Fiat currencies description: Get a list of all supported fiat currencies with their onramp/offramp capabilities. Useful for discovering available fiat currencies for onramp and offramp operations. operationId: getFiatCurrencies responses: '200': description: Successfully retrieved fiat currencies content: application/json: schema: $ref: '#/components/schemas/FiatCurrenciesResponse' '500': description: Internal Server Error content: application/json: schema: $ref: '#/components/schemas/ErrorResponse' # Step 4: Discover payment rails for a country/currency /api/v1/supported/payment-rails: get: tags: - Supported Resources summary: Payment rails description: | Get payment rail capabilities for a specific country, currency, and direction (onramp/offramp). Returns available payment rails with their required fields and supported currencies. Partner rule: - if `institution_required` is `true`, send `institution_id` chosen from `/api/v1/supported/institutions` - if `institution_required` is `false`, institution selection is not needed for the rail itself Current partner API omission support is explicitly available only for: - `iban-sepa` - `nip-nigeria` For those rails, partners may omit `institution_id` and the backend will resolve the generic `other-bank` payment method automatically. operationId: getPaymentRails parameters: - name: country in: query description: ISO 2-letter country code (e.g., NG, US, IN) schema: type: string example: NG - name: currency in: query description: Fiat currency code (e.g., NGN, USD, EUR). Either country or currency must be provided. schema: type: string example: NGN - name: direction in: query required: true description: Transaction direction - either "onramp" (fiat to crypto) or "offramp" (crypto to fiat) schema: type: string enum: [onramp, offramp] example: offramp responses: '200': description: Successfully retrieved payment capabilities content: application/json: schema: $ref: '#/components/schemas/PaymentCapabilityResponse' example: success: true data: country: NG currency: NGN direction: offramp rails: - slug: nip-nigeria name: NIP Nigeria (NIBSS Instant Payments) institution_required: false institution_lookup_key: null currencies: - NGN settlement: speed: INSTANT typical_minutes: 2 note: null limits: null fields: - key: account_number label: Account Number type: text required: true pattern: "^\\d{10}$" placeholder: "10-digit account number" lookup: false - key: full_name label: Full Name type: text required: false pattern: "^[a-zA-ZÀ-ÿ\\u0100-\\u017F\\u0400-\\u04FF\\s'-]{2,50}$" placeholder: "Enter full name" links: search_institutions: /api/v1/supported/institutions '400': description: Bad request - missing required parameters or invalid values content: application/json: schema: $ref: '#/components/schemas/ErrorResponse' '500': description: Internal Server Error content: application/json: schema: $ref: '#/components/schemas/ErrorResponse' # Step 5: Discover institutions (banks) for a payment rail (used when institution_required: true) /api/v1/supported/institutions: get: tags: - Supported Resources summary: Institutions (banks) for a payment rail description: Get a list of financial institutions (banks) available for a specific payment rail. Useful for populating bank selection dropdowns when institution selection is required. operationId: getInstitutions parameters: - name: rail in: query required: true description: Payment rail slug (e.g., iban-sepa, pix-brazil, cvu-cbu) schema: type: string example: iban-sepa - name: institution_id in: query description: Optional institution ID to filter by specific institution schema: type: string example: revolut - name: country in: query description: ISO 2-letter country code for filtering schema: type: string example: IN - name: currency in: query description: ISO 3-letter currency code for filtering schema: type: string example: INR - name: search in: query description: Free-text search by institution name schema: type: string example: hdfc - name: code in: query description: Lookup by specific code such as IFSC or sort code schema: type: string example: HDFC0001234 - name: limit in: query description: Maximum number of results to return schema: type: integer minimum: 1 maximum: 100 default: 20 example: 20 - name: offset in: query description: Number of results to skip for pagination schema: type: integer minimum: 0 default: 0 example: 0 responses: '200': description: Successfully retrieved institutions content: application/json: schema: $ref: '#/components/schemas/InstitutionsResponse' example: success: true data: institutions: - id: wise name: Wise type: BANK code: "" branch: null supported_rails: - iban-sepa - wise-network pagination: total: 1 limit: 20 offset: 0 '400': description: Bad request - invalid rail or other validation errors content: application/json: schema: $ref: '#/components/schemas/ErrorResponse' '500': description: Internal Server Error content: application/json: schema: $ref: '#/components/schemas/ErrorResponse' # Fiat corridors snapshot - all-in rates per direction /api/v1/supported/corridors: get: tags: - Supported Resources summary: Fiat Corridors description: | Get a snapshot of supported fiat corridors with the all-in best rates Unigox would quote at a 500 USDT reference notional, split by direction (`onramp` / `offramp`) and partner type (`licensed` / `p2p`). Every `rate` is post-fee. `unigox_fee_pct` discloses the platform fee for the corridor; `mid_market_rate` is the Open Exchange Rates reference for the same currency. operationId: getSupportedCorridors responses: '200': description: Snapshot retrieved successfully content: application/json: schema: $ref: '#/components/schemas/SupportedCorridorsResponse' example: success: true data: notional_usdt: 500 generated_at: '2026-04-27T15:56:11Z' currencies: - code: NGN name: Nigerian Naira mid_market_rate: 1358.97 mid_market_updated_at: '2026-04-27T15:56:11Z' unigox_fee_pct: 0.25 offramp: supported: true p2p: rate: 1542.6 licensed: rate: 1376.857 onramp: supported: true p2p: rate: 1370 licensed: rate: 1389.35 - code: BRL name: Brazilian Real mid_market_rate: 4.9911 mid_market_updated_at: '2026-04-27T15:56:11Z' unigox_fee_pct: 0.5 offramp: supported: true p2p: rate: 4.4648 min_notional_fiat: 3465 '500': description: Internal Server Error content: application/json: schema: $ref: '#/components/schemas/ErrorResponse' # ============================================ # 3. EXCHANGE PAIRS # ============================================ /api/v1/supported-exchange-pairs: get: tags: - Exchange Pairs summary: Supported exchange pairs description: Get a list of all supported cryptocurrency and fiat currency exchange pairs. Shows which currency combinations can be traded. operationId: getSupportedPairs responses: '200': description: Successfully retrieved supported pairs content: application/json: schema: $ref: '#/components/schemas/SupportedPairsResponse' example: success: true data: - crypto_currency_code: USDC fiat_currency_code: ARS type: BUY - crypto_currency_code: USDC fiat_currency_code: ARS type: SELL - crypto_currency_code: USDC fiat_currency_code: AUD type: BUY - crypto_currency_code: USDC fiat_currency_code: AUD type: SELL - crypto_currency_code: USDC fiat_currency_code: CAD type: BUY - crypto_currency_code: USDC fiat_currency_code: CAD type: SELL - crypto_currency_code: USDC fiat_currency_code: EUR type: BUY - crypto_currency_code: USDC fiat_currency_code: EUR type: SELL - crypto_currency_code: USDC fiat_currency_code: GBP type: BUY - crypto_currency_code: USDC fiat_currency_code: GBP type: SELL - crypto_currency_code: USDC fiat_currency_code: GEL type: BUY - crypto_currency_code: USDC fiat_currency_code: GEL type: SELL - crypto_currency_code: USDC fiat_currency_code: GHS type: BUY # ============================================ # 5. ACCOUNT MANAGEMENT # ============================================ # 5. PARTNER AUTHENTICATION # ============================================ /api/v1/partner/verify-auth: get: tags: - User Management summary: Verify API key authentication description: | Verifies that the provided API key is valid and authentication is working correctly. operationId: verifyAuth security: - ApiKeyAuth: [] responses: '200': description: Authentication successful content: application/json: schema: allOf: - $ref: '#/components/schemas/APIResponse' - type: object properties: data: type: object properties: message: type: string example: "Authentication successful" authenticated: type: boolean example: true example: success: true data: message: "Authentication successful" authenticated: true '401': description: Unauthorized - invalid or missing API key content: application/json: schema: $ref: '#/components/schemas/ErrorResponse' example: success: false data: error_key: "unauthorized" # ============================================ # 6. PARTNER USER MANAGEMENT # ============================================ # Step 1: Create or get partner user /api/v1/partner/users: post: tags: - User Management summary: Create or get partner user description: Creates a new user relation or returns existing if already created. Returns user_ref (same as input) for subsequent API calls. Partners can only access their own users. operationId: createPartnerUser security: - ApiKeyAuth: [] requestBody: required: true content: application/json: schema: type: object required: [user_ref, email] properties: user_ref: type: string description: Partner's unique user ID (usually primary key or UUID) example: "partner_unique_id_123" email: type: string format: email description: User's email address example: user@example.com responses: '201': description: Successfully created user content: application/json: schema: $ref: '#/components/schemas/CreateUserResponse' '200': description: User already exists content: application/json: schema: $ref: '#/components/schemas/CreateUserResponse' '400': description: Bad Request '401': description: Unauthorized (missing or invalid X-API-Key) '500': description: Internal Server Error # Step 2: Get partner user by id /api/v1/partner/users/{user_uuid}: get: tags: - User Management summary: Partner user by ID description: Get user details including KYC status and payment profiles using the user_uuid returned during user creation. Partners can only access their own users. operationId: getPartnerUser security: - ApiKeyAuth: [] parameters: - name: user_uuid in: path required: true description: The user_uuid returned by POST /partner/users. Partners can only access their own users. schema: type: string format: uuid example: "550e8400-e29b-41d4-a716-446655440000" responses: '200': description: User found content: application/json: schema: $ref: '#/components/schemas/GetUserResponse' '401': description: Unauthorized '404': description: User not found # ============================================ # 7. PARTNER KYC VERIFICATION FLOW # ============================================ # Step 3: Submit KYC data for a partner user /api/v1/partner/users/{user_uuid}/kyc-submissions: post: tags: - User Management summary: Submit KYC data for a partner user description: | Submit KYC (Know Your Customer) data for a partner user. Supports three methods: - direct_data: Partner submits PII data directly, user is verified - external_token: Partner provides external KYC provider credentials (SumSub token or Persona inquiry_id) - handoff: Unigox handles KYC verification and generates verification URL **Important for EUR/AUD/GBP off-ramp:** EUR, AUD, and GBP payouts require the user's physical address. Include `address`, `city`, and `postal_code` in the PII data when submitting KYC. If not provided at submission time, you can add them later with `PATCH /partner/users/{user_uuid}/kyc`. Without address, EUR/AUD/GBP orders will be created with status `pending_address` until the address is provided. operationId: submitKYC security: - ApiKeyAuth: [] parameters: - name: user_uuid in: path required: true description: The user_uuid returned by POST /partner/users. Partners can only access their own users. schema: type: string format: uuid example: "550e8400-e29b-41d4-a716-446655440000" requestBody: required: true content: application/json: schema: oneOf: - $ref: '#/components/schemas/KYCSubmitDirectData' - $ref: '#/components/schemas/KYCSubmitExternalToken' - $ref: '#/components/schemas/KYCSubmitHandoff' examples: direct_data: summary: Direct data submission value: method: direct_data data: pii: first_name: John last_name: Doe dob: "1990-01-01" country_code: NG address: "123 Street Name, Lagos" postal_code: "100001" external_token_with_sumsub: summary: External token (SumSub) value: method: external_token data: provider: sumsub token: "sumsub_external_session_id" external_token_with_persona: summary: External token (Persona) value: method: external_token data: provider: persona inquiry_id: "inq_CWQzc5afzD4dmF66EJqjdPC2T79C" handoff: summary: Handoff to Unigox value: method: handoff data: pii: first_name: John last_name: Doe country_code: NG responses: '200': description: KYC submitted successfully content: application/json: schema: $ref: '#/components/schemas/KYCSubmitResponse' '400': description: Bad Request - invalid method or missing required data '401': description: Unauthorized '404': description: User not found '500': description: Internal Server Error # Step 4: Upload KYC document for a partner user (for direct_data method) /api/v1/partner/users/{user_uuid}/kyc/documents: post: tags: - User Management summary: Upload KYC document for a partner user description: | Upload a KYC document for a partner user. Choose one of two upload methods: --- ### Method 1: File Upload Send the document as a file attachment. ``` POST /api/v1/partner/users/{user_uuid}/kyc/documents Content-Type: multipart/form-data document: document_type: identity_front ``` ### Method 2: Single URL Upload Send a URL pointing to one document image. ```json { "upload_method": "url_link", "document_type": "identity_front", "url": "https://example.com/id-front.jpg" } ``` ### Method 3: Batch URL Upload Upload multiple documents in one call (up to 10). ```json { "upload_method": "url_link", "documents": [ { "document_type": "identity_front", "url": "https://example.com/id-front.jpg" }, { "document_type": "selfie", "url": "https://example.com/selfie.jpg" } ] } ``` **URL requirements:** HTTPS only, 10MB per file, JPEG/PNG/GIF/PDF. --- **Supported document types:** `identity_front` (required), `selfie` (required), `identity_back`, `passport`, `national_id`, `driver_license`, and more. **Automatic Verification:** After uploading all required documents (selfie + identity_front), verification starts automatically. operationId: uploadPartnerKYCDocument security: - ApiKeyAuth: [] parameters: - name: user_uuid in: path required: true description: Public UUID of the user schema: type: string format: uuid example: "550e8400-e29b-41d4-a716-446655440000" requestBody: required: true content: application/json: schema: oneOf: - title: File Upload (multipart) description: Upload document as a file attachment. Use Content-Type multipart/form-data. type: object required: [document, document_type] properties: document: type: string format: binary description: Document file (JPEG, PNG, GIF, or PDF, max 10MB) document_type: type: string description: Type of document being uploaded enum: [identity_front, identity_back, selfie, passport, national_id, driver_license, voter_id, resident_card, id_card] example: identity_front - title: Single URL Upload description: Upload one document by providing an HTTPS URL. type: object required: [upload_method, document_type, url] properties: upload_method: type: string enum: [url_link] example: url_link document_type: type: string enum: [identity_front, identity_back, selfie, passport, national_id, driver_license, voter_id, resident_card, id_card] example: identity_front url: type: string format: uri description: HTTPS URL of the image. 10MB max. example: "https://example.com/id-front.jpg" - title: Batch URL Upload description: Upload multiple documents in one call (up to 10). type: object required: [upload_method, documents] properties: upload_method: type: string enum: [url_link] example: url_link documents: type: array maxItems: 10 items: type: object required: [document_type, url] properties: document_type: type: string enum: [identity_front, identity_back, selfie, passport, national_id, driver_license] example: identity_front url: type: string format: uri example: "https://example.com/id-front.jpg" discriminator: propertyName: upload_method examples: single_url: summary: Single document via URL value: upload_method: url_link document_type: identity_front url: "https://example.com/id-front.jpg" batch_url: summary: Batch upload (multiple documents) value: upload_method: url_link documents: - document_type: identity_front url: "https://example.com/id-front.jpg" - document_type: selfie url: "https://example.com/selfie.jpg" responses: '200': description: Document uploaded successfully content: application/json: schema: type: object properties: success: type: boolean example: true data: type: object properties: document_id: type: string description: AiPrise document UUID example: "f1d5f922-bee2-4c7e-88ef-18022958c4c2" document_type: type: string example: "identity_front" file_name: type: string example: "passport_photo.jpg" message: type: string description: | Success message. If all required documents are uploaded, message indicates that verification started automatically. example: "Document uploaded successfully" verification_session_id: type: string description: | Verification session ID (only present if verification started automatically after uploading the last required document: selfie + identity_front) example: "4fd410c9-ba9b-46cf-a4f0-132204a9a564" verification_status: type: string enum: [PENDING, IN_PROGRESS, COMPLETED, FAILED] description: | Verification status (only present if verification started automatically after uploading the last required document: selfie + identity_front) example: "IN_PROGRESS" '400': description: Bad Request - invalid file type, missing document, or user not ready for uploads '401': description: Unauthorized '404': description: User not found or no active KYC session '500': description: Internal Server Error # Step 4b: Update optional KYC fields for a partner user /api/v1/partner/users/{user_uuid}/kyc: patch: tags: - User Management summary: Update KYC data for a partner user description: | Update optional PII fields on existing KYC data. The user stays verified. Only optional fields can be updated. Required fields (first_name, last_name, country_code) cannot be changed. For EUR/AUD/GBP offramp, provide address, city, and postal_code here. These will auto-populate into payment details for EUR/AUD/GBP payouts. operationId: updateKYC security: - ApiKeyAuth: [] parameters: - name: user_uuid in: path required: true description: The user_uuid returned by POST /partner/users. schema: type: string format: uuid example: "550e8400-e29b-41d4-a716-446655440000" requestBody: required: true content: application/json: schema: type: object properties: address: type: string description: Street address example: "Svitrigailos g 29" city: type: string description: City example: "Vilnius" postal_code: type: string example: "03229" dob: type: string format: date example: "1990-01-01" phone_number: type: string description: E.164 format example: "+37012345678" middle_name: type: string id_number: type: string id_type: type: string enum: [NATIONAL_ID, DRIVER_LICENSE, PASSPORT] responses: '200': description: KYC data updated successfully content: application/json: schema: type: object properties: success: type: boolean example: true data: type: object properties: updated_fields: type: array items: type: string example: ["address", "city", "postal_code"] message: type: string example: "KYC data updated successfully" '400': description: Bad request (no valid fields, or trying to change required fields) '401': description: Unauthorized '404': description: User not found # Step 5: Get verification status for a partner user (check KYC status) /api/v1/partner/users/{user_uuid}/verification-status: get: tags: - User Management summary: Verification status for a partner user description: | Get the current verification status for a partner user. This endpoint checks the verification status with the KYC provider (e.g., AiPrise) and returns the current status, verification URL (if available), and other relevant information. Optionally, you can provide a verification_id to check a specific verification session. Interpretation of terminal states: - `VERIFIED` — verification passed and the user is approved - `VERIFICATION_REJECTED` — verification completed with a rejection outcome - `FAILED` — verification could not be completed due to an error - `COMPLETED` — provider workflow finished processing, but partners should still treat `VERIFIED` as the success state and `VERIFICATION_REJECTED` / `FAILED` as terminal non-success states operationId: getVerificationStatus security: - ApiKeyAuth: [] parameters: - name: user_uuid in: path required: true description: The user_uuid returned by POST /partner/users. Partners can only access their own users. schema: type: string format: uuid example: "550e8400-e29b-41d4-a716-446655440000" - name: verification_id in: query required: false description: Optional verification session ID. If not provided, the latest verification session for the user will be checked. schema: type: string example: "a1b2c3d4-e5f6-7890-abcd-ef1234567890" responses: '200': description: Successfully retrieved verification status content: application/json: schema: $ref: '#/components/schemas/VerificationStatusResponse' example: success: true data: status: "IN_PROGRESS" verification_url: "https://verify-sandbox.aiprise.com/?verification_session_id=abc123" verification_seconds_left: 3600 '401': description: Unauthorized '404': description: User not found '500': description: Internal Server Error # ============================================ # 6. PARTNER PAYMENT DETAILS MANAGEMENT # ============================================ # Step 6: Create payment details for a partner user (for offramp operations) /api/v1/partner/users/{user_uuid}/payment-details: post: tags: - User Management summary: Create payment details for a partner user description: | Create payment details (beneficiary details) for a partner user. Used for offramp operations where crypto is converted to fiat and sent to the user's payment account. Partners can only access their own users. Institution handling: - for most rails, `institution_id` must be provided from `/api/v1/supported/institutions` - partners may omit `institution_id` only for the explicitly supported rails below; the backend will assign the generic `other-bank` payment method automatically Currently supported omission rails: - `iban-sepa` - `nip-nigeria` operationId: createPaymentDetails security: - ApiKeyAuth: [] parameters: - name: user_uuid in: path required: true description: The user_uuid returned by POST /partner/users. Partners can only access their own users. schema: type: string format: uuid example: "550e8400-e29b-41d4-a716-446655440000" requestBody: required: true content: application/json: schema: $ref: '#/components/schemas/CreatePaymentDetailsRequest' examples: iban-sepa: summary: SEPA payment details (Europe, institution optional) value: currency: EUR rail: iban-sepa country_code: DE details: full_name: "John Doe" iban: DE89370400440532013000 cvu-argentina: summary: CVU payment details (Argentina - Mobile Money/Wallets) value: currency: ARS rail: cvu-cbu institution_id: uala country_code: AR details: cvu: "0000003100000001234567" alias: "john.doe" phone_number: "+541112345678" cbu-argentina: summary: CBU payment details (Argentina - Traditional/Digital Banks) value: currency: ARS rail: cvu-cbu institution_id: banco-santander-argentina country_code: AR details: cbu: "0000003100000001234567" alias: "john.doe" phone_number: "+541112345678" responses: '201': description: Payment details created successfully content: application/json: schema: $ref: '#/components/schemas/CreatePaymentDetailsResponse' '400': description: Bad Request - invalid payment details or unsupported combination content: application/json: schema: $ref: '#/components/schemas/ErrorResponse' example: success: false data: error_key: "invalid_payment_details" '401': description: Unauthorized '404': description: User not found '500': description: Internal Server Error # Step 7: Get all payment details for a partner user get: tags: - User Management summary: Payment details for a partner user description: Retrieve all payment details (payment profiles) for a partner user. Returns all saved beneficiary details that can be used for offramp operations. Partners can only access their own users. operationId: getPaymentDetails security: - ApiKeyAuth: [] parameters: - name: user_uuid in: path required: true description: The user_uuid returned by POST /partner/users. Partners can only access their own users. schema: type: string format: uuid example: "550e8400-e29b-41d4-a716-446655440000" responses: '200': description: Successfully retrieved payment details content: application/json: schema: $ref: '#/components/schemas/GetPaymentDetailsResponse' '401': description: Unauthorized '404': description: User not found '500': description: Internal Server Error # Step 8: Delete payment details for a partner user /api/v1/partner/users/{user_uuid}/payment-details/{payment_details_id}: delete: tags: - User Management summary: Delete payment details for a partner user description: Delete a specific payment details (payment profile) for a partner user by its ID. Partners can only access their own users. operationId: deletePaymentDetails security: - ApiKeyAuth: [] parameters: - name: user_uuid in: path required: true description: The user_uuid returned by POST /partner/users. Partners can only access their own users. schema: type: string format: uuid example: "550e8400-e29b-41d4-a716-446655440000" - name: payment_details_id in: path required: true description: The payment_details_id returned when creating payment details. schema: type: string example: "987" responses: '200': description: Payment details deleted successfully content: application/json: schema: type: object properties: success: type: boolean example: true data: type: object properties: message: type: string example: "Payment details deleted successfully" '401': description: Unauthorized '404': description: User not found or payment details not found '409': description: Conflict - payment details linked to active offers '500': description: Internal Server Error # ============================================ # 8. LIQUIDITY # ============================================ /api/v1/partner/liquidity: get: tags: - Liquidity summary: Get aggregated liquidity by trading pairs description: | Returns real-time aggregated liquidity data across all active off-ramp offers. Use this endpoint for **liquidity discovery** — to determine which crypto/fiat pairs are currently available, what fiat amount ranges are supported, and which payment methods are accepted. No pricing or vendor details are returned. **Trade type semantics:** `trade_type` is expressed in partner terms. `SELL` means the partner sells crypto and receives fiat (the most common off-ramp direction). `BUY` means the partner buys crypto by sending fiat. **Eligibility:** Only offers that pass the same gates as the vendor-matching engine are included — the offer must be authorized, the vendor must be active and online, and at least one payment method option must be active. **Aggregation:** Results are grouped by `(crypto_currency_code, fiat_currency_code, trade_type)`. Amount ranges span the minimum and maximum across all eligible offers in the group. Payment methods are deduplicated and sorted alphabetically. **Authentication:** None. This is a public endpoint — no API key required. security: [] responses: '200': description: Liquidity pairs retrieved successfully content: application/json: schema: type: object properties: success: type: boolean example: true data: type: object properties: pairs: type: array items: $ref: '#/components/schemas/LiquidityPair' example: success: true data: pairs: - crypto_currency_code: USDC fiat_currency_code: EUR trade_type: SELL min_amount: 50.00 max_amount: 10000.00 min_amount_usd: 54.35 max_amount_usd: 10870.00 payment_methods: - sepa - wise - crypto_currency_code: USDC fiat_currency_code: NGN trade_type: SELL min_amount: 50000.00 max_amount: 5000000.00 min_amount_usd: 31.25 max_amount_usd: 3125.00 payment_methods: - gtbank - zenith-bank '500': description: Internal server error # ============================================ # 9. ON-RAMP OPERATIONS # ============================================ /api/v1/partner/onramp/estimate: post: tags: - On-Ramp summary: Get on-ramp price estimate description: | Get indicative on-ramp pricing (fiat to crypto) without creating a quote. This endpoint is lightweight and does not reserve liquidity. Use this when you need quick pricing discovery before calling `/partner/onramp/quote`. Optional filters: `payment_method_slug`, `payment_network_slug`, `provider_scope`, `country_code`. This endpoint is indicative and does not apply user/payment-details ownership checks; for executable pricing use quote. operationId: getOnRampEstimate security: - ApiKeyAuth: [] requestBody: required: true content: application/json: schema: $ref: '#/components/schemas/PartnerPriceEstimateRequest' example: crypto_currency: USDT fiat_currency: VND fiat_amount: "26345" payment_method_slug: zalopay payment_network_slug: napas-vietnam country_code: VN provider_scope: all responses: '200': description: Estimate retrieved successfully content: application/json: schema: allOf: - $ref: '#/components/schemas/APIResponse' - type: object properties: data: $ref: '#/components/schemas/PartnerPriceEstimateResponse' example: success: true data: rate: "26345.00" crypto_amount: "1.005000" fiat_amount: "26345.00" fee_breakdown: platform_fee: "0.005000" platform_fee_pct: 0.5 partner_fee: "0.00" partner_fee_pct: 0 total_fee: "0.005000" payment_method_slug: zalopay payment_network_slug: napas-vietnam provider_scope: all is_indicative: true '400': description: | Bad request — invalid parameters, missing required fields, unsupported cryptocurrency, or invalid provider_scope. content: application/json: schema: $ref: '#/components/schemas/PartnerErrorResponse' examples: invalid_amount: summary: Invalid amount configuration value: success: false error: code: INVALID_REQUEST message: "exactly one of crypto_amount or fiat_amount must be provided" unsupported_crypto: summary: Unsupported cryptocurrency value: success: false error: code: INVALID_REQUEST message: "unsupported cryptocurrency: BTC" invalid_provider_scope: summary: Invalid provider_scope value value: success: false error: code: INVALID_REQUEST message: "invalid provider_scope: must be all, licensed_only, or p2p_only" '401': description: Unauthorized — missing or invalid API key content: application/json: schema: $ref: '#/components/schemas/PartnerErrorResponse' example: success: false error: code: UNAUTHORIZED message: "missing X-API-Key header" '409': description: No offers available for the requested pair/amount content: application/json: schema: $ref: '#/components/schemas/PartnerErrorResponse' example: success: false error: code: NO_OFFERS_AVAILABLE message: "no matching offers found for the provided criteria" '500': description: Internal Server Error /api/v1/partner/onramp/quote: post: tags: - On-Ramp summary: Get on-ramp quote description: | Get a price quote for an on-ramp operation (fiat to crypto). Returns the exchange rate, amounts, and a quote ID that can be used to initiate the order. Quotes expire after 60 seconds. The `user_uuid` is the public UUID returned by `POST /partner/users` (the `id` field). **Fee structure:** The `fee_breakdown` object shows all fee components transparently: `platform_fee` (our fee in crypto), `partner_fee` (partner's markup, currently 0, configurable via dashboard in a future release), and `total_fee` (sum of both). **Payment method:** Provide `payment_method_slug` and/or `payment_network_slug` to narrow routing. If omitted, the best available option for the pair is selected automatically. operationId: getOnRampQuote security: - ApiKeyAuth: [] requestBody: required: true content: application/json: schema: $ref: '#/components/schemas/PartnerOnRampQuoteRequest' example: user_uuid: "23d60610-9737-43b2-a751-a2c6a7bba457" fiat_amount: "26345" fiat_currency: VND crypto_currency: USDT payment_method_slug: zalopay payment_network_slug: napas-vietnam country_code: VN responses: '200': description: Quote retrieved successfully content: application/json: schema: allOf: - $ref: '#/components/schemas/APIResponse' - type: object properties: data: $ref: '#/components/schemas/PartnerOnRampQuoteResponse' example: success: true data: quote_id: "a1b2c3d4-e5f6-7890-abcd-ef1234567890" rate: "26345.00" crypto_amount: "1.005000" fiat_amount: "26345.00" fee_breakdown: platform_fee: "0.005000" platform_fee_pct: 0.5 partner_fee: "0.00" partner_fee_pct: 0 total_fee: "0.005000" payment_method_slug: zalopay payment_network_slug: napas-vietnam expires_at: "2026-03-30T19:15:50Z" '400': description: Bad request — invalid parameters or missing required fields content: application/json: schema: $ref: '#/components/schemas/PartnerErrorResponse' example: success: false error: code: INVALID_REQUEST message: "Missing required field: fiat_currency" '401': description: Unauthorized — invalid or missing API key content: application/json: schema: $ref: '#/components/schemas/PartnerErrorResponse' example: success: false error: code: UNAUTHORIZED message: "Invalid or missing API key" '404': description: User not found content: application/json: schema: $ref: '#/components/schemas/PartnerErrorResponse' example: success: false error: code: INVALID_REQUEST message: "User not found" '409': description: No offers available for the requested pair content: application/json: schema: $ref: '#/components/schemas/PartnerErrorResponse' example: success: false error: code: NO_OFFERS_AVAILABLE message: "No offers available for USDT/VND" '500': description: Internal server error content: application/json: schema: $ref: '#/components/schemas/PartnerErrorResponse' /api/v1/partner/onramp/initiate: post: tags: - On-Ramp summary: Initiate on-ramp order description: | Initiate an on-ramp order using a previously obtained quote. The quote must still be active (not expired or already used). On success, returns the created order with status `awaiting_liquidity_provider`. Once a vendor accepts, the order moves to `awaiting_vendor_escrow_funding`, then `awaiting_fiat_transfer` when the crypto is locked in escrow. At `awaiting_fiat_transfer` the partner must call `confirm-payment-sent` to advance the order. If the order type is `payment_request`, call `submit-payer-details` first before confirming. operationId: initiateOnRamp security: - ApiKeyAuth: [] requestBody: required: true content: application/json: schema: $ref: '#/components/schemas/PartnerInitiateRequest' example: quote_id: "a1b2c3d4-e5f6-7890-abcd-ef1234567890" reference_id: "your-internal-order-id" responses: '201': description: On-ramp order created successfully content: application/json: schema: allOf: - $ref: '#/components/schemas/APIResponse' - type: object properties: data: $ref: '#/components/schemas/PartnerInitiateResponse' example: success: true data: order_id: "ce3cfd6a-1234-5678-abcd-ef1234567890" status: awaiting_liquidity_provider quote_id: "a1b2c3d4-e5f6-7890-abcd-ef1234567890" reference_id: "your-internal-order-id" order_type: onramp amounts: crypto_amount: "1.010025" fiat_amount: "26476.72" crypto_currency: USDT fiat_currency: VND rate: "26345.00" fee_breakdown: platform_fee: "0.005000" platform_fee_pct: 0.5 partner_fee: "0.00" partner_fee_pct: 0 total_fee: "0.005000" timeline: [] created_at: "2026-03-30T19:00:05Z" '400': description: Bad request — missing required fields content: application/json: schema: $ref: '#/components/schemas/PartnerErrorResponse' '401': description: Unauthorized — invalid or missing API key content: application/json: schema: $ref: '#/components/schemas/PartnerErrorResponse' '404': description: Quote not found content: application/json: schema: $ref: '#/components/schemas/PartnerErrorResponse' example: success: false error: code: QUOTE_NOT_FOUND message: "Quote not found" '409': description: Quote expired or already used content: application/json: schema: $ref: '#/components/schemas/PartnerErrorResponse' examples: quote_expired: summary: Quote has expired value: success: false error: code: QUOTE_EXPIRED message: "Quote has expired, please request a new quote" quote_already_used: summary: Quote already consumed value: success: false error: code: QUOTE_ALREADY_USED message: "Quote has already been used" '500': description: Internal server error content: application/json: schema: $ref: '#/components/schemas/PartnerErrorResponse' /api/v1/partner/orders/{order_id}/submit-payer-details: post: tags: - On-Ramp summary: Submit payer details description: | Submit the end-user's payment details for a `payment_request` on-ramp order. Only required when the order type is `payment_request` — regular on-ramp orders do not need this step. **Order types:** - **Regular on-ramp** — the vendor sends fiat to the end-user directly. No payer details needed. The partner receives vendor payment details in the `vendor_payment_details` field of `GET /orders/:id` and forwards them to the end-user to initiate the transfer. - **`payment_request` on-ramp** — the vendor sends a payment request (e.g. M-Pesa push) to the end-user. The end-user's account details must be submitted here first so the vendor knows where to send the request. Check `payment_request: true` in the order response to identify this type. Call this endpoint after initiating the order and before calling `confirm-payment-sent`. The order must be in `awaiting_fiat_transfer` status. Provide either a `payment_details_id` (referencing a previously saved payment profile) or an inline `payment_details` object with the payer's account information. operationId: submitPayerDetails security: - ApiKeyAuth: [] parameters: - name: order_id in: path required: true description: Order UUID returned by the initiate endpoint schema: type: string format: uuid example: "ce3cfd6a-1234-5678-abcd-ef1234567890" requestBody: required: true content: application/json: schema: $ref: '#/components/schemas/SubmitPayerDetailsRequest' examples: by_id: summary: Using a saved payment profile value: payment_details_id: 1444 inline: summary: Providing details inline value: payment_details: payment_method_id: 5 payment_network_id: 12 details: phone: "+254712345678" name: "John Doe" country_code: KE responses: '200': description: Payer details submitted successfully content: application/json: schema: allOf: - $ref: '#/components/schemas/APIResponse' - type: object properties: data: type: object properties: message: type: string example: "payer details submitted successfully" example: success: true data: message: "payer details submitted successfully" '400': description: Bad request — invalid or missing payer details content: application/json: schema: $ref: '#/components/schemas/PartnerErrorResponse' '401': description: Unauthorized — invalid or missing API key content: application/json: schema: $ref: '#/components/schemas/PartnerErrorResponse' '404': description: Order not found content: application/json: schema: $ref: '#/components/schemas/PartnerErrorResponse' example: success: false error: code: ORDER_NOT_FOUND message: "Order not found" '409': description: Order is not in the correct status or not a payment_request order content: application/json: schema: $ref: '#/components/schemas/PartnerErrorResponse' example: success: false error: code: INVALID_STATUS message: "Order is not in awaiting_fiat_transfer status" '500': description: Internal server error content: application/json: schema: $ref: '#/components/schemas/PartnerErrorResponse' /api/v1/partner/orders/{order_id}/confirm-payment-sent: post: tags: - On-Ramp summary: Confirm fiat payment sent description: | Confirm that the end-user has initiated the fiat payment to the vendor. This advances the order from `awaiting_fiat_transfer` to `fiat_transfer_pending`, signalling to the vendor that the fiat is on its way. For `payment_request` orders, call `submit-payer-details` first. No request body required. operationId: confirmPaymentSent security: - ApiKeyAuth: [] parameters: - name: order_id in: path required: true description: Order UUID schema: type: string format: uuid example: "ce3cfd6a-1234-5678-abcd-ef1234567890" responses: '200': description: Payment confirmed successfully content: application/json: schema: allOf: - $ref: '#/components/schemas/APIResponse' - type: object properties: data: type: object properties: order_id: type: string format: uuid example: "ce3cfd6a-1234-5678-abcd-ef1234567890" status: type: string example: fiat_transfer_pending example: success: true data: order_id: "ce3cfd6a-1234-5678-abcd-ef1234567890" status: fiat_transfer_pending '400': description: Bad Request content: application/json: schema: $ref: '#/components/schemas/PartnerErrorResponse' '401': description: Unauthorized — invalid or missing API key content: application/json: schema: $ref: '#/components/schemas/PartnerErrorResponse' '404': description: Order not found content: application/json: schema: $ref: '#/components/schemas/PartnerErrorResponse' example: success: false error: code: ORDER_NOT_FOUND message: "Order not found" '409': description: Order is not in awaiting_fiat_transfer status content: application/json: schema: $ref: '#/components/schemas/PartnerErrorResponse' example: success: false error: code: INVALID_STATUS message: "Order is not in awaiting_fiat_transfer status" '500': description: Internal server error content: application/json: schema: $ref: '#/components/schemas/PartnerErrorResponse' /api/v1/partner/orders/{order_id}/bridge-authorization-parameters: get: tags: - On-Ramp summary: Get send-out authorization parameters description: | After an on-ramp order reaches `completed` status (crypto on master wallet), the partner can optionally initiate a cross-chain send-out to transfer the USDT to an external network (e.g. Arbitrum). This endpoint returns the EIP-712 `ForwardRequest` parameters required to authorize the transfer. The partner must sign the request locally and submit the signature to `POST /api/v1/partner/orders/{order_id}/authorize-bridge`. **Prerequisites:** - Order status must be `completed` (internal: `escrow_released_to_buyer`) - `destination_chain` must be a supported chain slug (e.g. `arbitrum`, `ethereum`) - `destination_address` must be a valid EVM address **Response includes:** - `forward_request` — pre-filled EIP-712 ForwardRequest object to sign - `eip712_domain` — domain parameters for EIP-712 signing - `quote_id` — Relay quote ID embedded in the transfer calldata - `to_amount` — estimated USDT amount to be received on the destination chain (atomic units) - `forwarder_nonce` — current on-chain nonce for the master wallet operationId: getBridgeAuthorizationParameters security: - ApiKeyAuth: [] parameters: - name: order_id in: path required: true description: Order UUID schema: type: string format: uuid example: "ce3cfd6a-1234-5678-abcd-ef1234567890" - name: destination_chain in: query required: true description: Destination chain slug (e.g. `arbitrum`, `ethereum`) schema: type: string example: arbitrum - name: destination_address in: query required: true description: Recipient EVM address on the destination chain schema: type: string example: "0x9B61e9b3aed5bbFa66d778854E3dE7AF3241F021" responses: '200': description: Authorization parameters returned successfully content: application/json: example: success: true data: order_id: "ce3cfd6a-1234-5678-abcd-ef1234567890" chain_id: "660279" rpc_url: "https://xai-chain.net/rpc" token_address: "0xf86Cc81F4E480CF54Eb013FFe6929a0C2Ad5EdCA" forwarder_address: "0x6fFCF38bEc8c733b096958fcd2a8E31A00530EDC" solver_address: "0x00A72adEB6542916F53dc2d78450CD436320868B" sender_address: "0x6b14951552EA9fCE8f58EC52B909C14CB0619Af2" amount_atomic: "10100" amount_human: "0.010100" crypto_currency: "USDT" crypto_decimals: 6 recommended_gas: "500000" recommended_ttl: 3600 transfer_data: "0xa9059cbb..." forwarder_nonce: "63" quote_id: "0x9b0539b1..." to_amount: "10100" destination_chain: "arbitrum" destination_address: "0x9B61e9b3aed5bbFa66d778854E3dE7AF3241F021" expires_at: "2026-04-03T17:13:12Z" authorization_path: "/api/v1/partner/orders/ce3cfd6a-1234-5678-abcd-ef1234567890/authorize-bridge" '400': description: Invalid destination_chain or destination_address content: application/json: schema: $ref: '#/components/schemas/PartnerErrorResponse' '401': description: Unauthorized — invalid or missing API key content: application/json: schema: $ref: '#/components/schemas/PartnerErrorResponse' '404': description: Order not found content: application/json: schema: $ref: '#/components/schemas/PartnerErrorResponse' '409': description: Order is not in `completed` status content: application/json: schema: $ref: '#/components/schemas/PartnerErrorResponse' '500': description: Internal server error content: application/json: schema: $ref: '#/components/schemas/PartnerErrorResponse' /api/v1/partner/orders/{order_id}/authorize-bridge: post: tags: - On-Ramp summary: Authorize send-out (cross-chain transfer) description: | Submit the signed EIP-712 ForwardRequest to initiate a cross-chain send-out. The signature is verified on-chain via the XAI forwarder contract. Call `GET bridge-authorization-parameters` first to get the parameters to sign. After submission the order transitions to `send_out_pending`. A background worker monitors the Relay bridge and updates the status to `send_out_completed` or `send_out_failed` when the transfer settles. The partner receives a webhook for each transition. When the send-out completes, the `send_out_tx_hash` field in `GET /orders/:id` contains the destination-chain transaction hash. **This endpoint is idempotent at the on-chain level** — the EIP-712 nonce prevents duplicate submissions. A second call with the same nonce will be rejected. operationId: authorizeBridge security: - ApiKeyAuth: [] parameters: - name: order_id in: path required: true description: Order UUID schema: type: string format: uuid example: "ce3cfd6a-1234-5678-abcd-ef1234567890" requestBody: required: true content: application/json: schema: type: object required: [destination_chain, destination_address, forward_request, signature] properties: destination_chain: type: string description: Destination chain slug (must match the value used in bridge-authorization-parameters) example: arbitrum destination_address: type: string description: Recipient EVM address on the destination chain example: "0x9B61e9b3aed5bbFa66d778854E3dE7AF3241F021" forward_request: type: object description: EIP-712 ForwardRequest object (values from bridge-authorization-parameters) required: [from, to, value, gas, nonce, deadline, data] properties: from: type: string example: "0x6b14951552EA9fCE8f58EC52B909C14CB0619Af2" to: type: string example: "0xf86Cc81F4E480CF54Eb013FFe6929a0C2Ad5EdCA" value: type: string example: "0" gas: type: string example: "500000" nonce: type: string example: "63" deadline: type: string example: "1775236548" data: type: string example: "0xa9059cbb..." signature: type: string description: EIP-712 signature of the ForwardRequest (hex, with 0x prefix) example: "0xc9a69a67..." example: destination_chain: arbitrum destination_address: "0x9B61e9b3aed5bbFa66d778854E3dE7AF3241F021" forward_request: from: "0x6b14951552EA9fCE8f58EC52B909C14CB0619Af2" to: "0xf86Cc81F4E480CF54Eb013FFe6929a0C2Ad5EdCA" value: "0" gas: "500000" nonce: "63" deadline: "1775236548" data: "0xa9059cbb..." signature: "0xc9a69a67..." responses: '200': description: Send-out submitted successfully content: application/json: example: success: true data: order_id: "ce3cfd6a-1234-5678-abcd-ef1234567890" status: send_out_pending '400': description: Invalid request — bad forward_request, nonce mismatch, or invalid destination content: application/json: schema: $ref: '#/components/schemas/PartnerErrorResponse' '401': description: Unauthorized — invalid or missing API key content: application/json: schema: $ref: '#/components/schemas/PartnerErrorResponse' '404': description: Order not found content: application/json: schema: $ref: '#/components/schemas/PartnerErrorResponse' '409': description: Order is not in `completed` status or send-out already in progress content: application/json: schema: $ref: '#/components/schemas/PartnerErrorResponse' '502': description: Transactor service unavailable content: application/json: schema: $ref: '#/components/schemas/PartnerErrorResponse' '500': description: Internal server error content: application/json: schema: $ref: '#/components/schemas/PartnerErrorResponse' # ============================================ # 10. OFF-RAMP OPERATIONS # ============================================ /api/v1/partner/offramp/estimate: post: tags: - Off-Ramp summary: Get off-ramp price estimate description: | Get indicative off-ramp pricing (crypto to fiat) without creating a quote. Pricing can be driven from either side: provide `crypto_amount` to compute the fiat payout, or `fiat_amount` to compute the crypto required (exactly one of the two). This endpoint is lightweight and does not reserve liquidity. Use this when you need quick pricing discovery before calling `/partner/offramp/quote`. Optional filters: `payment_method_slug`, `payment_network_slug`, `provider_scope`, `country_code`. This endpoint is indicative and does not apply user/payment-details ownership checks; for executable pricing use quote. **Note:** EUR, AUD, and GBP off-ramp payouts require the user's physical address. Ensure the user's KYC includes `address`, `city`, and `postal_code` before initiating. See `PATCH /partner/users/{user_uuid}/kyc` to update. operationId: getOffRampEstimate security: - ApiKeyAuth: [] requestBody: required: true content: application/json: schema: $ref: '#/components/schemas/PartnerPriceEstimateRequest' example: crypto_currency: USDT fiat_currency: NGN crypto_amount: "100.00" payment_method_slug: access-bank payment_network_slug: nibss country_code: NG provider_scope: all responses: '200': description: Estimate retrieved successfully content: application/json: schema: allOf: - $ref: '#/components/schemas/APIResponse' - type: object properties: data: $ref: '#/components/schemas/PartnerPriceEstimateResponse' example: success: true data: rate: "1530.12" crypto_amount: "101.50" fiat_amount: "152880.00" fee_breakdown: platform_fee: "1.50" platform_fee_pct: 1.5 partner_fee: "0.00" partner_fee_pct: 0 total_fee: "1.50" payment_method_slug: access-bank payment_network_slug: nibss provider_scope: all is_indicative: true '400': description: | Bad request — invalid parameters, missing required fields, unsupported cryptocurrency, or invalid provider_scope. content: application/json: schema: $ref: '#/components/schemas/PartnerErrorResponse' examples: invalid_amount: summary: Invalid amount configuration value: success: false error: code: INVALID_REQUEST message: "exactly one of crypto_amount or fiat_amount must be provided" unsupported_crypto: summary: Unsupported cryptocurrency value: success: false error: code: INVALID_REQUEST message: "unsupported cryptocurrency: BTC" invalid_provider_scope: summary: Invalid provider_scope value value: success: false error: code: INVALID_REQUEST message: "invalid provider_scope: must be all, licensed_only, or p2p_only" '401': description: Unauthorized - missing or invalid API key content: application/json: schema: $ref: '#/components/schemas/PartnerErrorResponse' example: success: false error: code: UNAUTHORIZED message: "missing X-API-Key header" '409': description: No offers available for the requested pair/amount content: application/json: schema: $ref: '#/components/schemas/PartnerErrorResponse' example: success: false error: code: NO_OFFERS_AVAILABLE message: "no matching offers found for the provided criteria" '500': description: Internal Server Error /api/v1/partner/offramp/quote: post: tags: - Off-Ramp summary: Get off-ramp quote description: | Get a price quote for an off-ramp operation (crypto to fiat). Returns the exchange rate, amounts, and a quote ID that can be used to initiate the order. Quotes expire after 60 seconds. The `user_uuid` is the public UUID returned by `POST /partner/users` (the `id` field). The `payment_details_id` is the ID of a previously created payment profile for the user. **Anchor mode — provide exactly one of `crypto_amount` or `fiat_amount`:** | Field | Anchor | Guarantee | |---|---|---| | `crypto_amount` | Crypto-anchored | Exact crypto cost; fiat payout varies by Switch rate at settlement | | `fiat_amount` | Fiat-anchored | Exact fiat delivery to recipient; crypto cost may vary within a 1.20× safety bound | The response `anchor_type` field tells you which side is guaranteed: `"crypto"` or `"fiat"`. **Fiat-anchored notes:** - Useful for exact-payout use cases (e.g. deliver exactly 500,000 NGN). - If Switch quotes a crypto cost exceeding 1.20× the expected amount, the order moves to `failed` status and the deposit is automatically refunded from escrow back to the partner wallet. Create a new quote to retry. - `slippage_tolerance.type=amount` in fiat-anchored mode is evaluated as crypto-side delta, not fiat-side (fiat is always exact by construction). - Integer-minor-unit currencies (NGN, UGX, TZS, KES, GHS): `fiat_amount` must be a whole number. **Fee structure:** The `fee_breakdown` object shows all fee components transparently: `platform_fee` (our fee in crypto), `partner_fee` (partner's markup, currently 0, configurable via dashboard in a future release), and `total_fee` (sum of both). **Address requirement for EUR/AUD/GBP:** EUR, AUD, and GBP payouts require the user's physical address (street, city, postal code). If the user's KYC does not include address data, the quote response will include a `beneficiary_requirements` field indicating which fields are needed. You can update the user's KYC with `PATCH /partner/users/{user_uuid}/kyc` to provide the address before or after initiating the order. If you initiate without address, the order will be created with status `pending_address` until the address is provided. operationId: getOffRampQuote security: - ApiKeyAuth: [] requestBody: required: true content: application/json: schema: $ref: '#/components/schemas/PartnerQuoteRequest' examples: crypto_anchored: summary: Crypto-anchored (exact crypto cost) value: user_uuid: "550e8400-e29b-41d4-a716-446655440000" payment_details_id: 987 crypto_currency: USDT crypto_amount: "100.00" fiat_currency: NGN provider_scope: all slippage_tolerance: type: percent value: 1.5 fiat_anchored: summary: Fiat-anchored (exact fiat delivery) value: user_uuid: "550e8400-e29b-41d4-a716-446655440000" payment_details_id: 987 crypto_currency: USDT fiat_amount: "500000" fiat_currency: NGN provider_scope: all slippage_tolerance: type: percent value: 1.5 responses: '200': description: Quote retrieved successfully content: application/json: schema: allOf: - $ref: '#/components/schemas/APIResponse' - type: object properties: data: $ref: '#/components/schemas/PartnerQuoteResponse' examples: crypto_anchored: summary: Crypto-anchored quote value: success: true data: quote_id: "a1b2c3d4-e5f6-7890-abcd-ef1234567890" anchor_type: "crypto" rate: "1530.12" crypto_amount: "101.50" fiat_amount: "152880" fee_breakdown: platform_fee: "1.50" platform_fee_pct: 1.5 partner_fee: "0.00" partner_fee_pct: 0 total_fee: "1.50" payment_details_id: 987 payment_method_id: 5 payment_network_id: 12 expires_at: "2026-02-01T12:00:30Z" fiat_anchored: summary: Fiat-anchored quote value: success: true data: quote_id: "b2c3d4e5-f6a7-8901-bcde-ef1234567890" anchor_type: "fiat" rate: "1530.12" crypto_amount: "328.10" fiat_amount: "500000" fee_breakdown: platform_fee: "4.92" platform_fee_pct: 1.5 partner_fee: "0.00" partner_fee_pct: 0 total_fee: "4.92" payment_details_id: 987 payment_method_id: 5 payment_network_id: 12 expires_at: "2026-02-01T12:00:30Z" '400': description: Bad request — invalid parameters or missing required fields content: application/json: schema: $ref: '#/components/schemas/PartnerErrorResponse' examples: missing_currency: summary: Missing required field value: success: false error: code: INVALID_REQUEST message: "Missing required field: crypto_currency" ambiguous_amount: summary: Both or neither amount fields provided value: success: false error: code: INVALID_REQUEST message: "exactly one of crypto_amount or fiat_amount must be provided" sub_minor_unit: summary: Fractional fiat for integer-minor-unit currency value: success: false error: code: INVALID_REQUEST message: "fiat_amount must be a whole number for integer-minor-unit currencies" '401': description: Unauthorized — invalid or missing API key content: application/json: schema: $ref: '#/components/schemas/PartnerErrorResponse' example: success: false error: code: UNAUTHORIZED message: "Invalid or missing API key" '404': description: User or payment details not found content: application/json: schema: $ref: '#/components/schemas/PartnerErrorResponse' example: success: false error: code: INVALID_PAYMENT_DETAILS message: "Payment details not found or do not belong to user" '409': description: No offers available for the requested pair content: application/json: schema: $ref: '#/components/schemas/PartnerErrorResponse' example: success: false error: code: NO_OFFERS_AVAILABLE message: "No offers available for USDT/NGN" '500': description: Internal server error content: application/json: schema: $ref: '#/components/schemas/PartnerErrorResponse' /api/v1/partner/offramp/initiate: post: tags: - Off-Ramp summary: Initiate off-ramp order description: | Initiate an off-ramp order using a previously obtained quote. The quote must still be active (not expired or already used). The system refreshes the price and checks slippage tolerance before creating the order. If the price has moved beyond the slippage tolerance, a `PRICE_CHANGED_REQUOTE` error is returned with details about the old and new rates, so the partner can request a new quote. On success, returns the created order with status `awaiting_liquidity_provider`. **Address requirement for EUR/AUD/GBP:** For EUR/AUD/GBP orders, if the user's KYC does not include a physical address, the order will be created with status `pending_address`. The response will include `required_fields: ["address", "city", "postal_code"]` and `next_action: "provide_address"`. Call `PATCH /partner/users/{user_uuid}/kyc` to add the address, and the order will automatically advance. **Recommended flow for EUR/AUD/GBP:** 1. Submit KYC with address/city/postal_code (`POST /kyc-submissions`) 2. Create payment details (`POST /payment-details`) — address auto-populated from KYC 3. Get quote (`POST /offramp/quote`) 4. Initiate (`POST /offramp/initiate`) — proceeds without delays operationId: initiateOffRamp security: - ApiKeyAuth: [] requestBody: required: true content: application/json: schema: $ref: '#/components/schemas/PartnerInitiateRequest' example: quote_id: "a1b2c3d4-e5f6-7890-abcd-ef1234567890" responses: '201': description: Off-ramp order created successfully content: application/json: schema: allOf: - $ref: '#/components/schemas/APIResponse' - type: object properties: data: $ref: '#/components/schemas/PartnerInitiateResponse' example: success: true data: order_id: "b2c3d4e5-f6a7-8901-bcde-f12345678901" status: awaiting_liquidity_provider quote_id: "a1b2c3d4-e5f6-7890-abcd-ef1234567890" amounts: crypto_amount: "101.50" fiat_amount: "152880.00" crypto_currency: USDT fiat_currency: NGN rate: "1530.12" fee_breakdown: platform_fee: "1.50" platform_fee_pct: 1.5 partner_fee: "0.00" partner_fee_pct: 0 total_fee: "1.50" timeline: - status: awaiting_liquidity_provider timestamp: "2026-02-01T12:00:05Z" description: "Order created and awaiting liquidity provider" created_at: "2026-02-01T12:00:05Z" '400': description: Bad request — missing required fields content: application/json: schema: $ref: '#/components/schemas/PartnerErrorResponse' '401': description: Unauthorized — invalid or missing API key content: application/json: schema: $ref: '#/components/schemas/PartnerErrorResponse' '404': description: Quote not found content: application/json: schema: $ref: '#/components/schemas/PartnerErrorResponse' example: success: false error: code: QUOTE_NOT_FOUND message: "Quote not found" '409': description: Quote expired, already used, or slippage exceeded content: application/json: schema: $ref: '#/components/schemas/PartnerErrorResponse' examples: quote_expired: summary: Quote has expired value: success: false error: code: QUOTE_EXPIRED message: "Quote has expired, please request a new quote" quote_already_used: summary: Quote already consumed value: success: false error: code: QUOTE_ALREADY_USED message: "Quote has already been used" slippage_exceeded: summary: Price changed beyond tolerance value: success: false error: code: PRICE_CHANGED_REQUOTE message: "Price has changed beyond slippage tolerance, please request a new quote" details: quoted_rate: 1530.12 current_rate: 1545.00 fiat_difference: 1510.38 '500': description: Internal server error content: application/json: schema: $ref: '#/components/schemas/PartnerErrorResponse' # ============================================ # 11. ORDER MANAGEMENT # ============================================ /api/v1/partner/orders/{order_id}: get: tags: - Orders summary: Order details description: | Get the current status, amounts, and full timeline for a specific order. The `next_action` field indicates what the partner **must** do to advance the order. `null` means no action is required from the partner at this stage — the order is waiting for the counterparty (e.g. the buyer to submit fiat payment proof). The `allowed_actions` array lists all API actions the partner **can** perform at the current status. An action can be in `allowed_actions` even when `next_action` is `null` — this means the action is optional. For example, `confirm-fiat-received` is available at `crypto_received` as a fast-track: if you already know the fiat has arrived (via your own systems), you can confirm immediately without waiting for the buyer's payment proof flow to complete. **Order flow summary:** | Status | next_action | Who acts | |---|---|---| | `created` | — | Partner (or wait for vendor match) | | `awaiting_crypto_transfer_authorization` | `authorize_crypto_transfer` | **Partner** | | `crypto_transfer_authorization_pending` | — | System (on-chain confirmation) | | `crypto_received` | — | **Buyer** submits fiat payment proof | | `fiat_payment_started` | — | System reviews proof | | `fiat_payment_review_started` | — | System accepts/rejects proof | | `awaiting_fiat_received_confirmation` | `confirm_fiat_received` | **Partner** | | `completed` | — | — | **Fiat settlement and confirmation:** You can call `confirm-fiat-received` at any time after crypto is in escrow. You do not need to wait for a system notification. If you know through your own systems that the recipient has received the fiat payment, confirm immediately. Some liquidity providers also send automatic settlement confirmations. When this happens, the order moves to `completed` on its own. If you already confirmed manually, the auto-confirmation is a no-op. If the auto-confirmation arrives first, your manual confirm call is also a no-op. Both paths are safe. **How to implement:** - Always implement a `confirm-fiat-received` call in your flow. - Listen for webhooks. If you receive `status=completed`, no further action needed. - If `next_action=confirm_fiat_received`, call `confirm-fiat-received` when you know fiat has been received. - Do not wait for `next_action` if you already have confirmation from your own banking or payment systems. **Possible statuses:** `created`, `awaiting_liquidity_provider`, `awaiting_crypto_transfer_authorization`, `crypto_transfer_authorization_pending`, `crypto_received`, `fiat_payment_started`, `awaiting_fiat_received_confirmation`, `fiat_received_confirmed`, `crypto_release_pending`, `completed`, `cancelled`, `failed`, `price_changed_requote_needed`. operationId: getOrder security: - ApiKeyAuth: [] parameters: - name: order_id in: path required: true description: Order UUID returned by the initiate endpoint schema: type: string format: uuid example: "b2c3d4e5-f6a7-8901-bcde-f12345678901" responses: '200': description: Order retrieved successfully content: application/json: schema: allOf: - $ref: '#/components/schemas/APIResponse' - type: object properties: data: $ref: '#/components/schemas/PartnerOrderResponse' example: success: true data: order_id: "b2c3d4e5-f6a7-8901-bcde-f12345678901" status: awaiting_crypto_transfer_authorization next_action: authorize_crypto_transfer allowed_actions: ["authorize-crypto-transfer", "cancel"] crypto_transfer_authorization_seconds_left: 1720 amounts: crypto_amount: "101.50" fiat_amount: "152880.00" crypto_currency: USDT fiat_currency: NGN rate: "1530.12" fee_breakdown: platform_fee: "1.50" platform_fee_pct: 1.5 partner_fee: "0.00" partner_fee_pct: 0 total_fee: "1.50" timeline: - status: created timestamp: "2026-02-01T12:00:05Z" description: "Order created" - status: awaiting_crypto_transfer_authorization timestamp: "2026-02-01T12:00:06Z" description: "Awaiting crypto transfer authorization" created_at: "2026-02-01T12:00:05Z" updated_at: "2026-02-01T12:00:06Z" '401': description: Unauthorized — invalid or missing API key content: application/json: schema: $ref: '#/components/schemas/PartnerErrorResponse' '404': description: Order not found or does not belong to this partner content: application/json: schema: $ref: '#/components/schemas/PartnerErrorResponse' example: success: false error: code: ORDER_NOT_FOUND message: "Order not found" '500': description: Internal server error content: application/json: schema: $ref: '#/components/schemas/PartnerErrorResponse' /api/v1/partner/orders/{order_id}/transfer-authorization-parameters: get: tags: - SDK - Orders summary: Get transfer authorization parameters description: | Returns pre-filled parameters required to build and sign the EIP-712 forward request. Use this endpoint first, then sign locally and send the signature to `/api/v1/partner/orders/{order_id}/authorize-crypto-transfer`. operationId: getTransferAuthorizationParameters security: - ApiKeyAuth: [] parameters: - name: order_id in: path required: true description: Order UUID schema: type: string format: uuid responses: '200': description: Transfer authorization parameters returned content: application/json: schema: allOf: - $ref: '#/components/schemas/APIResponse' - type: object properties: data: type: object properties: order_id: type: string format: uuid network_preset: type: string chain_id: type: string rpc_url: type: string description: JSON-RPC URL for the target chain token_address: type: string forwarder_address: type: string recipient_address: type: string description: Escrow address to receive the crypto sender_address: type: string description: Partner wallet address (must match the signing private key) amount_atomic: type: string amount_human: type: string crypto_currency: type: string crypto_decimals: type: integer recommended_gas_limit: type: string recommended_ttl_seconds: type: integer transfer_data: type: string description: Pre-encoded ERC20 transfer(address,uint256) calldata (hex with 0x prefix) forwarder_nonce: type: string description: Current forwarder nonce for the sender address (used in EIP-712 signing) authorization_path: type: string '401': description: Unauthorized '404': description: Order not found /api/v1/partner/orders/{order_id}/authorize-crypto-transfer: post: tags: - SDK - Orders summary: Authorize crypto transfer description: | Submit signed EIP-712 `forward_request` to authorize transfer of order crypto to escrow. **Security note:** Your private key is never sent to the API. The SDK uses it locally to construct and sign the EIP-712 ForwardRequest on your machine. Only the resulting signature and the unsigned forward_request fields are submitted. You can verify this by reading the SDK source code. Recommended: use SDK helper files (only 4 arguments needed): - Python: `https://github.com/Unigox/sdk_python/blob/main/partner_authorize_crypto_transfer_sdk.py` - JavaScript: `https://github.com/Unigox/sdk_javascript/blob/main/partner_authorize_crypto_transfer_sdk.js` The SDK automatically fetches all required parameters (chain, token, forwarder, escrow address, RPC URL) from the `transfer-authorization-parameters` endpoint. SDK helper options: - End-to-end one call: `authorize_crypto_transfer(...)` / `authorizeCryptoTransfer(...)` - Build unsigned request only: `build_forward_request(...)` / `buildForwardRequest(...)` - Sign existing request only: `sign_forward_request(...)` / `signForwardRequest(...)` - Build full request body `{forward_request, signature}`: `build_authorize_crypto_transfer_payload(...)` / `buildAuthorizeCryptoTransferPayload(...)` Manual implementation: call `GET /transfer-authorization-parameters` first, then build the EIP-712 typed data and sign with your partner wallet private key locally. operationId: authorizeCryptoTransfer security: - ApiKeyAuth: [] parameters: - name: order_id in: path required: true description: Order UUID schema: type: string format: uuid example: "b2c3d4e5-f6a7-8901-bcde-f12345678901" requestBody: required: true content: application/json: schema: type: object required: - forward_request - signature properties: forward_request: type: object description: Signed EIP-712 ForwardRequest payload signature: type: string description: Hex-encoded signature from partner wallet example: forward_request: from: "0x674b16054290b4f06D7Ba8e3Ac379E3B0a359a16" to: "0x2222222222222222222222222222222222222222" value: "0" gas: "500000" nonce: "0" deadline: "1760000000" data: "0xa9059cbb000000000000000000000000..." signature: "0xabc123..." responses: '200': description: Crypto authorization accepted '400': description: Bad Request '401': description: Unauthorized '404': description: Order not found '409': description: Order is not in the correct status for crypto authorization '422': description: | Insufficient token balance — the partner wallet does not hold enough tokens to fund the escrow. Top up the wallet and retry. content: application/json: schema: $ref: '#/components/schemas/PartnerErrorResponse' example: success: false error: code: INSUFFICIENT_BALANCE message: "insufficient USDC balance: wallet 0x... has 1928961 but 3000000 is required" '502': description: Transactor service error (e.g. on-chain simulation failed) /api/v1/partner/orders/{order_id}/confirm-fiat-received: post: tags: - Orders summary: Confirm fiat received description: | Confirm that the recipient has received the fiat payment for an off-ramp order. This triggers the crypto release from escrow. Call this endpoint as soon as you know fiat has been received. You do not need to wait for a system prompt. If you have confirmation from your own banking or payment systems, confirm immediately. This endpoint is idempotent. If the order already completed (via automatic settlement confirmation from the liquidity provider), calling this returns the current order status with no side effects. operationId: confirmFiatReceived security: - ApiKeyAuth: [] parameters: - name: order_id in: path required: true description: Order UUID schema: type: string format: uuid example: "b2c3d4e5-f6a7-8901-bcde-f12345678901" responses: '200': description: Fiat receipt confirmed successfully '400': description: Bad Request '401': description: Unauthorized '404': description: Order not found /api/v1/partner/orders/{order_id}/cancel: post: tags: - Orders summary: Cancel order description: | Cancel an order if cancellation is allowed at the current status. Check `allowed_actions` on the order response — `cancel` is available when cancellation is permitted. operationId: cancelOrder security: - ApiKeyAuth: [] parameters: - name: order_id in: path required: true description: Order UUID schema: type: string format: uuid example: "b2c3d4e5-f6a7-8901-bcde-f12345678901" responses: '200': description: Order cancelled successfully '400': description: Bad Request — cancellation not allowed at current status '401': description: Unauthorized '404': description: Order not found /api/v1/partner/orders: get: tags: - Orders summary: List orders description: | Get a paginated list of orders with optional filtering by status. Each order includes `allowed_actions` indicating which API actions are available. operationId: listOrders security: - ApiKeyAuth: [] parameters: - name: status in: query required: false description: Filter by partner order status schema: type: string enum: [created, awaiting_liquidity_provider, awaiting_crypto_transfer_authorization, crypto_transfer_authorization_pending, crypto_received, awaiting_fiat_received_confirmation, fiat_received_confirmed, crypto_release_pending, completed, cancelled, failed, price_changed_requote_needed] example: "completed" - name: page in: query required: false description: Page number (default 1) schema: type: integer default: 1 example: 1 - name: limit in: query required: false description: Results per page (default 20, max 100) schema: type: integer default: 20 maximum: 100 example: 20 responses: '200': description: Orders retrieved successfully content: application/json: schema: allOf: - $ref: '#/components/schemas/APIResponse' - type: object properties: data: type: object properties: orders: type: array items: $ref: '#/components/schemas/PartnerOrderResponse' pagination: type: object properties: page: type: integer example: 1 limit: type: integer example: 20 total_count: type: integer example: 42 total_pages: type: integer example: 3 example: success: true data: orders: - order_id: "b2c3d4e5-f6a7-8901-bcde-f12345678901" status: completed next_action: null allowed_actions: [] amounts: crypto_amount: "101.50" fiat_amount: "152880.00" crypto_currency: USDT fiat_currency: NGN rate: "1530.12" fee_breakdown: platform_fee: "1.50" platform_fee_pct: 1.5 partner_fee: "0.00" partner_fee_pct: 0 total_fee: "1.50" timeline: - status: created timestamp: "2026-02-01T12:00:05Z" description: "Order created" - status: completed timestamp: "2026-02-01T12:30:00Z" description: "Order completed" created_at: "2026-02-01T12:00:05Z" updated_at: "2026-02-01T12:30:00Z" pagination: page: 1 limit: 20 total_count: 42 total_pages: 3 '401': description: Unauthorized — invalid or missing API key content: application/json: schema: $ref: '#/components/schemas/PartnerErrorResponse' # ============================================ # 12. WEBHOOKS # ============================================ /api/v1/partner/webhooks: post: tags: - Webhooks summary: Register webhook URL description: | Register or update the webhook URL for your partner account. The URL must use HTTPS. Once registered, the webhook is automatically enabled and will start receiving order status events. operationId: registerWebhook security: - ApiKeyAuth: [] requestBody: required: true content: application/json: schema: $ref: '#/components/schemas/RegisterWebhookRequest' example: url: "https://partner.example.com/webhooks/unigox" responses: '200': description: Webhook registered successfully content: application/json: schema: allOf: - $ref: '#/components/schemas/APIResponse' - type: object properties: data: $ref: '#/components/schemas/WebhookConfigResponse' example: success: true data: url: "https://partner.example.com/webhooks/unigox" enabled: true '400': description: Bad request — missing or invalid URL content: application/json: schema: $ref: '#/components/schemas/ErrorResponse' examples: url_required: summary: URL is required value: success: false data: error_key: "webhook URL is required" url_not_https: summary: URL must use HTTPS value: success: false data: error_key: "webhook URL must use HTTPS" '401': description: Unauthorized — invalid or missing API key get: tags: - Webhooks summary: Get webhook config description: | Returns the current webhook configuration for your partner account, including the URL and whether the webhook is enabled. operationId: getWebhook security: - ApiKeyAuth: [] responses: '200': description: Webhook configuration retrieved content: application/json: schema: allOf: - $ref: '#/components/schemas/APIResponse' - type: object properties: data: $ref: '#/components/schemas/WebhookConfigResponse' example: success: true data: url: "https://partner.example.com/webhooks/unigox" enabled: true '401': description: Unauthorized — invalid or missing API key /api/v1/partner/webhooks/{id}: delete: tags: - Webhooks summary: Delete webhook description: | Remove the webhook URL and disable webhook delivery for your partner account. No further events will be sent until a new URL is registered. operationId: deleteWebhook security: - ApiKeyAuth: [] parameters: - name: id in: path required: true description: Webhook ID (any value accepted — the system uses the authenticated partner context) schema: type: string example: "1" responses: '200': description: Webhook deleted successfully content: application/json: schema: allOf: - $ref: '#/components/schemas/APIResponse' - type: object properties: data: type: object properties: message: type: string example: "webhook deleted" example: success: true data: message: "webhook deleted" '401': description: Unauthorized — invalid or missing API key /api/v1/partner/webhooks/test: post: tags: - Webhooks summary: Test webhook description: | Send a signed test event to your configured webhook URL. Uses the same HMAC-SHA256 signature scheme as production events. Use this to verify your endpoint is reachable and correctly validating signatures. operationId: testPingWebhook security: - ApiKeyAuth: [] responses: '200': description: Test event delivered successfully content: application/json: schema: allOf: - $ref: '#/components/schemas/APIResponse' - type: object properties: data: $ref: '#/components/schemas/WebhookTestPingResponse' example: success: true data: delivered: true http_status: 200 '400': description: Webhook URL not configured content: application/json: schema: $ref: '#/components/schemas/ErrorResponse' example: success: false data: error_key: "webhook URL is not set" '401': description: Unauthorized — invalid or missing API key '502': description: Webhook delivery failed — target endpoint returned non-2xx or timed out content: application/json: schema: $ref: '#/components/schemas/ErrorResponse' example: success: false data: error_key: "webhook delivery failed" components: schemas: APIResponse: type: object required: - success properties: success: type: boolean description: Indicates if the request was successful example: true data: description: Response data (structure varies by endpoint) or error information if success is false ErrorResponse: type: object required: - success - data properties: success: type: boolean example: false data: type: object required: - error_key properties: error_key: type: string description: Standardized error key for client error handling example: "required_fields_missing" CryptoCurrencyCapability: type: object properties: code: type: string description: Cryptocurrency code (e.g., BTC, ETH) example: BTC name: type: string description: Cryptocurrency name example: Bitcoin contract: type: string description: Token contract address (for ERC-20 tokens) example: "0x..." decimals: type: integer format: int64 description: Token decimals (number of decimal places) example: 8 blockchains: type: array items: type: string description: List of supported blockchain names/slugs example: ["ethereum", "polygon"] CryptoCurrenciesResponse: allOf: - $ref: '#/components/schemas/APIResponse' - type: object properties: data: type: array items: $ref: '#/components/schemas/CryptoCurrencyCapability' BlockchainCapability: type: object properties: name: type: string description: Blockchain name example: Ethereum slug: type: string description: Blockchain slug identifier example: ethereum ticker: type: string description: Blockchain ticker symbol example: eth crypto_currencies: type: array items: $ref: '#/components/schemas/CryptoCurrencyCapability' description: List of cryptocurrencies supported on this blockchain BlockchainsResponse: allOf: - $ref: '#/components/schemas/APIResponse' - type: object properties: data: type: array items: $ref: '#/components/schemas/BlockchainCapability' FiatCurrencyCapability: type: object properties: code: type: string description: Fiat currency code (ISO 4217) example: USD name: type: string description: Fiat currency name example: United States Dollar institution_id: type: array items: type: string description: List of payment method slugs that support this currency (retrieved from active payment method options) example: ["wise", "revolut", "bank-transfer"] countries: type: array items: type: string description: List of ISO 2-letter country codes where this currency is supported (retrieved from active payment method options) example: ["US", "GB", "EU"] onramp: type: boolean description: | Whether onramp (fiat to crypto) is supported. Currently set to true if there are any active payment method options for this currency. This will be refined in future updates based on trade types and explicit direction configuration. example: true offramp: type: boolean description: | Whether offramp (crypto to fiat) is supported. Currently set to true if there are any active payment method options for this currency. This will be refined in future updates based on trade types and explicit direction configuration. example: true FiatCurrenciesResponse: allOf: - $ref: '#/components/schemas/APIResponse' - type: object properties: data: type: array items: $ref: '#/components/schemas/FiatCurrencyCapability' CorridorRateQuote: type: object description: | One partner-type variant of a corridor rate (P2P or Licensed). properties: rate: type: number format: double nullable: true description: All-in fiat-per-USDT rate (platform fee already applied). example: 1542.6 min_notional_fiat: type: number format: double nullable: true description: Smallest amount accepted by any active offer in this variant, in the corridor's local currency. Set only when it exceeds the reference notional in dollar terms — i.e. the customer would need to size up to use this variant. example: 3465 CorridorDirectionInfo: type: object description: | Per-direction info for one fiat corridor. The whole object is omitted when no active offer exists in that direction. Either `p2p` or `licensed` (or both) will be present. properties: supported: type: boolean description: Always `true` when the object is present (the object is omitted otherwise). Reserved for future states where a corridor may be configured but temporarily without liquidity. example: true p2p: $ref: '#/components/schemas/CorridorRateQuote' licensed: $ref: '#/components/schemas/CorridorRateQuote' Corridor: type: object properties: code: type: string example: NGN name: type: string example: Nigerian Naira mid_market_rate: type: number format: double nullable: true description: Open Exchange Rates reference rate (fiat per USDT) for this currency. Same value applies to both directions. example: 1358.97 mid_market_updated_at: type: string format: date-time nullable: true example: '2026-04-27T15:56:11Z' unigox_fee_pct: type: number format: double nullable: true description: Cheapest active Unigox platform fee for this corridor, in percent. Already baked into every `rate` below. example: 0.25 onramp: $ref: '#/components/schemas/CorridorDirectionInfo' offramp: $ref: '#/components/schemas/CorridorDirectionInfo' SupportedCorridorsPayload: type: object properties: notional_usdt: type: number format: double example: 500 generated_at: type: string format: date-time example: '2026-04-27T15:56:11Z' currencies: type: array items: $ref: '#/components/schemas/Corridor' SupportedCorridorsResponse: allOf: - $ref: '#/components/schemas/APIResponse' - type: object properties: data: $ref: '#/components/schemas/SupportedCorridorsPayload' PaymentField: type: object properties: key: type: string description: Field identifier/key example: vpa label: type: string description: Human-readable field label example: UPI ID type: type: string description: Field input type (text, number, select, etc.) example: text required: type: boolean description: Whether this field is required example: true pattern: type: string description: Regex pattern for validation (optional) example: "^[\\w.-]+@[\\w.-]+$" placeholder: type: string description: Placeholder text for input (optional) example: "username@upi" lookup: type: boolean description: Whether field can be used for lookup (optional) example: true options: type: array description: Options for select fields (optional) items: type: object properties: code: type: string example: CBU name: type: string example: "CBU (Traditional Bank)" Rails: type: object properties: slug: type: string description: Payment rail slug identifier example: upi-india name: type: string description: Payment rail name example: UPI India institution_required: type: boolean description: Whether institution (bank) selection is required by the rail itself. In the current partner API, omission of `institution_id` is explicitly supported only for `iban-sepa` and `nip-nigeria`. example: false institution_lookup_key: type: string nullable: true description: Lookup key for institution search, e.g., ifsc or sort_code example: ifsc currencies: type: array items: type: string description: List of supported fiat currency codes example: ["INR"] settlement: type: object nullable: true description: Settlement information properties: speed: type: string enum: [INSTANT, SAME_DAY, NEXT_DAY, STANDARD] description: Settlement speed example: INSTANT typical_minutes: type: integer description: Typical settlement time in minutes example: 2 note: type: string nullable: true description: Optional note about settlement limits: type: object nullable: true description: Transaction limits properties: min_amount: type: number nullable: true description: Minimum transaction amount max_amount: type: number nullable: true description: Maximum transaction amount (null = no limit) max_per_transaction: type: number nullable: true description: Maximum per transaction (null = no limit) fields: type: array items: $ref: '#/components/schemas/PaymentField' description: Required fields for payment details input PaymentCapabilityResponse: allOf: - $ref: '#/components/schemas/APIResponse' - type: object properties: data: type: object required: - country - direction - rails properties: country: type: string description: Country code used in the query example: NG currency: type: string description: Currency code (from query parameter or first rail) example: NGN direction: type: string enum: [onramp, offramp] description: Transaction direction example: offramp rails: type: array items: $ref: '#/components/schemas/Rails' description: List of available payment rails links: type: object additionalProperties: type: string description: Related API endpoints example: search_institutions: "/api/v1/supported/institutions" InstitutionBranch: type: object nullable: true properties: code: type: string description: Branch code (e.g., IFSC, sort code) example: HDFC0001234 name: type: string description: Branch name example: Mumbai Main Branch address: type: string description: Branch address example: "123 Financial Street, Mumbai 400001" Institution: type: object properties: id: type: string description: Institution identifier example: hdfc name: type: string description: Institution name example: HDFC Bank type: type: string description: Institution type (e.g., BANK, WALLET) example: BANK code: type: string nullable: true description: Institution code for lookup (e.g., IFSC, sort code) example: HDFC0001234 branch: $ref: '#/components/schemas/InstitutionBranch' supported_rails: type: array items: type: string description: List of payment rail slugs this institution supports example: ["imps-india", "upi-india"] Pagination: type: object properties: total: type: integer description: Total number of items example: 1 limit: type: integer description: Items per page example: 20 offset: type: integer description: Current offset example: 0 InstitutionsResponse: allOf: - $ref: '#/components/schemas/APIResponse' - type: object properties: data: type: object required: - institutions - pagination properties: institutions: type: array items: $ref: '#/components/schemas/Institution' pagination: $ref: '#/components/schemas/Pagination' SupportedPair: type: object properties: crypto_currency_code: type: string description: Cryptocurrency code (e.g., USDC, BTC) example: USDC fiat_currency_code: type: string description: Fiat currency code (e.g., USD, EUR) example: USD type: type: string enum: [BUY, SELL] description: Trading pair type - BUY (fiat to crypto) or SELL (crypto to fiat) example: BUY SupportedPairsResponse: allOf: - $ref: '#/components/schemas/APIResponse' - type: object properties: data: type: array items: $ref: '#/components/schemas/SupportedPair' CreateUserResponse: allOf: - $ref: '#/components/schemas/APIResponse' - type: object properties: data: type: object properties: user_uuid: type: string format: uuid description: Public UUID of the user. Use this value in all subsequent API calls. example: "550e8400-e29b-41d4-a716-446655440000" email: type: string format: email example: user@example.com kyc_status: type: string enum: [NOT_INITIATED, IN_PROGRESS, VERIFIED, VERIFICATION_REJECTED] example: NOT_INITIATED created_at: type: string format: date-time example: "2026-01-06T14:30:00Z" GetUserResponse: allOf: - $ref: '#/components/schemas/APIResponse' - type: object properties: data: type: object properties: user_uuid: type: string format: uuid description: Public UUID of the user. Use this value in all subsequent API calls. example: "550e8400-e29b-41d4-a716-446655440000" email: type: string format: email example: user@example.com created_at: type: string format: date-time example: "2026-01-06T14:30:00Z" kyc: type: object properties: status: type: string enum: [NOT_INITIATED, IN_PROGRESS, VERIFIED, VERIFICATION_REJECTED] example: IN_PROGRESS kyc_method: type: string enum: [direct_data, external_token, handoff] description: KYC submission method used example: direct_data first_name: type: string description: User's first name from KYC data example: John last_name: type: string description: User's last name from KYC data example: Doe country_code: type: string description: User's country code (ISO 2-letter) from KYC data example: NG rejection_reason: type: string nullable: true description: Reason for KYC rejection (if status is VERIFICATION_REJECTED) example: "Verification declined by AiPrise" updated_at: type: string format: date-time description: Last update timestamp for KYC status example: "2026-01-06T14:30:00Z" payment_profiles: type: array items: type: object properties: id: type: string example: "987" rail: type: string example: nip-nigeria currency: type: string example: NGN institution_name: type: string nullable: true example: Guaranty Trust Bank institution_id: type: string nullable: true description: Institution identifier (our internal slug) example: guaranty-trust-bank details: type: object additionalProperties: true example: account_number: "0123456789" KYCSubmitDirectData: type: object required: [method, data] properties: method: type: string enum: [direct_data] example: direct_data data: type: object required: [pii] properties: pii: type: object required: [first_name, last_name, country_code] properties: first_name: type: string example: John last_name: type: string example: Doe country_code: type: string example: NG dob: type: string format: date example: "1990-01-01" middle_name: type: string example: Michael phone_number: type: string description: E.164 format example: "+2348012345678" id_number: type: string example: "A12345678" id_type: type: string enum: [NATIONAL_ID, DRIVER_LICENSE, PASSPORT] example: NATIONAL_ID address: type: string description: Street address example: "Svitrigailos g 29" city: type: string description: City. Required for EUR/AUD/GBP offramp (auto-populates into payment details). example: "Vilnius" postal_code: type: string example: "03229" KYCSubmitExternalToken: type: object required: [method, data] properties: method: type: string enum: [external_token] example: external_token data: type: object required: [provider] description: | Provider-specific fields: - SumSub: requires `token` (SumSub share token) - Persona: requires `inquiry_id` (Persona inquiry ID, e.g. inq_xxx) properties: provider: type: string enum: [sumsub, persona] description: KYC provider name example: persona token: type: string description: SumSub share token (required when provider=sumsub) example: "sumsub_external_session_id" inquiry_id: type: string description: Persona inquiry ID (required when provider=persona). This is the inq_xxx identifier returned by Persona when the inquiry was created. example: "inq_CWQzc5afzD4dmF66EJqjdPC2T79C" KYCSubmitHandoff: type: object required: [method, data] properties: method: type: string enum: [handoff] example: handoff data: type: object properties: pii: type: object properties: first_name: type: string example: John last_name: type: string example: Doe country_code: type: string example: NG KYCSubmitResponse: allOf: - $ref: '#/components/schemas/APIResponse' - type: object properties: data: type: object properties: kyc_status: type: string enum: [NOT_INITIATED, IN_PROGRESS, VERIFIED, VERIFICATION_REJECTED] example: IN_PROGRESS message: type: string example: "PII data accepted" verification_url: type: string format: uri nullable: true description: Verification URL (only returned for handoff method) example: "https://verify.aiprise.com/?verification_session_id=4fd410c9-ba9b-46cf-a4f0-132204a9a564" VerificationStatusResponse: allOf: - $ref: '#/components/schemas/APIResponse' - type: object properties: data: type: object properties: status: type: string enum: [NOT_STARTED, PENDING, IN_PROGRESS, COMPLETED, VERIFIED, FAILED, VERIFICATION_REJECTED] description: | Current verification status. Recommended partner interpretation: - `NOT_STARTED`, `PENDING`, `IN_PROGRESS` — still processing - `VERIFIED` — approved / successful verification - `VERIFICATION_REJECTED` — completed with rejection - `FAILED` — terminal error - `COMPLETED` — provider workflow completed, but partners should not treat this alone as approval; wait for `VERIFIED` to consider the user approved example: "IN_PROGRESS" verification_url: type: string format: uri nullable: true description: Verification URL (for handoff method) example: "https://verify-sandbox.aiprise.com/?verification_session_id=abc123" verification_seconds_left: type: integer nullable: true description: Seconds remaining for verification session (if applicable) example: 3600 provider_messages: type: array items: type: string nullable: true description: Provider-specific messages or rejection reasons example: ["Verification in progress"] CreatePaymentDetailsRequest: description: Conditional request schema for partner payment details. `institution_id` may be omitted only for `iban-sepa` and `nip-nigeria`. oneOf: - $ref: '#/components/schemas/CreatePaymentDetailsRequestInstitutionOptional' - $ref: '#/components/schemas/CreatePaymentDetailsRequestInstitutionRequired' CreatePaymentDetailsRequestBase: type: object required: [currency, rail, details] properties: currency: type: string description: ISO 3-letter fiat currency code (e.g., INR, EUR, USD) example: INR rail: type: string description: Payment rail identifier (e.g., "upi-india", "iban-sepa", "imps-india") example: iban-sepa details: type: object description: Payment details object with fields specific to the payment network (e.g., account_number, vpa, iban, full_name) additionalProperties: true example: full_name: "John Doe" iban: DE89370400440532013000 country_code: type: string description: Optional ISO 2-letter country code example: DE CreatePaymentDetailsRequestInstitutionOptional: allOf: - $ref: '#/components/schemas/CreatePaymentDetailsRequestBase' - type: object properties: rail: type: string enum: [iban-sepa, nip-nigeria] description: Payment rail identifier for rails that support omission of `institution_id` example: iban-sepa institution_id: type: string description: | Optional for `iban-sepa` and `nip-nigeria`. If omitted, the backend assigns the generic `other-bank` payment method automatically. example: other-bank CreatePaymentDetailsRequestInstitutionRequired: allOf: - $ref: '#/components/schemas/CreatePaymentDetailsRequestBase' - type: object required: [institution_id] properties: rail: type: string description: Payment rail identifier for rails that require institution selection not: enum: [iban-sepa, nip-nigeria] example: cvu-cbu institution_id: type: string description: | Institution (payment method) identifier - our internal slug (e.g., "wise", "hdfc-bank", "revolut", "uala", "banco-santander-argentina"). Required for all rails except `iban-sepa` and `nip-nigeria`. If omitted for any other rail, the API returns a validation error. For rails with multiple formats (e.g., CVU/CBU), the format is automatically determined based on the payment method type of this institution. example: wise PaymentProfile: type: object properties: payment_details_id: type: string description: Payment details ID. Use this value in offramp/onramp quote requests. example: "987" rail: type: string description: Payment rail identifier example: nip-nigeria currency: type: string description: Fiat currency code example: NGN institution_name: type: string nullable: true description: Institution (bank) name if applicable example: Guaranty Trust Bank institution_id: type: string nullable: true description: Institution identifier (our internal slug) example: guaranty-trust-bank details: type: object description: Payment details (account number, VPA, IBAN, etc.) additionalProperties: true example: account_number: "0123456789" CreatePaymentDetailsResponse: allOf: - $ref: '#/components/schemas/APIResponse' - type: object properties: data: $ref: '#/components/schemas/PaymentProfile' GetPaymentDetailsResponse: allOf: - $ref: '#/components/schemas/APIResponse' - type: object properties: data: type: array items: $ref: '#/components/schemas/PaymentProfile' description: List of all payment profiles for the user # ============================================ # Off-Ramp & Order Schemas # ============================================ SlippageTolerance: type: object properties: type: type: string enum: [percent, amount, fiat] description: | Tolerance type: - `percent` — percentage of the quoted rate (e.g., 1.5 = 1.5%) - `amount` — absolute fiat amount (e.g., 50.00 = 50 NGN) - `fiat` — alias of `amount` example: percent value: type: number format: double description: Tolerance value (percentage or fiat amount depending on type) example: 1.5 LiquidityPair: type: object required: - crypto_currency_code - fiat_currency_code - trade_type - min_amount - max_amount - min_amount_usd - max_amount_usd - payment_methods properties: crypto_currency_code: type: string description: Cryptocurrency code (e.g. USDC, BTC) example: USDC fiat_currency_code: type: string description: Fiat currency code (ISO 4217) example: EUR trade_type: type: string enum: [BUY, SELL] description: | Partner-facing trade direction. `SELL` — partner sells crypto and receives fiat (off-ramp). `BUY` — partner buys crypto by sending fiat (on-ramp). example: SELL min_amount: type: number format: double description: Minimum fiat amount accepted across all eligible offers, in the pair's native fiat currency example: 50.00 max_amount: type: number format: double description: Maximum fiat amount accepted across all eligible offers, in the pair's native fiat currency example: 10000.00 min_amount_usd: type: number format: double description: Minimum fiat amount converted to USD example: 54.35 max_amount_usd: type: number format: double description: Maximum fiat amount converted to USD example: 10870.00 payment_methods: type: array items: type: string description: Deduplicated, alphabetically sorted list of payment method slugs available for this pair example: - sepa - wise PartnerPriceEstimateRequest: type: object required: [crypto_currency, fiat_currency] oneOf: - required: [crypto_amount] - required: [fiat_amount] properties: crypto_currency: type: string description: Cryptocurrency code (e.g., USDT, USDC) example: USDT fiat_currency: type: string description: Target fiat currency code (e.g., NGN, USD, EUR) example: NGN crypto_amount: type: string description: Crypto amount to estimate from. Provide exactly one of crypto_amount or fiat_amount. example: "100.00" fiat_amount: type: string description: Fiat amount to estimate from. Provide exactly one of crypto_amount or fiat_amount. example: "150000.00" payment_method_slug: type: string description: Optional payment method slug filter to narrow routing example: access-bank payment_network_slug: type: string description: Optional payment network slug filter to narrow routing example: nibss provider_scope: type: string description: Optional provider scope filter; defaults to all enum: [all, licensed_only, p2p_only] example: all country_code: type: string description: Optional ISO country code (alpha-2) used for fee/routing context example: NG PartnerPriceEstimateResponse: type: object properties: rate: type: string description: Indicative crypto-to-fiat exchange rate example: "1530.12" crypto_amount: type: string description: Estimated total crypto amount (includes fees) example: "101.50" fiat_amount: type: string description: Estimated fiat amount example: "152880.00" fee_breakdown: $ref: '#/components/schemas/FeeBreakdown' payment_method_slug: type: string description: Matched payment method slug used for estimate example: access-bank payment_network_slug: type: string description: Matched payment network slug used for estimate example: nibss provider_scope: type: string description: Applied provider scope example: all is_indicative: type: boolean description: True when response is an estimate and not a reserving quote example: true PartnerQuoteRequest: type: object required: [user_uuid, payment_details_id, crypto_currency, fiat_currency] oneOf: - required: [crypto_amount] - required: [fiat_amount] properties: user_uuid: type: string format: uuid description: Public UUID of the end-user (returned by `POST /partner/users` as `id`) example: "550e8400-e29b-41d4-a716-446655440000" payment_details_id: type: integer format: int64 description: ID of a previously created payment profile for the user example: 987 crypto_currency: type: string description: Cryptocurrency code (e.g., USDT, USDC) example: USDT crypto_amount: type: string description: | Amount of crypto to sell (standard units, e.g., "100.00" for 100 USDT). Provide exactly one of `crypto_amount` or `fiat_amount`. When provided, the order is **crypto-anchored** — exact crypto cost is guaranteed, fiat payout varies by rate at settlement. example: "100.00" fiat_amount: type: string description: | Exact fiat amount the recipient will receive (e.g., "500000" for 500,000 NGN). Provide exactly one of `crypto_amount` or `fiat_amount`. When provided, the order is **fiat-anchored** — exact fiat delivery is guaranteed, crypto cost may vary within a 1.20× safety bound. For integer-minor-unit currencies (NGN, UGX, TZS, KES, GHS), must be a whole number. example: "500000" fiat_currency: type: string description: Target fiat currency code (e.g., NGN, USD, EUR) example: NGN rail: type: string description: Optional rail hint; must be compatible with payment details if provided example: Bank Transfer route: type: string description: Optional route hint; must be compatible with payment details if provided example: Instant Transfer provider_scope: type: string description: Optional provider scope filter; defaults to all enum: [all, licensed_only, p2p_only] example: all slippage_tolerance: allOf: - $ref: '#/components/schemas/SlippageTolerance' description: | Optional. Maximum tolerated price movement between `/quote` and `/initiate`, and between `/initiate` and vendor acceptance. When omitted, the default is `percent` / `0` — any rate movement returns `PRICE_CHANGED_REQUOTE` and the partner must request a fresh quote. Set this to opt in to silent execution for small market movements. has_fiat_settlement_notification: type: boolean description: | When `true`, only match liquidity providers that send fiat settlement notifications. Orders from these providers complete automatically when fiat is delivered. When omitted or `false`, all providers are eligible. PartnerQuoteResponse: type: object properties: quote_id: type: string format: uuid description: Unique quote identifier — use this to initiate the order example: "a1b2c3d4-e5f6-7890-abcd-ef1234567890" anchor_type: type: string enum: [crypto, fiat] description: | Indicates which amount is guaranteed at settlement. - `crypto` — `crypto_amount` is exact; `fiat_amount` may vary by Switch rate at settlement. - `fiat` — `fiat_amount` is exact; `crypto_amount` may vary within a 1.20× safety bound. example: "crypto" rate: type: string description: Crypto-to-fiat exchange rate at quote time example: "1530.12" crypto_amount: type: string description: Total crypto amount the user sends (includes all fees) example: "101.50" fiat_amount: type: string description: Fiat amount the recipient receives example: "152880.00" fee_breakdown: $ref: '#/components/schemas/FeeBreakdown' payment_details_id: type: integer format: int64 description: Payment details ID used for this quote example: 987 payment_method_id: type: integer format: int64 description: Matched payment method ID example: 5 payment_network_id: type: integer format: int64 description: Matched payment network ID example: 12 expires_at: type: string format: date-time description: Quote expiration timestamp (30 seconds from creation) example: "2026-02-01T12:00:30Z" has_fiat_settlement_notification: type: boolean description: | Whether the matched liquidity provider sends fiat settlement notifications. When `true`, the order will complete automatically when fiat is delivered to the recipient. When `false`, you must call `confirm-fiat-received`. You can call `confirm-fiat-received` at any time regardless of this value. example: true PartnerInitiateRequest: type: object required: [quote_id] properties: quote_id: type: string format: uuid description: Quote ID from the quote endpoint. Payment details are taken from the quote. example: "a1b2c3d4-e5f6-7890-abcd-ef1234567890" reference_id: type: string description: Optional partner-supplied external reference ID (e.g. your internal order ID). Stored on the order and echoed back in responses. example: "your-internal-order-id" PartnerInitiateResponse: type: object properties: order_id: type: string format: uuid description: Unique order identifier for tracking example: "b2c3d4e5-f6a7-8901-bcde-f12345678901" status: $ref: '#/components/schemas/PartnerOrderStatus' quote_id: type: string format: uuid description: Quote ID that was consumed example: "a1b2c3d4-e5f6-7890-abcd-ef1234567890" reference_id: type: string nullable: true description: Partner-supplied external reference ID (echoed from initiate request) example: "your-internal-order-id" order_type: type: string enum: [offramp, onramp] description: Direction of the order example: offramp amounts: $ref: '#/components/schemas/PartnerOrderAmounts' fee_breakdown: $ref: '#/components/schemas/FeeBreakdown' timeline: type: array items: $ref: '#/components/schemas/TimelineEntry' created_at: type: string format: date-time example: "2026-02-01T12:00:05Z" PartnerOrderResponse: type: object properties: order_id: type: string format: uuid description: Unique order identifier example: "b2c3d4e5-f6a7-8901-bcde-f12345678901" status: $ref: '#/components/schemas/PartnerOrderStatus' next_action: type: string nullable: true description: | The action the partner **must** perform to advance the order. `null` means no action is required from the partner at this stage — the order is waiting for the counterparty or for an automated system step. Possible values: - `authorize_crypto_transfer` — partner must authorize escrow funding - `confirm_fiat_received` — partner must confirm they received the fiat payment - `request_new_quote` — price changed, partner must request a new quote - `update_kyc_address` — address fields are required for EUR/AUD/GBP payouts; see `action_required` for the endpoint and fields to submit - `null` — no required action (waiting for buyer or system) enum: [authorize_crypto_transfer, confirm_fiat_received, request_new_quote, update_kyc_address, null] example: authorize_crypto_transfer action_required: type: object nullable: true description: | Present when the order needs partner intervention before it can proceed. Contains machine-readable type, human-readable message, the API endpoint to call, and the fields to provide. Null/omitted when no action is needed. required: [type, message] properties: type: type: string description: Machine-readable action identifier example: update_kyc_address message: type: string description: Human-readable explanation of what is needed and why example: "EUR/AUD/GBP bank transfers require the recipient's physical address. Update the user's KYC before the order can proceed." endpoint: type: string description: API endpoint to call to resolve this action example: "PATCH /api/v1/partner/users/{user_uuid}/kyc" fields: type: array description: Fields the partner needs to provide items: type: object required: [field, label] properties: field: type: string description: API field name example: city label: type: string description: Human-readable description of the field example: "City of the recipient" allowed_actions: type: array items: type: string description: | List of API actions the partner **can** perform at the current status. An action may be present here even when `next_action` is `null` — meaning it is optional. Notable case: `confirm-fiat-received` appears at both `crypto_received` and `awaiting_fiat_received_confirmation`. At `crypto_received` it is a **fast-track**: if you already know the fiat has arrived via your own systems, you can confirm immediately. Otherwise, wait for the buyer's proof flow to complete — the order will reach `awaiting_fiat_received_confirmation` automatically. Status-to-actions mapping: - `created` → `["cancel"]` - `awaiting_liquidity_provider` → `["cancel"]` - `awaiting_crypto_transfer_authorization` → `["authorize-crypto-transfer", "cancel"]` - `crypto_transfer_authorization_pending` → `["cancel"]` - `crypto_received` → `["confirm-fiat-received"]` (optional fast-track) - `fiat_payment_started` → `["confirm-fiat-received"]` (optional fast-track after buyer proof submission) - `awaiting_fiat_received_confirmation` → `["confirm-fiat-received"]` (required) - `fiat_received_confirmed`, `crypto_release_pending`, `completed`, `cancelled`, `failed` → `[]` example: ["authorize-crypto-transfer", "cancel"] crypto_transfer_authorization_seconds_left: type: integer format: int64 nullable: true description: | Remaining seconds to authorize crypto transfer while order is in `awaiting_crypto_transfer_authorization` / `crypto_transfer_authorization_pending`. `null` for all other statuses. example: 1720 amounts: $ref: '#/components/schemas/PartnerOrderAmounts' fee_breakdown: $ref: '#/components/schemas/FeeBreakdown' timeline: type: array items: $ref: '#/components/schemas/TimelineEntry' description: Chronological list of status changes has_fiat_settlement_notification: type: boolean description: | Whether the matched liquidity provider sends fiat settlement notifications. When `true`, the order will complete automatically when fiat is delivered. When `false`, you must call `confirm-fiat-received`. You can call `confirm-fiat-received` at any time regardless of this value. example: true created_at: type: string format: date-time example: "2026-02-01T12:00:05Z" updated_at: type: string format: date-time example: "2026-02-01T12:00:06Z" order_type: type: string nullable: true description: | Order type. Present for on-ramp orders. Absent for off-ramp orders. - `onramp` — buy crypto with fiat example: onramp vendor_payment_details: type: object nullable: true description: | Vendor's payment details for the end-user to send fiat to. Present only for on-ramp orders at `awaiting_fiat_transfer` status and beyond. properties: payment_method_name: type: string example: "Agribank" payment_network_name: type: string example: "Napas Vietnam" details: type: object description: Payment method-specific fields (e.g. account_number, phone) payment_request: type: boolean nullable: true description: | `true` if the vendor uses payment request flow — the partner must call `submit-payer-details` before `confirm-payment-sent`. example: false send_out_tx_hash: type: string nullable: true description: | Destination-chain transaction hash after a send-out completes. Present only for on-ramp orders with `send_out_completed` status. example: "0xd413bf3b8fe3b1d86b1e6d4c40a82eb8c94824202ff9a010161301400ab1552d" PartnerOrderAmounts: type: object properties: crypto_amount: type: string description: Total crypto amount (standard units) example: "101.50" fiat_amount: type: string description: Fiat amount the user receives example: "152880.00" crypto_currency: type: string description: Cryptocurrency code example: USDT fiat_currency: type: string description: Fiat currency code example: NGN rate: type: string description: Crypto-to-fiat exchange rate example: "1530.12" FeeBreakdown: type: object description: Transparent breakdown of all fee components (amounts in crypto) properties: platform_fee: type: string description: Platform fee in crypto example: "1.50" platform_fee_pct: type: number format: double description: Platform fee as a percentage example: 1.5 partner_fee: type: string description: Partner's fee markup in crypto (0 until partner fee dashboard is available) example: "0.00" partner_fee_pct: type: number format: double description: Partner's fee percentage (0 until partner fee dashboard is available) example: 0 total_fee: type: string description: Total fee in crypto (platform_fee + partner_fee) example: "1.50" PartnerOrderStatus: type: string description: | Order status. **Off-ramp statuses (crypto → fiat):** - `created` — order created, quote locked - `awaiting_liquidity_provider` — order initiated, waiting for vendor acceptance - `awaiting_crypto_transfer_authorization` — partner must authorize crypto transfer via EIP-712 signature - `crypto_transfer_authorization_pending` — authorization submitted, awaiting on-chain confirmation - `crypto_received` — crypto locked in escrow - `fiat_payment_started` — buyer submitted fiat payment proof - `awaiting_fiat_received_confirmation` — waiting for partner to confirm fiat receipt - `fiat_received_confirmed` — fiat confirmed, crypto release pending - `crypto_release_pending` — escrow release in progress - `completed` — order fully completed - `cancelled` — order cancelled - `failed` — terminal error - `price_changed_requote_needed` — slippage exceeded at initiation (not a trade status) **On-ramp statuses (fiat → crypto):** - `awaiting_liquidity_provider` — order initiated, waiting for vendor acceptance - `awaiting_vendor_escrow_funding` — vendor accepted, funding escrow with crypto - `awaiting_fiat_transfer` — crypto locked in escrow, partner must confirm payment sent - `fiat_transfer_pending` — fiat payment confirmed, awaiting vendor confirmation of receipt - `completed` — vendor confirmed fiat received, crypto released to end-user wallet - `cancelled` — order cancelled - `failed` — escrow error **On-ramp send-out statuses (optional post-trade cross-chain transfer):** > After an on-ramp order reaches `completed`, the partner may optionally initiate a > **send-out** — a cross-chain transfer of the received crypto to a destination network > (e.g. XAI → Arbitrum). This is an additional flow and does not affect the core trade lifecycle. > The `completed` status reflects that the trade itself is done; send-out statuses track > the subsequent cross-chain transfer only. - `send_out_pending` — send-out initiated, cross-chain transfer in progress - `send_out_completed` — cross-chain transfer successfully delivered to destination - `send_out_failed` — cross-chain transfer failed; the on-ramp trade itself remains completed enum: - created - awaiting_liquidity_provider - awaiting_crypto_transfer_authorization - crypto_transfer_authorization_pending - crypto_received - fiat_payment_started - awaiting_fiat_received_confirmation - fiat_received_confirmed - crypto_release_pending - awaiting_vendor_escrow_funding - awaiting_fiat_transfer - fiat_transfer_pending - completed - cancelled - failed - price_changed_requote_needed - send_out_pending - send_out_completed - send_out_failed example: awaiting_crypto_transfer_authorization TimelineEntry: type: object properties: status: $ref: '#/components/schemas/PartnerOrderStatus' timestamp: type: string format: date-time description: When this status was reached example: "2026-02-01T12:00:05Z" description: type: string description: Human-readable description of the status change example: "Order created" RegisterWebhookRequest: type: object required: [url] properties: url: type: string format: uri description: HTTPS URL that will receive webhook events example: "https://partner.example.com/webhooks/unigox" WebhookConfigResponse: type: object properties: url: type: string nullable: true description: Currently configured webhook URL (null if not set) example: "https://partner.example.com/webhooks/unigox" enabled: type: boolean description: Whether webhook delivery is active example: true WebhookTestPingResponse: type: object properties: delivered: type: boolean description: Whether the test event was delivered successfully example: true http_status: type: integer description: HTTP status code returned by the webhook endpoint example: 200 WebhookEventPayload: type: object description: Payload shape delivered to your webhook URL on each order status change properties: event_id: type: string description: Unique event identifier for de-duplication example: "evt_a1b2c3d4-e5f6-7890-abcd-ef1234567890" event_type: type: string description: Event type (always `order.status.changed`) example: "order.status.changed" created_at: type: string format: date-time description: Timestamp when the event was created example: "2026-02-01T12:00:05Z" data: type: object properties: order_id: type: string format: uuid description: Partner order UUID example: "b2c3d4e5-f6a7-8901-bcde-f12345678901" status: $ref: '#/components/schemas/PartnerOrderStatus' user_uuid: type: string format: uuid description: Public UUID of the end-user (from user creation) example: "550e8400-e29b-41d4-a716-446655440000" crypto_amount: type: string description: Total crypto amount including fees example: "101.500000" crypto_currency: type: string description: Cryptocurrency code example: "USDT" fiat_amount: type: string description: Fiat amount example: "152880.00" fiat_currency: type: string description: Fiat currency code example: "NGN" provider: type: string description: Liquidity provider type enum: [p2p, licensed] example: "p2p" payment_details_id: type: string description: Payment details ID used for the order example: "987" PartnerOnRampQuoteRequest: type: object required: [user_uuid, fiat_currency, crypto_currency] oneOf: - required: [fiat_amount] - required: [crypto_amount] properties: user_uuid: type: string format: uuid description: Public UUID of the end-user (returned by `POST /partner/users` as `id`) example: "550e8400-e29b-41d4-a716-446655440000" fiat_amount: type: string description: Fiat amount the end-user will send. Provide exactly one of fiat_amount or crypto_amount. example: "26345" crypto_amount: type: string description: Crypto amount to receive. Provide exactly one of fiat_amount or crypto_amount. example: "1.00" fiat_currency: type: string description: Source fiat currency code (ISO 4217) example: VND crypto_currency: type: string description: Target cryptocurrency code (e.g., USDT, USDC) example: USDT payment_method_slug: type: string description: Optional payment method slug to narrow routing example: zalopay payment_network_slug: type: string description: Optional payment network slug to narrow routing example: napas-vietnam provider_scope: type: string description: Optional provider scope filter; defaults to all enum: [all, licensed_only, p2p_only] example: all country_code: type: string description: Optional ISO 2-letter country code for fee/routing context example: VN PartnerOnRampQuoteResponse: type: object properties: quote_id: type: string format: uuid description: Unique quote identifier — use this to initiate the order example: "a1b2c3d4-e5f6-7890-abcd-ef1234567890" rate: type: string description: Fiat-to-crypto exchange rate (fiat units per 1 crypto unit) example: "26345.00" crypto_amount: type: string description: Total crypto amount the end-user will receive (net of fees) example: "1.005000" fiat_amount: type: string description: Fiat amount the end-user sends example: "26345.00" fee_breakdown: $ref: '#/components/schemas/FeeBreakdown' payment_method_slug: type: string description: Matched payment method slug used for this quote example: zalopay payment_network_slug: type: string description: Matched payment network slug used for this quote example: napas-vietnam expires_at: type: string format: date-time description: Quote expiration timestamp (60 seconds from creation) example: "2026-03-30T19:15:50Z" SubmitPayerDetailsRequest: type: object description: | Submit the payer's payment details for a `payment_request` on-ramp order. Provide exactly one of `payment_details_id` (saved profile) or `payment_details` (inline object). oneOf: - required: [payment_details_id] properties: payment_details_id: type: integer format: int64 description: ID of a previously saved payment profile for the end-user example: 1444 - required: [payment_details] properties: payment_details: type: object required: [payment_method_id, payment_network_id, details] properties: payment_method_id: type: integer format: int64 description: Payment method ID example: 5 payment_network_id: type: integer format: int64 description: Payment network ID example: 12 details: type: object description: Payer's account details (fields depend on payment method) additionalProperties: true example: phone: "+254712345678" name: "John Doe" country_code: type: string description: Optional ISO 2-letter country code example: KE PartnerErrorResponse: type: object required: [success, error] properties: success: type: boolean example: false error: type: object required: [code, message] properties: code: type: string description: | Machine-readable error code. Possible values: - `QUOTE_EXPIRED` — quote TTL has passed - `QUOTE_NOT_FOUND` — quote ID does not exist - `QUOTE_ALREADY_USED` — quote was already consumed by initiate - `PRICE_CHANGED_REQUOTE` — price changed beyond slippage tolerance, partner should request a new quote - `ORDER_NOT_FOUND` — order ID does not exist or belongs to another partner - `NO_OFFERS_AVAILABLE` — no vendor offers for the requested pair - `INVALID_REQUEST` — malformed request or missing fields - `INVALID_PAYMENT_DETAILS` — payment details not found or invalid - `RAIL_ROUTE_MISMATCH` — rail or route does not match payment details - `UNAUTHORIZED` — authentication failed - `INTERNAL_ERROR` — unexpected server error - `INVALID_STATUS` — order is not in the correct status for the requested action - `TRANSACTOR_ERROR` — on-chain transaction relay failed (e.g. simulation reverted) - `INSUFFICIENT_BALANCE` — partner wallet lacks tokens to fund escrow - `OPERATION_NOT_ALLOWED` — action is not permitted for the current order state enum: - QUOTE_EXPIRED - QUOTE_NOT_FOUND - QUOTE_ALREADY_USED - PRICE_CHANGED_REQUOTE - ORDER_NOT_FOUND - NO_OFFERS_AVAILABLE - INVALID_REQUEST - INVALID_PAYMENT_DETAILS - RAIL_ROUTE_MISMATCH - UNAUTHORIZED - INTERNAL_ERROR - INVALID_STATUS - TRANSACTOR_ERROR - INSUFFICIENT_BALANCE - OPERATION_NOT_ALLOWED example: QUOTE_EXPIRED message: type: string description: Human-readable error message example: "Quote has expired, please request a new quote" details: type: object nullable: true additionalProperties: true description: Additional context (e.g., quoted_rate, current_rate for slippage errors) example: quoted_rate: 1530.12 current_rate: 1545.00 securitySchemes: ApiKeyAuth: type: apiKey in: header name: X-API-Key description: Partner API key for authentication. Required for all partner account endpoints.