{
  "openapi": "3.1.0",
  "info": {
    "title": "Atlas Online — Merchant API",
    "version": "2.20.0",
    "x-contract-date": "2026-11-04",
    "summary": "Public Merchant API for PayIn, PayOut and Disputes flows.",
    "description": "The Merchant API exposes synchronous endpoints for creating PayIns and\nPayOuts, opening disputes, and reading status. Asynchronous outcomes are\ndelivered via signed webhooks.\n\n**Contract version 2.20** (contract date `2026-11-04`). Every response carries\nthe contract date in the `X-Api-Version` response header.\n\nAll requests are authenticated with a per-request HMAC-SHA256 signature\nover the canonical string `<X-Timestamp>.<raw-request-body>` (body is empty\nfor `GET`; for `multipart/form-data` it is the whole encoded multipart body\nas sent, boundaries and file bytes included, signed as raw bytes). The\nsignature is sent as a lowercase hex string in the\n`X-Signature` header.\n\nSee https://api.atlas-online.io/docs for the rendered reference.\n\n## Per-geo request objects (v2.0, India added in v2.2, South Korea in v2.3)\nEach geo has its OWN recipient object on pay-out. Send ONLY the fields that\nbelong to the geo; a field that does not belong to the geo is rejected with\n`400 VALIDATION_ERROR`. The endpoint is the same for all geos.\n- **Argentina** (`geo: 1`, `currency: ARS`): recipient = `{ recipientCbu }`.\n- **Venezuela** (`geo: 2`, `currency: VES`): recipient = `{ recipientPhone, recipientCedula, recipientBank, recipientAccountNumber }`.\n- **India** (`geo: 3`, `currency: INR`): TWO pay-out rails since v2.6, selected with `paymentMethod` (omitted ⇒ `imps`) — `imps` ⇒ recipient = `{ recipientAccountNumber, recipientBank, recipientIfsc }`; `upi` ⇒ recipient = `{ recipientUpiVpa }` (the IMPS trio is then rejected, and vice versa). Any other value is rejected with `VALIDATION_ERROR`. Amounts are whole INR on both pay-in and pay-out (fractional values are rejected, never rounded). PayIn create REQUIRES `paymentMethod` (`upi` | `upi_intent` | `imps`), and confirming an India pay-in requires the payer's UTR (relay it via `POST /pay-in/{payInId}/utr`).\n- **SouthKorea** (`geo: 4`, `currency: KRW`): recipient = `{ recipientAccountNumber, recipientBank }` — kor_bank bank transfer is the ONLY pay-out rail (`paymentMethod: \"kakao_pay\"` is rejected). Amounts are whole KRW on both pay-in and pay-out (KRW has no minor unit — fractional values are rejected, never rounded). PayIn create REQUIRES `paymentMethod` (`kor_bank` | `kakao_pay`); a kor_bank order returns `accountNumber` + `bankName` + `ownerName`, a kakao_pay order returns `qrPayload` (+ `qrImageBase64`). No UTR — the trader confirms against the payer's bank/Kakao receipt.\n\n## India UPI rails (v2.6)\n**Pay-in.** India has THREE methods over TWO rails: `upi` and `upi_intent`\nboth ride the `upi` rail (identical requisites — a VPA — served by the same\ntrader cards), `imps` rides its own. `upi_intent` is the intent-oriented\nvariant: behaviour, confirmation, spreads and webhooks are identical to\n`upi`; the difference is that you hand the payer the `upiIntentLink` deep\nlink instead of (or next to) the QR. Every India pay-in on the `upi` rail —\nplain `upi` included — returns `upiIntentLink`\n(`upi://pay?pa=…&pn=…&am=…&cu=INR`) in the\ncreate response, idempotent replays included. It is absent for `imps`, for\nevery non-India geo and for cascade-routed orders, and it is NOT part of the\nwebhooks or `GET /pay-in/{payInId}/status`. On Android render it as a tap\ntarget (\"Pay in UPI app\") — the payer's UPI app opens pre-filled with the\nVPA, payee name and exact amount; keep the QR\n(`qrImageBase64` / `upiVpa`) as the iOS/desktop fallback. Confirmation\nworks exactly as for `upi`; a payer UTR is optional on both.\n\n**App deep links (v2.14).** Send the optional `includeUpiAppLinks: true`\non PayIn create and an order that has `upiIntentLink` also returns\n`upiAppLinks { phonepe, paytm }` (`UpiAppLinks`), placed right before\n`checkout`. Each link opens that app's own \"send money to a UPI ID\" screen\nwith the payee and the locked payable amount filled in (a \"quasi-intent\"):\nthe payer makes an ordinary P2P transfer, which payer banks do not decline\nthe way they sometimes decline a `upi://pay` intent to a P2P VPA. The app\nroutes are not public APIs of PhonePe or Paytm and can move with an app\nupdate, so keep `upiIntentLink` and the QR on screen as the fallback. The\nflag is ignored (never rejected) where it does not apply, and a request\nwithout it gets a byte-identical response. Not in the webhooks or\n`GET /pay-in/{payInId}/status`.\n\n**Pay-out.** UPI pay-outs are supported since v2.6: send\n`paymentMethod: \"upi\"` + `recipientUpiVpa` (`handle@psp`, stored\nlower-cased) instead of the IMPS trio. An Atlas trader sends the money from\ntheir own UPI app to that VPA and uploads the proof; you are notified by the\nsame `pay_out.completed` webhook, which now carries `recipientUpiVpa` right\nafter `recipientIfsc` (present only on an India UPI pay-out — `recipientIfsc`\nitself stays in the payload as `null`, as since v2.2). `GET /pay-out/{id}/status`\nand the create response carry the same field under the same rule.\n\n## Hosted checkout (v2.5, scope widened in v2.6 and v2.8)\nOptional `checkout` object on PayIn create (`CheckoutRequest`) requests an\nAtlas-hosted payment page instead of embedding recipient details directly.\nScope: available for India (`paymentMethod: \"upi\"`, `\"upi_intent\"` or\n`\"imps\"`, v2.6 — v2.5 accepted the literal `\"upi\"` only) and South Korea\n(`paymentMethod: \"kor_bank\"` or `\"kakao_pay\"`, v2.8), routed to the internal\ntrader pool — otherwise the\nPayIn is rejected with\n`400 CHECKOUT_NOT_SUPPORTED` (the PayIn is NOT created). One page serves all\nfive methods and adapts to the one you asked for: `upi` and `upi_intent` =\na choice of PhonePe, Paytm and Other UPI (QR + UPI ID), `imps` =\naccount + IFSC + bank + holder and no QR, `kor_bank` = account + bank +\nholder and no QR, `kakao_pay` = the trader's Kakao Pay QR plus a\ntap-to-open button when that payload is a `kakaopay.com` link. On India\npages the payer may enter a UTR to speed up confirmation, but it is not\nrequired; Korean pages have NO reference step —\nthe payer confirms and the trader matches the deposit. When `checkout` is\npresent, `expiresIn` must be within 300-3600 seconds. The 201 response gains\na trailing `checkout` object (`CheckoutResponse`) ONLY when checkout was\nrequested and accepted — the key is omitted otherwise, so existing\nintegrations see a byte-identical response. Since v2.7 that object carries a\nFOURTH member, `embedUrl` — appended, never re-ordered — see \"Embedded\ncheckout\" below. The payer is always returned to\n`checkout.returnUrl` (`302 GET returnUrl?payInId=…&merchantOrderId=…`) for\nEVERY outcome — success, not confirmed, expired, \"start again\"; only an\nexplicit payer cancel goes to `checkout.cancelUrl` (falls back to\n`returnUrl`). **The redirect is not a confirmation** — credit the order only\nfrom the signed `pay_in.completed` webhook or `GET /pay-in/{payInId}/status`.\nSubmitting a UTR on the hosted page may extend `expiresAt` once by up to 30\nminutes, so do not derive expiry from the create response. No new webhook\nevents in v2.5. Browser-facing page endpoints (`GET /pay/{token}`,\n`GET /api/checkout/{token}`, `GET /api/checkout/{token}/status`,\n`POST /api/checkout/{token}/claim`, `GET /pay/{token}/return`) are informational\nonly — they are not HMAC-authenticated and are not part of this merchant API.\nA create refused with `400 CLIENT_BLOCKED` (v2.18) has no `checkout`\nobject, so there is no `checkout.url` / `checkout.embedUrl` and no page to\nopen or embed: show the payer a neutral message (for example \"Payment is\nunavailable. Please contact the shop's support.\") and do not retry.\n\n## Embedded checkout (v2.7)\nThe same checkout page can also be rendered INSIDE your own page instead of\nbeing navigated to. The only contract change is the new\n`checkout.embedUrl` (`{PublicBaseUrl}/embed/{token}`) returned alongside\n`checkout.url`: put it into `Atlas.mount('#atlas-pay', { url: embedUrl })`\nafter loading `https://api.atlas-online.io/embed/v1/atlas.js`, and the page\nis served in a cross-origin `<iframe>` that reports its height and raises\n`ready` / `claim` / `success` / `expired` / `failed` / `cancel` events to\nyour JavaScript. Everything else is identical — the same pay-in, the same\nUTR claim, the same webhooks. **A browser event is a UI hint, not a payment**:\ncredit the order from the signed `pay_in.completed` webhook or\n`GET /pay-in/{payInId}/status`, exactly as for the redirect flow.\n\nTwo things live OUTSIDE this HMAC contract because they are account\nSETTINGS rather than per-transaction fields: the allow-list of origins that\nmay frame the page (an unregistered domain gets a readable 403 page inside\nthe frame) and the stored appearance theme, which applies to the hosted page\nand the embed alike. Both are written from the MERCHANT PORTAL SESSION —\n`GET /api/merchant/checkout-settings`, `PUT .../origins`, `PUT .../theme` —\nand are deliberately not specified here. The loader API, the event payloads,\nthe `appearance` schema and the CSP snippet are documented for merchant\ndevelopers at https://api.atlas-online.io/docs#embedded-checkout.\n\n## Status by merchantOrderId (v2.19)\n`GET /api/merchant/pay-out/status?merchantOrderId={merchantOrderId}` and\n`GET /api/merchant/pay-in/status?merchantOrderId={merchantOrderId}` find an\norder by the merchant's own `merchantOrderId` and return exactly the body of\n`GET /pay-out/{payOutId}/status` / `GET /pay-in/{payInId}/status`. They are\nfor a create that timed out or lost its connection before the response, so\nthe merchant has no id: 200 means the order exists (use its status, do not\ncreate it again); 404 `NOT_FOUND` means it was not created, so repeat the\ncreate with the same `merchantOrderId` and the same body (create is\nidempotent by `merchantOrderId`, so a first request that got through in the\nmeantime is returned, not duplicated). The lookup is scoped to the API key's\nmerchant and matches exactly (case-sensitive, no trimming); a first create\nrefused with 400 created nothing and gives 404. A missing or blank value,\nor one over 200 characters, is 400 `VALIDATION_ERROR`. Send the value\nURL-encoded as a query component (`+` → `%2B`, `&` → `%26`, `#` → `%23`,\n`%` → `%25`). Query parameters are not signed: the canonical string is the\nempty-body `<X-Timestamp>.`, as for every GET.\n\n## Cancel a pay-in (v2.20)\n`POST /api/merchant/pay-in/{payInId}/cancel` and\n`POST /api/merchant/pay-in/cancel?merchantOrderId={merchantOrderId}` cancel\na pay-in the merchant no longer needs: typically it sent the same order to\nseveral providers and kept whichever answered first, or it never showed the\npayment details to the payer. No request body, so the canonical string is\n`<X-Timestamp>.` as for a GET; the `merchantOrderId` form uses the same\nlookup and encoding rules as the v2.19 status by merchantOrderId. A pay-in\nin `WaitingPayment` or `Expired` moves to `Failed` for good (a late payment\nno longer completes it) and the answer is 200 with the body of\n`GET /pay-in/{payInId}/status`. An already `Failed` pay-in (for any reason)\ngets the same 200: the call is idempotent. No `pay_in.failed` webhook is\nsent for a cancel; the 200 response is the confirmation. 409\n`PAYIN_COMPLETED`: the pay-in is completed, credit it. 409\n`PAYMENT_REPORTED`: a payment was reported for the order (a payment\nreference is on file, the payer pressed \"I have paid\" on the hosted page,\nor a payment check or a dispute is open on it); Atlas support settles it\nand the order goes on normally. Do not retry either. 409 `CANCEL_FAILED`:\nAtlas could not confirm the cancellation with the payment network right\nnow; the pay-in stays `WaitingPayment` and can still complete or expire.\nRetry after a delay.\n\n## Error codes\nErrors use `{ \"error\": \"<CODE>\", \"message\": \"<text>\" }`. Codes:\n`MISSING_API_KEY`, `MISSING_SIGNATURE`, `MISSING_TIMESTAMP`, `INVALID_TIMESTAMP`,\n`EXPIRED_REQUEST`, `INVALID_API_KEY`, `INVALID_SIGNATURE` (all 401);\n`VALIDATION_ERROR`, `NO_CARD_AVAILABLE`, `AMOUNT_BELOW_MINIMUM`,\n`AMOUNT_ABOVE_MAXIMUM`, `INSUFFICIENT_BALANCE`, `IDEMPOTENCY_KEY_MISMATCH`,\n`METHOD_NOT_CONTRACTED`, `SPREAD_MISCONFIGURED`, `CHECKOUT_NOT_SUPPORTED`,\n`CLIENT_BLOCKED`, `NO_WALLET_ADDRESS`, `WITHDRAWAL_IN_FLIGHT` (400;\n`CLIENT_BLOCKED`: new pay-ins from this `clientReference` are blocked on the\nmerchant's account, the pay-in is not created, do not retry;\n`IDEMPOTENCY_KEY_MISMATCH`\nis 409 on `POST /api/merchant/transfers`); `CONCURRENT_UPDATE` (409: the\nrequest collided with a concurrent request on the same data and was not\ncompleted; retry it with the same body, a create retried with the same\n`merchantOrderId` never makes a duplicate; a create waiting for other\nrequests on the same merchant gives up after about 5 s with this code; not\nreturned by `POST /api/merchant/disputes/{id}/messages`); `PAYIN_COMPLETED`,\n`PAYMENT_REPORTED` (409, pay-in cancel refused, do not retry); `CANCEL_FAILED`\n(409, Atlas could not confirm the cancellation with the payment network right now, the pay-in stays open, retry after a delay);\n`NOT_FOUND` (404).\n`POST /api/merchant/disputes/{id}/messages` is the exception: it answers a\nrefused message with `{ \"error\": \"<text>\" }` only.\n",
    "contact": {
      "name": "Atlas Online",
      "url": "https://api.atlas-online.io/docs"
    },
    "license": {
      "name": "Proprietary"
    },
    "x-release-notes": [
      {
        "version": "2.20.0",
        "date": "2026-11-04",
        "changes": [
          "New: POST /api/merchant/pay-in/{payInId}/cancel and POST /api/merchant/pay-in/cancel?merchantOrderId={merchantOrderId} cancel a pay-in the merchant no longer needs, for example when it sent the same order to several providers and another one answered first, or never showed the payer the payment details. Cancelling frees the order at once instead of leaving it open until it expires. No request body: the canonical string is `<X-Timestamp>.` as for a GET. The `merchantOrderId` form matches exactly among the merchant's own orders and takes the value URL-encoded, as the v2.19 status by merchantOrderId; a missing or blank value is 400 VALIDATION_ERROR (\"merchantOrderId is required\"), one over 200 characters is 400 VALIDATION_ERROR (\"merchantOrderId exceeds 200 character limit\").",
          "A pay-in in WaitingPayment or Expired moves to Failed for good: a late payment no longer completes it. 200 with the body of GET /api/merchant/pay-in/{payInId}/status (status Failed). A pay-in that is already Failed, for any reason, gets the same 200, so the call is safe to retry. No pay_in.failed webhook is sent for a cancel: the 200 response is the confirmation. To take the payment again, create a new pay-in with a new `merchantOrderId`.",
          "New error codes, both 409 and both final (do not retry): PAYIN_COMPLETED (\"{displayId} is completed — a completed pay-in cannot be cancelled.\"): the payment was received, credit the order. PAYMENT_REPORTED (\"A payment was reported for {displayId} — it is settled by Atlas support and cannot be cancelled.\"): a payment reference is on file for the order, the payer pressed \"I have paid\" on the hosted page, or a payment check or a dispute is open on it; the order goes on normally and can still complete or expire. 409 CANCEL_FAILED (\"{displayId} could not be cancelled right now — it stays open. Retry later.\"): Atlas could not confirm the cancellation with the payment network right now; the pay-in stays WaitingPayment and can still complete or expire in the meantime; retrying later is safe. 409 CONCURRENT_UPDATE: retry after a short delay. 404 NOT_FOUND (\"PayIn not found\"): no such pay-in on the merchant's account.",
          "Migration: none, two new endpoints and three new error codes, nothing else changes. X-Api-Version is now 2026-11-04."
        ]
      },
      {
        "version": "2.19.0",
        "date": "2026-10-22",
        "changes": [
          "New: GET /api/merchant/pay-out/status?merchantOrderId={merchantOrderId} finds a pay-out by the merchant's own `merchantOrderId` and returns the same body as GET /api/merchant/pay-out/{payOutId}/status (no `payOutId` field; `displayId` identifies the pay-out). For a create that timed out or dropped the connection: 200 means the pay-out exists, use its status and do not create it again; 404 NOT_FOUND (\"PayOut not found\") means it was not created, so repeat POST /api/merchant/pay-out with the same `merchantOrderId` and the same body, which returns the existing pay-out instead of creating a second one if the first request got through in the meantime.",
          "New: GET /api/merchant/pay-in/status?merchantOrderId={merchantOrderId}, the same lookup for pay-ins, returning the body of GET /api/merchant/pay-in/{payInId}/status (`payInId` included); 404 NOT_FOUND is \"PayIn not found\".",
          "Both lookups are scoped to the merchant of the API key (another merchant's order with the same id gives 404) and match `merchantOrderId` exactly: case-sensitive, not trimmed. A first create refused with 400 created no order, so its id gives 404 (IDEMPOTENCY_KEY_MISMATCH is the exception: the order exists). A missing or blank `merchantOrderId` is 400 VALIDATION_ERROR (\"merchantOrderId is required\"); one over 200 characters is 400 VALIDATION_ERROR (\"merchantOrderId exceeds 200 character limit\"). Send the value URL-encoded as a query component (`+` as %2B, `&` as %26, `#` as %23, `%` as %25). HMAC signing is unchanged: the query string is not signed, so the canonical string is `<X-Timestamp>.` as for every GET.",
          "Migration: none, two new endpoints, nothing else changes. X-Api-Version is now 2026-10-22."
        ]
      },
      {
        "version": "2.18.0",
        "date": "2026-10-20",
        "changes": [
          "New error code CLIENT_BLOCKED (400) on POST /api/merchant/pay-in. Atlas can block new pay-ins from one of the merchant's clients. While the block is active, a create with that `clientReference` and a new `merchantOrderId` is refused with 400 CLIENT_BLOCKED, message \"Pay-ins from this clientReference are blocked. Contact Atlas support.\" The pay-in is not created and no webhook is sent. The block is per merchant account and per exact `clientReference`. A repeat of an existing `merchantOrderId` still returns that pay-in; pay-outs are not affected.",
          "Hosted and embedded checkout: a refused create has no `checkout` object, so there is no `checkout.url` / `checkout.embedUrl` and nothing to open or mount. Show the payer a neutral message (for example \"Payment is unavailable. Please contact the shop's support.\") and do not retry.",
          "Migration: treat CLIENT_BLOCKED as final for that client; do not retry and do not resend the payer under another `clientReference`. No field is added, removed or renamed. X-Api-Version is now 2026-10-20."
        ]
      },
      {
        "version": "2.17.0",
        "date": "2026-10-16",
        "changes": [
          "New transfer source `autoSettle`: when Atlas has set a pay-out balance top-up for the merchant, automatic settlement first moves that amount from W1 (`availableBalance`) to W2 (`payOutBalance`) and requests the withdrawal for the rest. GET /api/merchant/transfers and GET /api/merchant/transfers/{transferId} list that move with `source: autoSettle` and `clientReference: null` (it would have been reported as `manual` before).",
          "Additive: accept unknown `source` values. Nothing else changes. X-Api-Version is now 2026-10-16.",
          "Update 2026-10-18: pay-outs created at the same moment are each decided on the current pay-out balance (W2). Requests that used to be refused with 400 VALIDATION_ERROR (\"Concurrent update detected ...\") when W2 covered only some of them are now created with 201, reserved while W2 covers them and held as AwaitingFunds after that, exactly as if they had been sent one after another. A merchant API request that still collides with a concurrent request on the same data gets the new code 409 CONCURRENT_UPDATE instead of 400 VALIDATION_ERROR: retry it with the same body. A pay-out create waiting for other creates on the same merchant gives up after about 5 s with this code. POST /api/merchant/disputes/{id}/messages does not return it. No field changes; X-Api-Version stays 2026-10-16."
        ]
      },
      {
        "version": "2.16.0",
        "date": "2026-10-12",
        "changes": [
          "New: transfers from the pay-in wallet to the pay-out wallet. POST /api/merchant/transfers with `amount` and `clientReference` (both required) moves USDT from W1 (`availableBalance`) to W2 (`payOutBalance`), the same move as the cabinet's transfer. One direction only, W1 → W2. The move is synchronous: 201 with the transfer record (`transferId`, `clientReference`, `amount`, `currency`, `direction: payInToPayOut`, `source`, `createdAt`, `balances` right after the move). No webhook. Pay-outs held for funds (AwaitingFunds) that W2 now covers are released right after the transfer.",
          "`clientReference` (1–64 characters of A–Z a–z 0–9 . _ : -) is the idempotency key, unique per merchant: sending it again with the same amount returns 200 with the original transfer and moves nothing; with a different amount the request is refused with 409 IDEMPOTENCY_KEY_MISMATCH. `amount` > 0 with at most 6 decimal places; above `availableBalance` it is refused with INSUFFICIENT_BALANCE (also when a concurrent withdrawal or transfer used the balance first).",
          "GET /api/merchant/transfers/{transferId} and GET /api/merchant/transfers (page, pageSize; newest first) list every W1 → W2 transfer, including the ones made in the cabinet (`source: manual`, `clientReference: null`).",
          "Purely additive: nothing else changes. X-Api-Version is now 2026-10-12.",
          "Fixed: POST /api/merchant/disputes/{id}/messages checks that the dispute belongs to you before storing any file; another merchant's or a missing dispute gets 404 NOT_FOUND like the other dispute endpoints (a missing one used to get 400 {\"error\": \"Dispute not found\"}). Multipart requests are verified over the raw body bytes (`timestamp + \".\"` then the bytes as sent); the old signature over the body read as UTF-8 text is still accepted. POST /pay-in/{payInId}/utr answers another merchant's pay-in with 404 NOT_FOUND, like a missing one. JSON signing is unchanged."
        ]
      },
      {
        "version": "2.15.0",
        "date": "2026-10-06",
        "changes": [
          "POST /api/merchant/pay-in (first call and idempotent replays) and GET /pay-in/{payInId}/status return three new always-present fields: `exchangeRate` (your rate for the order, fixed at creation, units of `currency` per 1 USDT), `commissionRate` (your commission on the order, percent: 8.5 means 8.5%) and `amountUsdt` (the USDT credited to your balance when the order completes).",
          "`requestedAmount / exchangeRate` is the gross USDT value of the order; `amountUsdt` = gross × (1 − commissionRate / 100), rounded to 6 decimal places: what is credited, the same value as `amountUsdt` in the pay_in.completed webhook. The rate and the commission do not change after creation; on an order closed on a lower received amount (v2.12) `amountUsdt` on the status is recalculated on that amount at the same rate. Treat `amountUsdt` as authoritative: a recomputation from the published `exchangeRate` can differ by 0.000001 (the gross is rounded to 6 dp first).",
          "Nothing is removed, renamed or reordered: the three fields are appended after the existing ones (after `checkout` when present). Webhooks, pay-outs and the other endpoints are unchanged. X-Api-Version is now 2026-10-06."
        ]
      },
      {
        "version": "2.14.0",
        "date": "2026-10-01",
        "changes": [
          "India: POST /api/merchant/pay-in accepts an optional boolean `includeUpiAppLinks` (default false). When true and the order has `upiIntentLink` (a upi or upi_intent order served by an Atlas trader card), the response adds `upiAppLinks { phonepe, paytm }` right before `checkout`: deep links into the PhonePe and Paytm send-money screens with the payee and the locked payable amount filled in. Keep `upiIntentLink` and the QR as the fallback; the app routes are not public APIs of those apps.",
          "New docs section 'Payment screen options' (QR, UPI ID, UPI Intent, app deep links). Hosted and embedded checkout: upi and upi_intent orders both offer PhonePe, Paytm and Other UPI (QR + UPI ID); the separate generic-intent 'UPI' button is removed from the page. upiIntentLink in the API is unchanged.",
          "No migration: the flag is optional, a request without it gets a byte-identical response, and it is ignored (never rejected) on imps and externally routed orders. Not in the webhooks or GET /pay-in/{payInId}/status. X-Api-Version is now 2026-10-01."
        ]
      },
      {
        "version": "2.13.0",
        "date": "2026-09-28",
        "changes": [
          "Automatic settlement: a withdrawal requested for you at accumulationThreshold (autoSettleEnabled true) may be sent to your wallet automatically. While the transfer is on its way the withdrawal is Processing (the status reserved until now) and txHash stays null; once confirmed on chain it is Completed with its txHash, as before.",
          "Processing → Pending is possible: when an automatic transfer cannot be made, the withdrawal goes back to Pending and is paid by the finance team like any other. A withdrawal you request yourself is unchanged and never sent automatically. autoSettleEnabled is managed by the platform.",
          "Nothing is added, removed or renamed and no value changes meaning; treat Processing as in flight and poll until Completed or Failed. X-Api-Version is now 2026-09-28."
        ]
      },
      {
        "version": "2.12.0",
        "date": "2026-09-24",
        "changes": [
          "New always-present field `originalAmount` on GET /pay-in/{payInId}/status and on the pay_in.completed webhook, right after `amountAdjustment`: what you originally asked for. On every ordinary order it equals `requestedAmount`.",
          "Pay-ins closed on the amount actually received: when the payer transfers less than the order amount (you asked 1000, the payer sent 500), support may close the order as Completed on what was received. `amount` and `requestedAmount` are then the RECEIVED amount (what you are credited for; `amountUsdt` is priced on it at the order's original rate), `amountAdjustment` is 0, `isManualConfirmation` is true and `originalAmount` keeps your original ask. Downward only, at most once per order.",
          "Nothing is removed or renamed; the create response and the pay_in.expired / pay_in.failed webhooks are unchanged. Reconcile on `requestedAmount` as before; `originalAmount` != `requestedAmount` means the order settled on a lower received amount. X-Api-Version is now 2026-09-24."
        ]
      },
      {
        "version": "2.11.0",
        "date": "2026-09-21",
        "changes": [
          "India, UPI orders (`paymentMethod` upi or upi_intent): the platform may move the PAYABLE amount of a pay-in so it stays unique on the requisite it is served by. Since 2026-09-29 the move is configured per merchant account and is off by default: when enabled, the payable amount moves within ± the range set for the account, in its step, up or down (with ±2 by 1, 1000 may be served as 1001, 999, 1002 or 998; with ±1.5 by 0.5, as 1000.5, 999.5, 1001 ...; the exact amount is always tried first, nearest first, and the side is chosen so the adjustments cancel out over time); when not enabled, the exact amount or NO_CARD_AVAILABLE. From 2026-09-23 it was one or two WHOLE rupees, up or down; between 2026-09-21 and 2026-09-23 a raise of 0.10-2.00 INR in 0.10 steps. `imps` orders are never moved, because their payer types the amount by hand. `amount` is still what the PAYER has to transfer: show it exactly. Two always-present fields join it on the create response, on GET /pay-in/{payInId}/status and on the pay_in.completed / pay_in.expired / pay_in.failed webhooks: `requestedAmount` (what you sent, and what you are credited for) and `amountAdjustment` (the signed difference, within ± the range configured for the merchant, 0 when there is none - accept a negative and a fractional value).",
          "Nothing is removed or renamed and no existing value changes meaning; outside India `requestedAmount` always equals `amount` and `amountAdjustment` is 0. Reconcile your order on `requestedAmount` and `merchantOrderId`. X-Api-Version is now 2026-09-21."
        ]
      },
      {
        "version": "2.10.0",
        "date": "2026-09-19",
        "changes": [
          "India: upiIntentLink (PayIn create response) and the QR / 'Pay in UPI app' link on the hosted and embedded checkout page now carry the payee, the amount and the currency ONLY: upi://pay?pa=<vpa>&pn=<payee>&am=<amount 2dp>&cu=INR. The tn and tr parameters (both were our displayId) are removed - tr on a person-to-person VPA made some payer apps treat the intent as a merchant payment and decline it.",
          "No field added, removed or renamed; the amount is still locked in the payer's app. Treat upiIntentLink as opaque. If you parsed tn / tr out of the link to recover the order id, read displayId from the same response instead. Confirmation is unchanged: UTR and exact amount. X-Api-Version is now 2026-09-19."
        ]
      },
      {
        "version": "2.9.0",
        "date": "2026-09-14",
        "changes": [
          "PayIn create response is now PER-GEO: POST /api/merchant/pay-in returns only the requisite keys that apply to the order's geo and rail; a key that does not apply is OMITTED (no longer serialized as null or \"\"). Argentina: cbu. Venezuela: recipientPhone + recipientCedula. India upi/upi_intent: upiVpa + upiIntentLink. India imps: accountNumber + ifscCode. SouthKorea: accountNumber. qrImageBase64 and qrPayload are present only when the requisite carries a QR. cbu: \"\" is gone for every non-Argentina order.",
          "Always present: payInId, displayId, geo, ownerName, bankName, amount, currency, expiresAt, requiresManualConfirmation. Key order on the wire is fixed: common -> requisites -> QR -> checkout.",
          "Migration: treat a missing key exactly as null. No new, renamed or re-typed keys; strictly-typed parsers must mark the requisite and QR keys optional. GET /pay-in/{payInId}/status and the webhooks are unchanged. X-Api-Version is now 2026-09-14."
        ]
      },
      {
        "version": "2.8.0",
        "date": "2026-09-11",
        "changes": [
          "Hosted AND embedded checkout now serve South Korea. The optional checkout object on POST /api/merchant/pay-in is accepted for geo=SouthKorea on both Korean rails (paymentMethod kor_bank or kakao_pay) alongside India's upi / upi_intent / imps, routed to the internal trader pool. Same page, same checkout { url, expiresAt, locale, embedUrl } object, same redirect contract, same loader and events.",
          "The CHECKOUT_NOT_SUPPORTED message is now \"checkout is currently available for India (paymentMethod upi, upi_intent or imps) and South Korea (paymentMethod kor_bank or kakao_pay) only.\" The routing refusal (\"checkout is not available for this merchant's routing …\") is unchanged.",
          "What the Korean payer sees: kor_bank renders the trader's account number, bank and account holder for a manual transfer (no QR, no deep link); kakao_pay renders the trader's Kakao Pay QR plus a tap-to-open button when the payload is a kakaopay.com https link (a raw EMV payload is rendered as a QR only). Page languages are ko (default) and en; checkout.locale resolves against that set exactly as it does against en/hi for India. Amounts are whole KRW, displayed as ₩1,234,567.",
          "No UTR in Korea. Neither Korean rail has a transaction-reference concept: the payer confirms and the Atlas trader matches the deposit against their own statement, so the page shows no reference form and the browser-facing POST /api/checkout/{token}/claim rejects a reference sent anyway with 400 UTR_NOT_APPLICABLE. The HMAC endpoint POST /pay-in/{payInId}/utr is unchanged.",
          "No request or response SHAPE change: no new fields, no new endpoints, no new webhook events, and the only new error code is on the browser-facing page API rather than on this contract. Deploy prerequisite: the merchant needs SouthKorea in its allowed geos and a merchant_method_spreads PAY-IN row per Korean method (a missing pay-in spread is METHOD_NOT_CONTRACTED and has no fallback)."
        ]
      },
      {
        "version": "2.7.0",
        "date": "2026-09-10",
        "changes": [
          "New: embedded checkout. The checkout object on the PayIn create response gains a FOURTH member, embedUrl ({PublicBaseUrl}/embed/{token}) — the same hosted page served embeddable. It is APPENDED after locale and never re-orders the existing three; it is present whenever checkout itself is present, and the whole checkout key is still omitted when the merchant did not ask for a page, so a v2.6 integration sees a byte-identical response.",
          "How to use it: load https://api.atlas-online.io/embed/v1/atlas.js and call Atlas.mount('#atlas-pay', { url: checkout.embedUrl }). The loader creates ONE cross-origin iframe, keeps its height in sync and raises ready / resize / claim / success / expired / failed / cancel / intent / download / focus / error events. url and embedUrl are alternatives, not a sequence — redirect to url OR embed embedUrl.",
          "A browser event is a UI HINT, not a payment — success can be missed (closed tab) and the merchant page can be compromised. Credit the order only from the signed pay_in.completed webhook or GET /pay-in/{payInId}/status, exactly as for the redirect flow. cancel means the payer asked to leave: the frame never navigates, routing is the merchant's.",
          "Embedding domains are an ACCOUNT SETTING, not a contract field: register the origins that may frame the page from the merchant portal session with PUT /api/merchant/checkout-settings/origins — https only, host[+port], apex AND wildcard listed separately (a wildcard without its bare domain saves with an ORIGIN_WILDCARD_WITHOUT_APEX warning), max 10. An unregistered origin gets a readable 403 page INSIDE the frame (and NO_RESPONSE from the loader after ~8 s), because a browser-blocked frame is otherwise undiagnosable.",
          "Appearance (theme) is an account setting too, shared by the hosted page and the embed: schema v1 { version, theme, variables (37 public names), rules (allow-listed selectors x properties), branding, layout, locale, languageSwitcher }, saved on the merchant account and overridable per page with mount({appearance}). Hex colours only, 0-48px sizes, enum shadows/weights, fonts from a self-hosted allow-list, <=4 KB, <=40 rules. The endpoints that write both settings (GET /api/merchant/checkout-settings, PUT .../theme, PUT .../origins) use the portal session, NOT the HMAC contract, and are deliberately not part of this specification.",
          "No new endpoints, no new error codes and no new webhook events on the HMAC surface. Deploy prerequisite: Checkout:PublicBaseUrl must be set (embedUrl is derived from it, as url already was)."
        ]
      },
      {
        "version": "2.6.0",
        "date": "2026-09-09",
        "changes": [
          "New: India UPI pay-outs. POST /api/merchant/pay-out accepts paymentMethod \"upi\" together with the new optional recipientUpiVpa (UPI ID, handle@psp, stored lower-cased) as the WHOLE recipient object. imps stays the default when paymentMethod is omitted, and its rules are unchanged. Operationally an Atlas trader sends the money from their own UPI app to that VPA and uploads the proof — you are notified by the same pay_out.completed webhook.",
          "The two India pay-out rails are mutually exclusive: recipientUpiVpa on an imps pay-out, and recipientAccountNumber / recipientBank / recipientIfsc on a upi pay-out, are both rejected with VALIDATION_ERROR; recipientUpiVpa on any non-India geo is rejected too. A paymentMethod other than imps or upi now returns \"paymentMethod '<x>' is not available for India pay-outs; use imps or upi\" (the v2.2 \"UPI pay-outs are not supported for India\" message is gone). Whole-INR amounts unchanged.",
          "recipientUpiVpa is echoed in the pay-out create response, in GET /api/merchant/pay-out/{payOutId}/status and in the pay_out.completed webhook, immediately after recipientIfsc (which stays in the payload as null on a UPI pay-out, as since v2.2). The key is OMITTED when null, so Argentina / Venezuela / SouthKorea / India-IMPS payloads are byte-identical to v2.5. pay_out.failed and pay_out.expired are unchanged.",
          "New India pay-in method upi_intent (\"UPI Intent\", rail upi). GET /api/merchant/payment-methods?geo=India now returns { code: upi_intent, name: UPI Intent, type: upi } alongside upi and imps, and India paymentMethod on PayIn create is one of upi | upi_intent | imps. Behaviour, UTR confirmation, spreads and webhooks are identical to upi — upi and upi_intent are ONE rail, so a upi_intent order is served by any UPI-rail card and traders need no new requisites.",
          "PayIn create response adds upiIntentLink (string, omitted when null) right after ifscCode — the UPI deep link upi://pay?pa=<vpa>&pn=<payee>&am=<amount 2dp>&cu=INR&tn=<displayId>&tr=<displayId>. Present for EVERY India pay-in on the UPI rail (upi and upi_intent), idempotent replays included; absent for imps, for non-India geos and for cascade-routed orders. It is NOT in the webhooks and NOT in GET /pay-in/{payInId}/status.",
          "Integration guidance for upiIntentLink: on Android render it as a tap target (\"Pay in UPI app\") — the payer's UPI app opens pre-filled with the VPA, payee name, exact amount and our reference — and keep the QR (qrImageBase64 / upiVpa) as the iOS/desktop fallback. The payer still confirms with a UTR exactly as for upi.",
          "Hosted checkout scope widened from paymentMethod == \"upi\" to all three India methods (upi, upi_intent, imps). ONE page adapts to the method: upi = QR + UPI ID, upi_intent = a upi://pay app button on phones with the QR one tap away, imps = account number + IFSC + bank + account holder and no QR. The CHECKOUT_NOT_SUPPORTED message is now \"checkout is currently available for India (paymentMethod upi, upi_intent or imps) only.\"",
          "No new webhook events and no new error codes in v2.6. Deploy prerequisite: the upi_intent method row must exist for the geo AND each merchant needs a merchant_method_spreads row for it — a missing PAY-IN spread returns METHOD_NOT_CONTRACTED (pay-in spreads have no fallback; pay-out spreads fall back to the merchant default)."
        ]
      },
      {
        "version": "2.5.0",
        "date": "2026-09-07",
        "changes": [
          "New: Hosted checkout. Optional checkout { returnUrl, cancelUrl?, locale? } object on POST /api/merchant/pay-in requests an Atlas-hosted payment page. MVP scope: accepted ONLY for geo=India + paymentMethod=upi routed to the internal trader pool — otherwise 400 CHECKOUT_NOT_SUPPORTED and the PayIn is NOT created. When checkout is present, expiresIn must be within 300-3600 seconds.",
          "The 201 response gains a trailing checkout { url, expiresAt, locale } object, present ONLY when checkout was requested and accepted — the key is omitted otherwise, so existing integrations see a byte-identical response. Idempotent replay returns the same checkout.url.",
          "Redirect contract: the payer is returned to returnUrl (302 GET returnUrl?payInId=…&merchantOrderId=…) for EVERY outcome (success / not confirmed / expired / start again); only an explicit payer cancel goes to cancelUrl (falls back to returnUrl). No status and no signature in the URL — the redirect is not a confirmation. Credit orders only from the signed pay_in.completed webhook or GET /pay-in/{payInId}/status.",
          "Submitting a UTR on the hosted page may extend expiresAt once by up to 30 minutes (pay_in.expired then arrives later accordingly) — do not derive expiry from the create response.",
          "New error code CHECKOUT_NOT_SUPPORTED. New schemas CheckoutRequest / CheckoutResponse. No new webhook events.",
          "Browser-facing hosted-page endpoints (GET /pay/{token}, GET /api/checkout/{token}, GET /api/checkout/{token}/status, POST /api/checkout/{token}/claim, GET /pay/{token}/return) are informational only — not HMAC-authenticated, not part of this merchant API."
        ]
      },
      {
        "version": "2.4.0",
        "date": "2026-09-02",
        "changes": [
          "New Finance API. GET /api/merchant/balance returns the merchant USDT position: availableBalance (W1, withdrawable), payOutBalance (W2), reservedBalance, totalBalance, walletAddress, autoSettleEnabled, accumulationThreshold and the in-flight pendingWithdrawal.",
          "POST /api/merchant/withdrawals requests a withdrawal of W1 to the configured wallet address — optional partial amount, whole balance when omitted or with no body. One withdrawal at a time (WITHDRAWAL_IN_FLIGHT); the amount is deducted from W1 on request and refunded on Failed.",
          "GET /api/merchant/withdrawals/{withdrawalId} and GET /api/merchant/withdrawals (page, pageSize, status) to track Pending → Processing → Completed | Failed. No webhook for withdrawals yet — poll.",
          "New error codes NO_WALLET_ADDRESS and WITHDRAWAL_IN_FLIGHT; INSUFFICIENT_BALANCE reused for withdrawals above W1. Purely additive."
        ]
      },
      {
        "version": "2.3.0",
        "date": "2026-08-07",
        "changes": [
          "New geo South Korea (geo 4 / SouthKorea, currency KRW). Pay-in rails kor_bank (bank + account number) and kakao_pay (QR payload); pay-outs are kor_bank ONLY (paymentMethod kakao_pay is rejected, omitted defaults to kor_bank).",
          "paymentMethod is REQUIRED on SouthKorea PayIn create (kor_bank | kakao_pay) — the second multi-rail geo after India. Still optional for Argentina/Venezuela.",
          "Whole KRW only — fractional SouthKorea amounts are rejected with VALIDATION_ERROR, never rounded (KRW has no minor unit).",
          "SouthKorea PayOut recipient — recipientAccountNumber (5-20 digits; hyphens/spaces accepted, stored bare) + recipientBank. No new fields — both are shared columns.",
          "PayIn create response for SouthKorea reuses existing fields: kor_bank populates accountNumber + bankName + ownerName; kakao_pay populates qrPayload (+ qrImageBase64 when an image is attached). No schema additions.",
          "requiresManualConfirmation is always true for SouthKorea (trader confirms against the payer's bank/Kakao receipt). No UTR requirement — that stays India-only.",
          "GET /payment-methods?geo=SouthKorea returns kor_bank and kakao_pay; GET /banks?geo=SouthKorea returns the KR bank catalog (KB Kookmin, Shinhan, Woori, …)."
        ]
      },
      {
        "version": "2.2.0",
        "date": "2026-07-31",
        "changes": [
          "New geo India (geo 3 / India, currency INR). Pay-in rails upi (VPA + QR) and imps (account + IFSC); pay-outs are IMPS ONLY (paymentMethod upi is rejected, omitted defaults to imps).",
          "paymentMethod is REQUIRED on India PayIn create (upi | imps) — India is the first multi-rail geo. Still optional for Argentina/Venezuela.",
          "Whole INR only — fractional India amounts are rejected with VALIDATION_ERROR, never rounded.",
          "New India PayOut recipient fields — recipientAccountNumber (shared with VE) + recipientBank + new recipientIfsc (11-char IFSC, AAAA0XXXXXX).",
          "PayIn create response adds upiVpa, accountNumber, ifscCode (India requisites; null for other geos, additive).",
          "UTR is mandatory evidence for India — an India pay-in cannot be confirmed without one; requiresManualConfirmation is always true for India.",
          "pay_out.completed webhook + PayOut status add nullable recipientIfsc (additive).",
          "GET /payment-methods type is now per-method (India returns upi and imps under one geo); GET /banks?geo=India returns [].",
          "Doc correction: banks catalog kind has always been Bank — Wallet sample entries were never returned by the endpoint."
        ]
      },
      {
        "version": "2.1.1",
        "date": "2026-06-15",
        "changes": [
          "paymentMethod is OPTIONAL again on POST /pay-in (reverts the v2.1 requirement). Omitted ⇒ the geo's default rail (for Venezuela any bank is Pago Móvil)."
        ]
      },
      {
        "version": "2.1.0",
        "date": "2026-06-11",
        "changes": [
          "BREAKING — paymentMethod became REQUIRED on POST /pay-in and is the method code (same vocabulary as the pay-out recipientBank). Superseded by 2.1.1.",
          "GET /api/merchant/payment-methods now returns code — pass it as paymentMethod on PayIn create."
        ]
      },
      {
        "version": "2.0.1",
        "date": "2026-06-10",
        "changes": [
          "Consistent validation error shape — a missing required field on POST /pay-in and /pay-out always returns {\"error\":\"VALIDATION_ERROR\",\"message\":\"<field> is required\"} with the lowercase JSON field name."
        ]
      },
      {
        "version": "2.0.0",
        "date": "2026-06-04",
        "changes": [
          "Added `clientReference` (required, maxLength 64, pattern `^[A-Za-z0-9._:-]{1,64}$`) to PayIn create and PayOut create — the merchant's stable id for their end customer.",
          "Added `paymentMethod` (optional enum `ven_bank` / `ven_bank_account` / `cvu`) to PayIn create.",
          "PayOut create is now per-geo — Argentina requires `recipientCbu`; Venezuela requires `recipientPhone` + `recipientCedula` + `recipientBank` + `recipientAccountNumber`. Cross-geo fields are rejected with `400 VALIDATION_ERROR`.",
          "Added `recipientAccountNumber` (bank account number / numeroDeCuenta) to Venezuela PayOut.",
          "Added disputeAmount (required, may be partial <= transaction amount) to Dispute create.",
          "New endpoint GET /api/merchant/payment-methods?geo={geo} — available payment methods for a geo.",
          "PayOut status response now includes geo, VE recipient fields (recipientPhone, recipientCedula, recipientBank, recipientAccountNumber), and clientReference.",
          "PayIn status response now includes clientReference (echo) and qrPayload.",
          "Added AwaitingFunds to PayOutStatus enum — a pay-out submitted before balance is funded is ACCEPTED (201) and held up to ~12h, then settled or pay_out.expired.",
          "pay_in.completed webhook adds geo + utr; pay_out.completed adds geo + VE recipient fields.",
          "Added pay_in.failed webhook event (with reason).",
          "Added error codes METHOD_NOT_CONTRACTED and SPREAD_MISCONFIGURED to the documented error catalog.",
          "Every response carries X-Api-Version header with the contract date (2026-06-04)."
        ]
      },
      {
        "version": "1.2.0",
        "date": "2026-05-13",
        "changes": [
          "Added error codes for amount limits — AMOUNT_BELOW_MINIMUM, AMOUNT_ABOVE_MAXIMUM (PayIn and PayOut).",
          "Added INSUFFICIENT_BALANCE on PayOut creation. The response includes the available balance and the shortfall.",
          "Added IDEMPOTENCY_KEY_MISMATCH for accounts on strict idempotency — a repeated merchantOrderId sent with a different body is rejected explicitly.",
          "Added Argentina CBU format validation (22 digits + checksum). Invalid CBU is rejected with VALIDATION_ERROR.",
          "GET /api/merchant/banks?geo=Venezuela returns a structured catalog with stable code (for example 0134 for Banesco), name, and kind (Bank or Wallet). HMAC-authenticated like the rest of /api/merchant/*.",
          "pay_in.completed webhook payload now includes isManualConfirmation — true when the PayIn was confirmed manually after its TTL elapsed.",
          "Tightened currency validation — empty/null currency is rejected with VALIDATION_ERROR.",
          "Invalid geo values now return a dedicated enum error.",
          "utr is validated for length (max 128 characters) at the API layer."
        ]
      }
    ]
  },
  "servers": [
    {
      "url": "https://api.atlas-online.io",
      "description": "Production"
    },
    {
      "url": "https://sandbox.api.atlas-online.io",
      "description": "Sandbox"
    }
  ],
  "tags": [
    {
      "name": "PayIn",
      "description": "Inbound payments from end customers."
    },
    {
      "name": "PayOut",
      "description": "Outbound payments from the merchant to a recipient bank account."
    },
    {
      "name": "Disputes",
      "description": "Contest a completed PayIn or PayOut and exchange evidence."
    },
    {
      "name": "Catalog",
      "description": "Reference data (banks, payment methods)."
    },
    {
      "name": "Finance",
      "description": "Merchant USDT balance, withdrawals to the merchant wallet (v2.4) and transfers from the pay-in to the pay-out wallet (v2.16)."
    },
    {
      "name": "Webhooks",
      "description": "Asynchronous event notifications delivered to the merchant's webhookUrl."
    }
  ],
  "security": [
    {
      "ApiKey": [],
      "Timestamp": [],
      "Signature": []
    }
  ],
  "components": {
    "securitySchemes": {
      "ApiKey": {
        "type": "apiKey",
        "in": "header",
        "name": "X-Api-Key",
        "description": "Merchant public identifier (`mk_<48-hex>`)."
      },
      "Timestamp": {
        "type": "apiKey",
        "in": "header",
        "name": "X-Timestamp",
        "description": "Unix timestamp in seconds (UTC) at request time. Must be within ±300 s\nof the server clock.\n"
      },
      "Signature": {
        "type": "apiKey",
        "in": "header",
        "name": "X-Signature",
        "description": "Lowercase hex HMAC-SHA256 signature of the canonical string\n`<X-Timestamp>.<raw-request-body>` (body is empty for GET; for multipart\nit is the encoded multipart body bytes as sent).\nSigning key is the merchant `apiSecret`.\n"
      }
    },
    "schemas": {
      "Error": {
        "type": "object",
        "required": [
          "error",
          "message"
        ],
        "properties": {
          "error": {
            "type": "string",
            "description": "Machine-readable error code. The full catalog includes:\n`VALIDATION_ERROR`, `AMOUNT_BELOW_MINIMUM`, `AMOUNT_ABOVE_MAXIMUM`,\n`INSUFFICIENT_BALANCE`, `IDEMPOTENCY_KEY_MISMATCH`,\n`METHOD_NOT_CONTRACTED`, `SPREAD_MISCONFIGURED`,\n`CHECKOUT_NOT_SUPPORTED`, `CLIENT_BLOCKED`, `NO_CARD_AVAILABLE`,\n`NO_WALLET_ADDRESS`, `WITHDRAWAL_IN_FLIGHT`, `CONCURRENT_UPDATE`,\n`PAYIN_COMPLETED`, `PAYMENT_REPORTED`, `CANCEL_FAILED`, `NOT_FOUND`,\n`MISSING_API_KEY`, `MISSING_SIGNATURE`, `MISSING_TIMESTAMP`,\n`INVALID_TIMESTAMP`, `EXPIRED_REQUEST`, `INVALID_API_KEY`,\n`INVALID_SIGNATURE`.\n",
            "examples": [
              "VALIDATION_ERROR"
            ]
          },
          "message": {
            "type": "string",
            "description": "Human-readable description of the failure."
          }
        },
        "examples": [
          {
            "error": "VALIDATION_ERROR",
            "message": "amount must be greater than zero"
          }
        ]
      },
      "ErrorCode": {
        "type": "string",
        "enum": [
          "VALIDATION_ERROR",
          "AMOUNT_BELOW_MINIMUM",
          "AMOUNT_ABOVE_MAXIMUM",
          "INSUFFICIENT_BALANCE",
          "IDEMPOTENCY_KEY_MISMATCH",
          "METHOD_NOT_CONTRACTED",
          "SPREAD_MISCONFIGURED",
          "CHECKOUT_NOT_SUPPORTED",
          "CLIENT_BLOCKED",
          "NO_CARD_AVAILABLE",
          "NO_WALLET_ADDRESS",
          "WITHDRAWAL_IN_FLIGHT",
          "CONCURRENT_UPDATE",
          "PAYIN_COMPLETED",
          "PAYMENT_REPORTED",
          "CANCEL_FAILED",
          "NOT_FOUND",
          "MISSING_API_KEY",
          "MISSING_SIGNATURE",
          "MISSING_TIMESTAMP",
          "INVALID_TIMESTAMP",
          "EXPIRED_REQUEST",
          "INVALID_API_KEY",
          "INVALID_SIGNATURE"
        ],
        "description": "Stable machine-readable error code returned in the `error` field."
      },
      "VenezuelaBank": {
        "type": "object",
        "required": [
          "code",
          "name",
          "kind"
        ],
        "properties": {
          "code": {
            "type": "string",
            "description": "Stable bank identifier. 4-digit SUDEBAN code (e.g. \"0134\" for Banesco).",
            "examples": [
              "0134"
            ]
          },
          "name": {
            "type": "string",
            "description": "Human-readable bank or wallet name.",
            "examples": [
              "Banesco"
            ]
          },
          "kind": {
            "type": "string",
            "enum": [
              "Bank",
              "Wallet"
            ],
            "description": "Distinguishes traditional banks from digital wallets / e-money providers."
          }
        }
      },
      "PaymentMethod": {
        "type": "object",
        "required": [
          "name",
          "type"
        ],
        "properties": {
          "code": {
            "type": "string",
            "description": "Method code — pass as `paymentMethod` on PayIn create (v2.1; e.g. `0102`, `upi`, `upi_intent`, `imps`).",
            "examples": [
              "0102"
            ]
          },
          "name": {
            "type": "string",
            "description": "Human-readable method name (e.g. \"Banesco\", \"UPI\", \"UPI Intent\").",
            "examples": [
              "Banesco"
            ]
          },
          "type": {
            "type": "string",
            "description": "Rail identifier, per-method since v2.2 (`ven_bank`,\n`ven_bank_account`, `cvu`, `upi`, `imps`, `kor_bank`, `kakao_pay`).\nSeveral methods may share ONE rail: India's `upi` and `upi_intent`\nare both `type: upi` (v2.6) — same requisites, same trader cards,\nonly the payer hand-off differs.\n",
            "examples": [
              "ven_bank"
            ]
          }
        }
      },
      "Geo": {
        "type": "string",
        "enum": [
          "Argentina",
          "Venezuela",
          "India",
          "SouthKorea"
        ],
        "description": "Region. Numeric form (`1` = Argentina, `2` = Venezuela, `3` = India, `4` = SouthKorea) is also accepted on input."
      },
      "Currency": {
        "type": "string",
        "enum": [
          "ARS",
          "VES",
          "INR",
          "KRW"
        ],
        "description": "ISO 4217 currency code."
      },
      "PayInStatus": {
        "type": "string",
        "enum": [
          "WaitingPayment",
          "Completed",
          "Expired",
          "Failed"
        ]
      },
      "PayOutStatus": {
        "type": "string",
        "enum": [
          "Pending",
          "Claimed",
          "PendingValidation",
          "Completed",
          "Expired",
          "Failed",
          "AwaitingFunds"
        ],
        "description": "`AwaitingFunds` (6) — the pay-out wallet could not cover the pay-out at\ncreation. It is ACCEPTED (201), reserves nothing and is held up to 12 h\nfrom creation, then released when the pay-out wallet covers it (its\n`expiresAt` restarts at release) or auto-expired (`pay_out.expired`).\nNo webhook marks the hold or the release; read\n`GET /pay-out/{payOutId}/status`. Treat 201 as \"accepted, not yet\nsettled\" — wait for the webhook.\n"
      },
      "DisputeStatus": {
        "type": "string",
        "enum": [
          "PendingEvidence",
          "UnderReview",
          "ResolvedForTrader",
          "ResolvedForClient",
          "EvidenceExpired"
        ],
        "description": "`Client` means the merchant. `ResolvedForClient` and `EvidenceExpired`\nare resolved in the merchant's favour; `ResolvedForTrader` against it.\n"
      },
      "DisputeType": {
        "type": "string",
        "enum": [
          "PayIn",
          "PayOut"
        ]
      },
      "CreatePayInRequest": {
        "type": "object",
        "required": [
          "amount",
          "currency",
          "geo",
          "expiresIn",
          "merchantOrderId",
          "clientReference"
        ],
        "properties": {
          "amount": {
            "type": "number",
            "format": "decimal",
            "minimum": 0,
            "exclusiveMinimum": true,
            "description": "Payment amount, greater than zero.",
            "examples": [
              5000.0
            ]
          },
          "currency": {
            "$ref": "#/components/schemas/Currency"
          },
          "geo": {
            "oneOf": [
              {
                "$ref": "#/components/schemas/Geo"
              },
              {
                "type": "integer",
                "enum": [
                  1,
                  2,
                  3,
                  4
                ]
              }
            ],
            "description": "Region code or enum name.",
            "examples": [
              1
            ]
          },
          "expiresIn": {
            "type": "integer",
            "minimum": 60,
            "description": "Lifetime of the PayIn in seconds.",
            "examples": [
              900
            ]
          },
          "merchantOrderId": {
            "type": "string",
            "maxLength": 128,
            "description": "Idempotency key unique to the merchant.",
            "examples": [
              "order-12345"
            ]
          },
          "clientReference": {
            "type": "string",
            "maxLength": 64,
            "pattern": "^[A-Za-z0-9._:-]{1,64}$",
            "description": "REQUIRED. Your stable identifier for YOUR end customer (the payer).\nUsed for deduplication and reconciliation on your side. Must be\nunique per end customer across all transactions. Since v2.18 Atlas\nmay block new pay-ins from a `clientReference` on your account: a\ncreate for a new `merchantOrderId` is then refused with\n`400 CLIENT_BLOCKED` (do not retry; a repeat of an existing\n`merchantOrderId` still returns that pay-in).\n",
            "examples": [
              "customer-7781"
            ]
          },
          "paymentMethod": {
            "type": "string",
            "description": "The payment method CODE for the geo (see `GET /payment-methods`).\nOptional for Argentina/Venezuela — omitted ⇒ the geo's default rail\n(for Venezuela any bank is Pago Móvil). REQUIRED for the multi-rail\ngeos: India (`upi` | `upi_intent` | `imps`, v2.2, `upi_intent` added\nin v2.6) and SouthKorea (`kor_bank` | `kakao_pay`, v2.3) — the rail\ndecides which card serves the order; a missing value returns\n`400 VALIDATION_ERROR`. India's `upi` and `upi_intent` are ONE rail\n(identical behaviour, UTR flow, spreads and webhooks); pick\n`upi_intent` when you want to hand the payer the `upiIntentLink`\ndeep link rather than a QR.\n",
            "examples": [
              "0102",
              "upi",
              "upi_intent",
              "imps",
              "kor_bank",
              "kakao_pay",
              "cvu"
            ]
          },
          "checkout": {
            "$ref": "#/components/schemas/CheckoutRequest"
          },
          "includeUpiAppLinks": {
            "type": "boolean",
            "default": false,
            "description": "v2.14, OPTIONAL. India only. When `true`, an order that has\n`upiIntentLink` (a `upi` / `upi_intent` order served by an Atlas\ntrader card) also returns `upiAppLinks` with PhonePe and Paytm deep\nlinks. Ignored, never rejected, where it does not apply (`imps`,\nexternally routed orders, other geos). Omitted or `false` means the\nresponse is byte-identical to v2.13.\n"
          }
        }
      },
      "UpiAppLinks": {
        "type": "object",
        "required": [
          "phonepe",
          "paytm"
        ],
        "description": "v2.14. Deep links into the payer apps' own \"send money to a UPI ID\"\nscreens (\"quasi-intent\"), pre-filled with the payee VPA and the PAYABLE\n`amount` of the same response, with amount editing disabled. Opaque:\nuse each as a button `href` unchanged. The routes are not public APIs\nof PhonePe or Paytm and can move with an app update, so always keep\n`upiIntentLink` and the QR next to them.\n",
        "properties": {
          "phonepe": {
            "type": "string",
            "description": "PhonePe P2P screen, `phonepe://native?data=<url-encoded base64 JSON>&id=p2ppayment`; the amount travels in paise.",
            "examples": [
              "phonepe://native?data=eyJjb250YWN0Ijp7ImNic05hbWUiOiIiLCJuaWNrTmFtZSI6IiIsInZwYSI6InJhdmkuc2hhcm1hQHlibCIsInR5cGUiOiJWUEEifSwicDJwUGF5bWVudENoZWNrb3V0UGFyYW1zIjp7Im5vdGUiOiIiLCJpc0J5RGVmYXVsdEtub3duQ29udGFjdCI6dHJ1ZSwiZW5hYmxlU3BlZWNoVG9UZXh0IjpmYWxzZSwiYWxsb3dBbW91bnRFZGl0IjpmYWxzZSwic2hvd1FyQ29kZU9wdGlvbiI6ZmFsc2UsImRpc2FibGVWaWV3SGlzdG9yeSI6dHJ1ZSwic2hvdWxkU2hvd1Vuc2F2ZWRDb250YWN0QmFubmVyIjpmYWxzZSwiaXNSZWN1cnJpbmciOmZhbHNlLCJjaGVja291dFR5cGUiOiJERUZBVUxUIiwidHJhbnNhY3Rpb25Db250ZXh0IjoicDJwIiwiaW5pdGlhbEFtb3VudCI6MzA0OTAwLCJkaXNhYmxlTm90ZXNFZGl0Ijp0cnVlLCJzaG93S2V5Ym9hcmQiOmZhbHNlLCJjdXJyZW5jeSI6IklOUiIsInNob3VsZFNob3dNYXNrZWROdW1iZXIiOnRydWV9fQ%3D%3D&id=p2ppayment"
            ]
          },
          "paytm": {
            "type": "string",
            "description": "Paytm send-money screen, `paytmmp://cash_wallet?pa=<vpa>&am=<amount 2dp>&cu=INR&featuretype=money_transfer`.",
            "examples": [
              "paytmmp://cash_wallet?pa=ravi.sharma%40ybl&am=3049.00&cu=INR&featuretype=money_transfer"
            ]
          }
        }
      },
      "CheckoutRequest": {
        "type": "object",
        "required": [
          "returnUrl"
        ],
        "description": "Opt-in for an Atlas-hosted payment page. When present, `expiresIn` must\nbe within 300-3600 seconds and the request must resolve to\n`geo: India` (`paymentMethod: \"upi\"`, `\"upi_intent\"` or `\"imps\"` —\nwidened from the literal `\"upi\"` in v2.6) or to `geo: SouthKorea`\n(`paymentMethod: \"kor_bank\"` or `\"kakao_pay\"`, v2.8), routed to\nthe internal trader pool — otherwise the whole PayIn is rejected with\n`400 CHECKOUT_NOT_SUPPORTED` (the PayIn is NOT created). Fixed at\nfirst create; an idempotent replay with the same `merchantOrderId`\nreturns the originally stored `checkout` values and the same\n`checkout.url` regardless of the replay body.\n",
        "properties": {
          "returnUrl": {
            "type": "string",
            "format": "uri",
            "maxLength": 1024,
            "description": "REQUIRED when `checkout` is present. Absolute HTTPS URL\n(`http` only in Development), no userinfo, no fragment. The payer\nis returned here via `302 GET returnUrl?payInId=…&merchantOrderId=…`\nfor EVERY outcome (success / not confirmed / expired / \"start\nagain\") — the redirect is not a confirmation; credit orders only\nfrom the signed `pay_in.completed` webhook or\n`GET /pay-in/{payInId}/status`. The literal placeholder\n`{PAY_IN_ID}` is replaced with the created `payInId`.\n",
            "examples": [
              "https://shop.example/pay/done?o={PAY_IN_ID}"
            ]
          },
          "cancelUrl": {
            "type": "string",
            "format": "uri",
            "maxLength": 1024,
            "description": "Optional. Same validation rules as `returnUrl`. Used ONLY when the\npayer explicitly cancels on the hosted page; falls back to\n`returnUrl` when omitted.\n",
            "examples": [
              "https://shop.example/cart"
            ]
          },
          "locale": {
            "type": "string",
            "description": "Optional BCP-47 base language (e.g. `hi`, `en`). An unknown value\nnever fails the request — it falls back to the geo default, and\nthe effective locale actually served is echoed back in\n`checkout.locale` on the response.\n",
            "examples": [
              "hi"
            ]
          }
        }
      },
      "CheckoutResponse": {
        "type": "object",
        "required": [
          "url",
          "expiresAt",
          "locale",
          "embedUrl"
        ],
        "description": "Present in the PayIn create response ONLY when `checkout` was\nrequested and accepted — the key is omitted entirely otherwise, so\nexisting integrations see a byte-identical response. On an\nidempotent replay of the same `merchantOrderId`, the same `url` is\nreturned. Members are APPENDED, never re-ordered: `embedUrl` is\nv2.7 and follows `locale`.\n",
        "properties": {
          "url": {
            "type": "string",
            "format": "uri",
            "description": "The hosted payment page URL — redirect or link the payer here.",
            "examples": [
              "https://api.atlas-online.io/pay/AbC9fQ2..."
            ]
          },
          "expiresAt": {
            "type": "string",
            "format": "date-time",
            "description": "Snapshot at creation time — NOT the source of truth. Submitting a\nUTR on the hosted page may extend this once by up to 30 minutes\n(`pay_in.expired` then arrives later accordingly); poll\n`GET /pay-in/{payInId}/status` or wait for the webhook rather\nthan deriving expiry from this value.\n"
          },
          "locale": {
            "type": "string",
            "description": "Effective locale served on the hosted page (after geo-default fallback).",
            "examples": [
              "hi"
            ]
          },
          "embedUrl": {
            "type": "string",
            "format": "uri",
            "description": "v2.7. The SAME page, served embeddable — what goes into\n`Atlas.mount('#atlas-pay', { url: … })` (or, if you build the\n`<iframe>` yourself, its `src`). Always present next to `url`\nwhenever the `checkout` object is present; the two are\nalternatives, not a sequence — send the payer to `url` or embed\n`embedUrl`, not both. Framing only works from an origin the\nmerchant registered for its account (portal session:\n`PUT /api/merchant/checkout-settings/origins`); every other origin\ngets a readable 403 page INSIDE the frame rather than a silent\nblank rectangle. The loader API, the\nevents and the appearance schema are documented at\nhttps://api.atlas-online.io/docs#embedded-checkout.\n",
            "examples": [
              "https://api.atlas-online.io/embed/AbC9fQ2..."
            ]
          }
        }
      },
      "CreatePayInResponse": {
        "description": "v2.9 — PER-GEO response. Only the requisite keys that apply to the\norder's geo and rail are present; a key that does not apply is OMITTED\n(never null / \"\"). Argentina: cbu. Venezuela: recipientPhone +\nrecipientCedula. India upi/upi_intent: upiVpa + upiIntentLink. India imps:\naccountNumber + ifscCode. SouthKorea: accountNumber. qrImageBase64 /\nqrPayload only when the requisite carries a QR. The `required` keys below\nare always present. Key order: common → requisites → QR → app links →\ncheckout → v2.15 pricing (exchangeRate, commissionRate, amountUsdt).\n",
        "type": "object",
        "required": [
          "payInId",
          "displayId",
          "geo",
          "ownerName",
          "bankName",
          "amount",
          "currency",
          "expiresAt",
          "requiresManualConfirmation",
          "exchangeRate",
          "commissionRate",
          "amountUsdt"
        ],
        "properties": {
          "payInId": {
            "type": "string",
            "format": "uuid"
          },
          "displayId": {
            "type": "string",
            "examples": [
              "PI00000007"
            ]
          },
          "geo": {
            "$ref": "#/components/schemas/Geo"
          },
          "ownerName": {
            "type": "string",
            "examples": [
              "Francisco Ezequies"
            ]
          },
          "bankName": {
            "type": "string",
            "examples": [
              "Prex"
            ]
          },
          "cbu": {
            "type": "string",
            "description": "Destination CBU/CVU. Present only when geo = Argentina (a Venezuela order served by an external provider may carry the destination account number here when one is issued)."
          },
          "recipientPhone": {
            "type": "string",
            "description": "Destination phone. Present only when geo = Venezuela."
          },
          "recipientCedula": {
            "type": "string",
            "description": "Destination ID / cédula. Present only when geo = Venezuela."
          },
          "upiVpa": {
            "type": "string",
            "description": "Destination UPI VPA (e.g. payer@axl). Present only for India upi / upi_intent orders (v2.2)."
          },
          "accountNumber": {
            "type": "string",
            "description": "Destination bank account. Present only for India imps orders (v2.2) and SouthKorea kor_bank orders (v2.3)."
          },
          "ifscCode": {
            "type": "string",
            "description": "Destination IFSC branch code. Present only for India imps orders (v2.2)."
          },
          "upiIntentLink": {
            "type": "string",
            "description": "v2.6. UPI deep link for the payer's phone —\n`upi://pay?pa=<vpa>&pn=<payee>&am=<amount 2dp>&cu=INR`.\nSince v2.10 the link carries the payee, the amount and the currency only — the tn / tr parameters (our displayId) were removed.\nPresent for EVERY India pay-in served on the UPI rail (`upi` AND\n`upi_intent`), idempotent replays included; the key is OMITTED\nentirely for `imps`, for non-India geos and for cascade-routed\norders. On Android render it as a tap target (\"Pay in UPI app\") —\nthe UPI app opens pre-filled with the VPA, payee name and exact\namount; keep the QR (`qrImageBase64` / `upiVpa`) as the\niOS/desktop fallback. A payer UTR is optional. NOT\npresent in the webhooks or in `GET /pay-in/{payInId}/status`.\n",
            "examples": [
              "upi://pay?pa=ravi.sharma%40ybl&pn=Ravi%20Sharma&am=3049.00&cu=INR"
            ]
          },
          "amount": {
            "type": "number",
            "format": "decimal",
            "description": "What the PAYER has to transfer. Show THIS value exactly. On an India UPI order of a merchant with amount adjustment enabled it may be above or below `requestedAmount` by up to the range configured for that merchant, and may carry paise (see below); on every other rail and market the two are equal."
          },
          "requestedAmount": {
            "type": "number",
            "format": "decimal",
            "description": "What you asked for, and what you are credited for. Equal to `amount` unless the payable amount was moved. Always present (v2.11)."
          },
          "amountAdjustment": {
            "type": "number",
            "format": "decimal",
            "description": "`amount` minus `requestedAmount`: on India UPI orders within ± the range configured for the merchant (e.g. -2..2 by 1, or -1.5..1.5 by 0.5), so it can be negative and may be fractional; 0 when the amount was not moved. Always present (v2.11)."
          },
          "currency": {
            "$ref": "#/components/schemas/Currency"
          },
          "expiresAt": {
            "type": "string",
            "format": "date-time"
          },
          "requiresManualConfirmation": {
            "type": "boolean",
            "description": "true when trader confirmation is expected (always for Venezuela, India and SouthKorea)."
          },
          "qrImageBase64": {
            "type": "string",
            "description": "QR image as a data URI (`data:image/png;base64,...`). Present only when the requisite carries a QR (v2.9 — previously null)."
          },
          "qrPayload": {
            "type": "string",
            "description": "Raw host-to-host QR payload (EMV / PIX / upi://pay) for rendering the QR yourself. Present only when the requisite carries a QR (v2.9 — previously null)."
          },
          "upiAppLinks": {
            "allOf": [
              {
                "$ref": "#/components/schemas/UpiAppLinks"
              }
            ],
            "description": "v2.14. Present ONLY when the request sent `includeUpiAppLinks: true`\nand the order has `upiIntentLink`; the key is omitted otherwise.\nPlaced right before `checkout`. Idempotent replays return the same\nlinks when the replay also sends the flag. NOT present in the\nwebhooks or in `GET /pay-in/{payInId}/status`.\n"
          },
          "checkout": {
            "$ref": "#/components/schemas/CheckoutResponse"
          },
          "exchangeRate": {
            "type": "number",
            "format": "decimal",
            "description": "Your exchange rate for this order, fixed when it was created: units of `currency` per 1 USDT. `requestedAmount / exchangeRate` is the gross USDT value of the order. Always present (v2.15).",
            "examples": [
              105.5
            ]
          },
          "commissionRate": {
            "type": "number",
            "format": "decimal",
            "description": "Your commission on this order, in percent (8.5 means 8.5%). Always present (v2.15).",
            "examples": [
              8.5
            ]
          },
          "amountUsdt": {
            "type": "number",
            "format": "decimal",
            "description": "The USDT credited to your balance when the order completes: requestedAmount / exchangeRate × (1 − commissionRate / 100), rounded to 6 decimal places. The same value as `amountUsdt` in the pay_in.completed webhook. Always present (v2.15).",
            "examples": [
              43.364929
            ]
          }
        }
      },
      "PayInStatusResponse": {
        "type": "object",
        "required": [
          "payInId",
          "displayId",
          "status",
          "amount",
          "currency",
          "createdAt",
          "expiresAt",
          "geo",
          "isManualConfirmation",
          "requiresManualConfirmation",
          "exchangeRate",
          "commissionRate",
          "amountUsdt"
        ],
        "properties": {
          "payInId": {
            "type": "string",
            "format": "uuid"
          },
          "displayId": {
            "type": "string"
          },
          "status": {
            "$ref": "#/components/schemas/PayInStatus"
          },
          "amount": {
            "type": "number",
            "format": "decimal",
            "description": "What the payer has to transfer."
          },
          "requestedAmount": {
            "type": "number",
            "format": "decimal",
            "description": "What you asked for, and are credited for (v2.11)."
          },
          "amountAdjustment": {
            "type": "number",
            "format": "decimal",
            "description": "`amount` minus `requestedAmount`; on India UPI within ± the range configured for the merchant (may be fractional, can be negative); 0 when not moved (v2.11)."
          },
          "originalAmount": {
            "type": "number",
            "format": "decimal",
            "description": "What you originally asked for; equals `requestedAmount` unless support closed the order on a lower amount the payer actually sent (then `amount`/`requestedAmount` are the received amount). Always present (v2.12)."
          },
          "currency": {
            "$ref": "#/components/schemas/Currency"
          },
          "createdAt": {
            "type": "string",
            "format": "date-time"
          },
          "expiresAt": {
            "type": "string",
            "format": "date-time"
          },
          "completedAt": {
            "type": [
              "string",
              "null"
            ],
            "format": "date-time"
          },
          "expiredAt": {
            "type": [
              "string",
              "null"
            ],
            "format": "date-time"
          },
          "payerName": {
            "type": [
              "string",
              "null"
            ]
          },
          "geo": {
            "$ref": "#/components/schemas/Geo"
          },
          "isManualConfirmation": {
            "type": "boolean"
          },
          "requiresManualConfirmation": {
            "type": "boolean"
          },
          "utr": {
            "type": [
              "string",
              "null"
            ]
          },
          "qrPayload": {
            "type": [
              "string",
              "null"
            ],
            "description": "Raw host-to-host EMV/PIX string, or null."
          },
          "clientReference": {
            "type": [
              "string",
              "null"
            ],
            "description": "Echo of the `clientReference` sent on creation."
          },
          "exchangeRate": {
            "type": "number",
            "format": "decimal",
            "description": "Your exchange rate for this order, fixed when it was created: units of `currency` per 1 USDT. Same value as on the create response (v2.15)."
          },
          "commissionRate": {
            "type": "number",
            "format": "decimal",
            "description": "Your commission on this order, in percent (8.5 means 8.5%) (v2.15)."
          },
          "amountUsdt": {
            "type": "number",
            "format": "decimal",
            "description": "The USDT credited to your balance when the order completes: requestedAmount / exchangeRate × (1 − commissionRate / 100), 6 decimal places; the same value as `amountUsdt` in the pay_in.completed webhook. On an order closed on a lower received amount it is priced on that amount (v2.15)."
          }
        }
      },
      "SubmitUtrRequest": {
        "type": "object",
        "required": [
          "utr"
        ],
        "properties": {
          "utr": {
            "type": "string",
            "maxLength": 128,
            "description": "Bank-issued transaction reference reported by the payer.",
            "examples": [
              "0086543219"
            ]
          }
        }
      },
      "SubmitUtrResponse": {
        "type": "object",
        "required": [
          "payInId",
          "displayId",
          "status",
          "utr",
          "requiresManualConfirmation"
        ],
        "properties": {
          "payInId": {
            "type": "string",
            "format": "uuid"
          },
          "displayId": {
            "type": "string"
          },
          "status": {
            "$ref": "#/components/schemas/PayInStatus"
          },
          "utr": {
            "type": "string"
          },
          "requiresManualConfirmation": {
            "type": "boolean"
          }
        }
      },
      "PayOutCommon": {
        "type": "object",
        "required": [
          "recipientName",
          "amount",
          "currency",
          "geo",
          "expiresIn",
          "merchantOrderId",
          "clientReference"
        ],
        "properties": {
          "recipientName": {
            "type": "string",
            "description": "Recipient full name as registered in the bank."
          },
          "amount": {
            "type": "number",
            "format": "decimal"
          },
          "currency": {
            "$ref": "#/components/schemas/Currency"
          },
          "geo": {
            "oneOf": [
              {
                "$ref": "#/components/schemas/Geo"
              },
              {
                "type": "integer",
                "enum": [
                  1,
                  2,
                  3,
                  4
                ]
              }
            ]
          },
          "expiresIn": {
            "type": "integer",
            "minimum": 60
          },
          "merchantOrderId": {
            "type": "string",
            "maxLength": 128
          },
          "clientReference": {
            "type": "string",
            "maxLength": 64,
            "pattern": "^[A-Za-z0-9._:-]{1,64}$",
            "description": "REQUIRED. Your stable identifier for the recipient end customer.\n",
            "examples": [
              "customer-7781"
            ]
          }
        }
      },
      "CreatePayOutArgentinaRequest": {
        "description": "Argentina pay-out — only `recipientCbu` as recipient identifier.",
        "allOf": [
          {
            "$ref": "#/components/schemas/PayOutCommon"
          },
          {
            "type": "object",
            "required": [
              "recipientCbu"
            ],
            "properties": {
              "recipientCbu": {
                "type": "string",
                "pattern": "^\\d{22}$",
                "minLength": 22,
                "maxLength": 22,
                "description": "Argentina-only. 22-digit CBU with checksum validation. The server\nre-runs the SUDEBAN modulo-10 weighted-checksum check; an invalid\nCBU is rejected with `VALIDATION_ERROR`.\n"
              }
            }
          }
        ]
      },
      "CreatePayOutVenezuelaRequest": {
        "description": "Venezuela pay-out — all four recipient fields are required.\nSending a field that does not belong to Venezuela (e.g. `recipientCbu`) is\nrejected with `400 VALIDATION_ERROR`.\n",
        "allOf": [
          {
            "$ref": "#/components/schemas/PayOutCommon"
          },
          {
            "type": "object",
            "required": [
              "recipientPhone",
              "recipientCedula",
              "recipientBank",
              "recipientAccountNumber"
            ],
            "properties": {
              "recipientPhone": {
                "type": "string",
                "description": "Venezuela-only. Phone number associated with the destination wallet."
              },
              "recipientCedula": {
                "type": "string",
                "description": "Venezuela-only. Recipient national ID (cédula)."
              },
              "recipientBank": {
                "type": "string",
                "description": "Venezuela-only. Destination bank or payment provider (e.g. Banesco, Pago Móvil, Zinli)."
              },
              "recipientAccountNumber": {
                "type": "string",
                "description": "Venezuela-only. Bank account number (numeroDeCuenta). NEW in v2.0.\nRequired for Venezuela pay-outs.\n"
              }
            }
          }
        ]
      },
      "CreatePayOutIndiaImpsRequest": {
        "description": "India pay-out on the IMPS rail — `paymentMethod: \"imps\"`, or omit\n`paymentMethod` entirely (it defaults to `imps`). `recipientUpiVpa` is\nrejected here with `VALIDATION_ERROR` (\"recipientUpiVpa is only valid\nfor paymentMethod upi\"). Amount must be a WHOLE number of INR —\nfractional values are rejected, never rounded. Sending a field that does\nnot belong to India (`recipientCbu`, `recipientPhone`,\n`recipientCedula`) is rejected with `400 VALIDATION_ERROR`.\n",
        "allOf": [
          {
            "$ref": "#/components/schemas/PayOutCommon"
          },
          {
            "type": "object",
            "required": [
              "recipientAccountNumber",
              "recipientBank",
              "recipientIfsc"
            ],
            "properties": {
              "recipientAccountNumber": {
                "type": "string",
                "description": "India. IMPS beneficiary account number."
              },
              "recipientBank": {
                "type": "string",
                "description": "India. Beneficiary bank display name (e.g. HDFC Bank)."
              },
              "recipientIfsc": {
                "type": "string",
                "pattern": "^[A-Z]{4}0[A-Z0-9]{6}$",
                "minLength": 11,
                "maxLength": 11,
                "description": "India-only. IFSC branch code (AAAA0XXXXXX), stored uppercase."
              },
              "paymentMethod": {
                "type": "string",
                "enum": [
                  "imps"
                ],
                "description": "Optional on this rail — omitted ⇒ `imps`."
              }
            }
          }
        ]
      },
      "CreatePayOutIndiaUpiRequest": {
        "description": "India pay-out on the UPI rail (v2.6) — `paymentMethod: \"upi\"` is\nREQUIRED (omitting it selects `imps`) and `recipientUpiVpa` is the WHOLE\nrecipient object. The IMPS trio is rejected here with `VALIDATION_ERROR`\n(\"recipientAccountNumber, recipientBank and recipientIfsc are not valid\nfor India UPI pay-outs; send recipientUpiVpa only\"), as are the foreign\nfields `recipientCbu` / `recipientPhone` / `recipientCedula`. Amount must\nbe a WHOLE number of INR. Operationally an Atlas trader sends the money\nfrom their own UPI app to the VPA and uploads the proof; you are notified\nby the same `pay_out.completed` webhook.\n",
        "allOf": [
          {
            "$ref": "#/components/schemas/PayOutCommon"
          },
          {
            "type": "object",
            "required": [
              "recipientUpiVpa",
              "paymentMethod"
            ],
            "properties": {
              "recipientUpiVpa": {
                "type": "string",
                "maxLength": 255,
                "description": "India-only, UPI rail only (v2.6). Beneficiary UPI ID / VPA in\n`handle@psp` form; stored lower-cased. Rejected with\n`VALIDATION_ERROR` when absent on a `upi` pay-out\n(\"recipientUpiVpa is required for India UPI pay-outs\"), when\nmalformed (\"recipientUpiVpa has invalid UPI ID format (expected\nhandle@psp)\"), and when sent on any other rail or geo\n(\"recipientUpiVpa is only valid for paymentMethod upi\" /\n\"recipientUpiVpa is only valid for India\").\n",
                "examples": [
                  "ravi.kumar@ybl"
                ]
              },
              "paymentMethod": {
                "type": "string",
                "enum": [
                  "upi"
                ],
                "description": "REQUIRED to select the UPI rail. Any value other than `imps` or\n`upi` is rejected with `VALIDATION_ERROR` (\"paymentMethod\n'<x>' is not available for India pay-outs; use imps or upi\").\n"
              }
            }
          }
        ]
      },
      "CreatePayOutSouthKoreaRequest": {
        "description": "South Korea pay-out — kor_bank bank transfer is the ONLY rail.\n`paymentMethod` may be omitted (defaults to `kor_bank`); `\"kakao_pay\"`\nis rejected with `VALIDATION_ERROR`. Amount must be a WHOLE number of\nKRW — fractional values are rejected, never rounded. Sending a field\nthat does not belong to South Korea (`recipientCbu`, `recipientPhone`,\n`recipientCedula`, `recipientIfsc`, `recipientUpiVpa`) is rejected with\n`400 VALIDATION_ERROR` — `recipientUpiVpa` outside India always returns\n\"recipientUpiVpa is only valid for India\".\n",
        "allOf": [
          {
            "$ref": "#/components/schemas/PayOutCommon"
          },
          {
            "type": "object",
            "required": [
              "recipientAccountNumber",
              "recipientBank"
            ],
            "properties": {
              "recipientAccountNumber": {
                "type": "string",
                "pattern": "^[\\d\\- ]{5,26}$",
                "description": "South Korea. Beneficiary account number — 5-20 digits; hyphens\nand spaces are accepted on input and stripped before storage\n(e.g. `110-123-456789`).\n"
              },
              "recipientBank": {
                "type": "string",
                "description": "South Korea. Beneficiary bank display name. Prefer a `name` (or\n`code`) from `GET /banks?geo=SouthKorea`, but any non-blank\nvalue is accepted.\n"
              },
              "paymentMethod": {
                "type": "string",
                "description": "Optional. South Korea pay-out rail — only `kor_bank` is accepted; omitted ⇒ `kor_bank`."
              }
            }
          }
        ]
      },
      "CreatePayOutRequest": {
        "description": "Per-geo recipient object. Send ONLY the fields that belong to the geo.\nA field that does not belong to the geo is rejected with `400 VALIDATION_ERROR`.\n- **Argentina**: include `recipientCbu` (required); do NOT send VE fields.\n- **Venezuela**: include `recipientPhone` + `recipientCedula` + `recipientBank` + `recipientAccountNumber` (all required); do NOT send `recipientCbu`.\n- **India** (v2.2, two rails since v2.6) — pick the rail with `paymentMethod`:\n  `imps` (default when omitted) ⇒ `recipientAccountNumber` + `recipientBank` + `recipientIfsc` (all required);\n  `upi` ⇒ `recipientUpiVpa` (required, `handle@psp`) and NOTHING from the IMPS trio.\n  The rails are mutually exclusive; whole INR amounts on both.\n- **SouthKorea** (v2.3): include `recipientAccountNumber` + `recipientBank` (both required); kor_bank only; whole KRW.\n",
        "oneOf": [
          {
            "$ref": "#/components/schemas/CreatePayOutArgentinaRequest"
          },
          {
            "$ref": "#/components/schemas/CreatePayOutVenezuelaRequest"
          },
          {
            "$ref": "#/components/schemas/CreatePayOutIndiaImpsRequest"
          },
          {
            "$ref": "#/components/schemas/CreatePayOutIndiaUpiRequest"
          },
          {
            "$ref": "#/components/schemas/CreatePayOutSouthKoreaRequest"
          }
        ],
        "examples": {
          "Argentina": {
            "summary": "Argentina — CBU",
            "value": {
              "recipientCbu": "0000177501057000189625",
              "recipientName": "Maria Garcia Lopez",
              "amount": 5000.0,
              "currency": "ARS",
              "geo": 1,
              "expiresIn": 1800,
              "merchantOrderId": "payout-67890",
              "clientReference": "customer-7781"
            }
          },
          "Venezuela": {
            "summary": "Venezuela — bank account",
            "value": {
              "recipientPhone": "+584121234567",
              "recipientCedula": "V-12345678",
              "recipientBank": "Banesco",
              "recipientAccountNumber": "01020123456789012345",
              "recipientName": "Carlos Rodríguez",
              "amount": 1500.0,
              "currency": "VES",
              "geo": 2,
              "expiresIn": 1800,
              "merchantOrderId": "payout-ve-12345",
              "clientReference": "customer-7781"
            }
          },
          "India": {
            "summary": "India — IMPS",
            "value": {
              "recipientAccountNumber": "40145477128",
              "recipientBank": "HDFC Bank",
              "recipientIfsc": "HDFC0001234",
              "recipientName": "Ravi Kumar",
              "amount": 10000,
              "currency": "INR",
              "geo": 3,
              "expiresIn": 1800,
              "merchantOrderId": "payout-in-12345",
              "clientReference": "customer-7781"
            }
          },
          "IndiaUpi": {
            "summary": "India — UPI (v2.6)",
            "value": {
              "recipientUpiVpa": "ravi.kumar@ybl",
              "recipientName": "Ravi Kumar",
              "amount": 10000,
              "currency": "INR",
              "geo": 3,
              "expiresIn": 1800,
              "merchantOrderId": "payout-in-upi-12345",
              "clientReference": "customer-7781",
              "paymentMethod": "upi"
            }
          },
          "SouthKorea": {
            "summary": "South Korea — kor_bank",
            "value": {
              "recipientAccountNumber": "110-123-456789",
              "recipientBank": "KB Kookmin Bank",
              "recipientName": "Kim Minseo",
              "amount": 150000,
              "currency": "KRW",
              "geo": 4,
              "expiresIn": 1800,
              "merchantOrderId": "payout-kr-12345",
              "clientReference": "customer-7781"
            }
          }
        }
      },
      "CreatePayOutResponse": {
        "type": "object",
        "required": [
          "payOutId",
          "displayId",
          "amountFiat",
          "amountUsdt",
          "currency",
          "expiresAt"
        ],
        "properties": {
          "payOutId": {
            "type": "string",
            "format": "uuid"
          },
          "displayId": {
            "type": "string",
            "examples": [
              "PO00000001"
            ]
          },
          "amountFiat": {
            "type": "number",
            "format": "decimal"
          },
          "amountUsdt": {
            "type": "number",
            "format": "decimal",
            "description": "Pay-out amount in USDT at the creation-time rate, without the pay-out commission."
          },
          "currency": {
            "$ref": "#/components/schemas/Currency"
          },
          "expiresAt": {
            "type": "string",
            "format": "date-time"
          },
          "recipientUpiVpa": {
            "type": "string",
            "description": "v2.6. The normalised (lower-cased) beneficiary VPA we stored.\nPresent ONLY on an India UPI pay-out — the key is OMITTED for every\nother geo/rail, so Argentina / Venezuela / SouthKorea / India-IMPS\nresponses are byte-identical to v2.5.\n",
            "examples": [
              "ravi.kumar@ybl"
            ]
          }
        }
      },
      "PayOutStatusResponse": {
        "type": "object",
        "required": [
          "displayId",
          "status",
          "amountFiat",
          "currency",
          "amountUsdt",
          "recipientName",
          "createdAt",
          "expiresAt"
        ],
        "properties": {
          "displayId": {
            "type": "string"
          },
          "status": {
            "$ref": "#/components/schemas/PayOutStatus"
          },
          "amountFiat": {
            "type": "number",
            "format": "decimal"
          },
          "currency": {
            "$ref": "#/components/schemas/Currency"
          },
          "amountUsdt": {
            "type": "number",
            "format": "decimal"
          },
          "geo": {
            "$ref": "#/components/schemas/Geo"
          },
          "recipientCbu": {
            "type": [
              "string",
              "null"
            ]
          },
          "recipientPhone": {
            "type": [
              "string",
              "null"
            ]
          },
          "recipientCedula": {
            "type": [
              "string",
              "null"
            ]
          },
          "recipientBank": {
            "type": [
              "string",
              "null"
            ]
          },
          "recipientAccountNumber": {
            "type": [
              "string",
              "null"
            ],
            "description": "Bank account number (VE numeroDeCuenta / India IMPS / SouthKorea kor_bank beneficiary account)."
          },
          "recipientIfsc": {
            "type": [
              "string",
              "null"
            ],
            "description": "India IFSC branch code (v2.2). Null for other geos and for an India UPI pay-out."
          },
          "recipientUpiVpa": {
            "type": "string",
            "description": "India UPI beneficiary VPA (v2.6), lower-cased. Present ONLY on an\nIndia UPI pay-out — the key is OMITTED for every other geo/rail, so\nexisting status responses stay byte-identical.\n",
            "examples": [
              "ravi.kumar@ybl"
            ]
          },
          "recipientName": {
            "type": "string"
          },
          "clientReference": {
            "type": [
              "string",
              "null"
            ],
            "description": "Echo of the clientReference sent on creation."
          },
          "createdAt": {
            "type": "string",
            "format": "date-time"
          },
          "expiresAt": {
            "type": "string",
            "format": "date-time"
          },
          "completedAt": {
            "type": [
              "string",
              "null"
            ],
            "format": "date-time"
          },
          "expiredAt": {
            "type": [
              "string",
              "null"
            ],
            "format": "date-time"
          },
          "supportNote": {
            "type": [
              "string",
              "null"
            ]
          }
        }
      },
      "WithdrawalStatus": {
        "type": "string",
        "enum": [
          "Pending",
          "Processing",
          "Completed",
          "Failed"
        ],
        "description": "Pending → Completed | Failed. Withdrawals are paid out by the platform's finance team; Completed carries the on-chain txHash, Failed carries failureReason and refunds the amount to W1. Processing (v2.13) — an automatic transfer is on its way (only for a withdrawal requested automatically at the threshold); txHash stays null until Completed, and Processing → Pending means the automatic transfer could not be made and it will be paid by hand."
      },
      "PendingWithdrawal": {
        "type": "object",
        "required": [
          "withdrawalId",
          "amount",
          "status",
          "createdAt"
        ],
        "properties": {
          "withdrawalId": {
            "type": "string",
            "format": "uuid"
          },
          "amount": {
            "type": "number",
            "format": "decimal"
          },
          "status": {
            "$ref": "#/components/schemas/WithdrawalStatus"
          },
          "createdAt": {
            "type": "string",
            "format": "date-time"
          }
        }
      },
      "MerchantBalanceResponse": {
        "type": "object",
        "required": [
          "currency",
          "availableBalance",
          "payOutBalance",
          "reservedBalance",
          "totalBalance",
          "autoSettleEnabled",
          "accumulationThreshold"
        ],
        "properties": {
          "currency": {
            "type": "string",
            "enum": [
              "USDT"
            ]
          },
          "availableBalance": {
            "type": "number",
            "format": "decimal",
            "description": "W1 — pay-in wallet",
            "withdrawable.": null
          },
          "payOutBalance": {
            "type": "number",
            "format": "decimal",
            "description": "W2 — pay-out wallet backing PayOut reservations."
          },
          "reservedBalance": {
            "type": "number",
            "format": "decimal",
            "description": "Locked by in-flight PayOuts."
          },
          "totalBalance": {
            "type": "number",
            "format": "decimal",
            "description": "availableBalance + payOutBalance + reservedBalance."
          },
          "walletAddress": {
            "type": [
              "string",
              "null"
            ],
            "description": "USDT (TRC-20) address withdrawals are sent to; null until configured."
          },
          "autoSettleEnabled": {
            "type": "boolean",
            "description": "When true, the whole W1 is requested for withdrawal automatically at accumulationThreshold; such a withdrawal may be sent automatically (Processing while on its way). Managed by the platform."
          },
          "accumulationThreshold": {
            "type": "number",
            "format": "decimal"
          },
          "pendingWithdrawal": {
            "oneOf": [
              {
                "$ref": "#/components/schemas/PendingWithdrawal"
              },
              {
                "type": "null"
              }
            ],
            "description": "The withdrawal currently Pending / Processing (an automatic transfer on its way), or null."
          }
        }
      },
      "CreateWithdrawalRequest": {
        "type": "object",
        "properties": {
          "amount": {
            "type": [
              "number",
              "null"
            ],
            "format": "decimal",
            "exclusiveMinimum": 0,
            "description": "USDT to withdraw (≤ availableBalance). Omit — or send no body — to withdraw the whole available balance."
          }
        }
      },
      "WithdrawalResponse": {
        "type": "object",
        "required": [
          "withdrawalId",
          "amount",
          "currency",
          "walletAddress",
          "status",
          "createdAt"
        ],
        "properties": {
          "withdrawalId": {
            "type": "string",
            "format": "uuid"
          },
          "amount": {
            "type": "number",
            "format": "decimal"
          },
          "currency": {
            "type": "string",
            "enum": [
              "USDT"
            ]
          },
          "walletAddress": {
            "type": "string"
          },
          "status": {
            "$ref": "#/components/schemas/WithdrawalStatus"
          },
          "txHash": {
            "type": [
              "string",
              "null"
            ]
          },
          "failureReason": {
            "type": [
              "string",
              "null"
            ]
          },
          "createdAt": {
            "type": "string",
            "format": "date-time"
          },
          "submittedAt": {
            "type": [
              "string",
              "null"
            ],
            "format": "date-time"
          },
          "confirmedAt": {
            "type": [
              "string",
              "null"
            ],
            "format": "date-time"
          }
        }
      },
      "WithdrawalListResponse": {
        "type": "object",
        "required": [
          "items",
          "totalCount",
          "page",
          "pageSize"
        ],
        "properties": {
          "items": {
            "type": "array",
            "items": {
              "$ref": "#/components/schemas/WithdrawalResponse"
            }
          },
          "totalCount": {
            "type": "integer"
          },
          "page": {
            "type": "integer"
          },
          "pageSize": {
            "type": "integer"
          }
        }
      },
      "CreateTransferRequest": {
        "type": "object",
        "required": [
          "amount",
          "clientReference"
        ],
        "properties": {
          "amount": {
            "type": "number",
            "format": "decimal",
            "exclusiveMinimum": 0,
            "description": "USDT to move from W1 to W2; ≤ availableBalance, at most 6 decimal places."
          },
          "clientReference": {
            "type": "string",
            "minLength": 1,
            "maxLength": 64,
            "pattern": "^[A-Za-z0-9._:-]+$",
            "description": "Your idempotency key, unique per merchant. The same reference with the same amount replays the original transfer (200); with another amount it is refused (409 IDEMPOTENCY_KEY_MISMATCH)."
          }
        }
      },
      "TransferBalances": {
        "type": "object",
        "required": [
          "availableBalance",
          "payOutBalance",
          "reservedBalance",
          "totalBalance"
        ],
        "description": "The wallets right after this transfer was booked (field names of GET /balance). Pay-outs released by the transfer are reserved afterwards; read GET /balance for the live position.",
        "properties": {
          "availableBalance": {
            "type": "number",
            "format": "decimal",
            "description": "W1 after the transfer."
          },
          "payOutBalance": {
            "type": "number",
            "format": "decimal",
            "description": "W2 after the transfer."
          },
          "reservedBalance": {
            "type": "number",
            "format": "decimal"
          },
          "totalBalance": {
            "type": "number",
            "format": "decimal",
            "description": "availableBalance + payOutBalance + reservedBalance."
          }
        }
      },
      "TransferResponse": {
        "type": "object",
        "required": [
          "transferId",
          "clientReference",
          "amount",
          "currency",
          "direction",
          "source",
          "createdAt",
          "balances"
        ],
        "properties": {
          "transferId": {
            "type": "string",
            "format": "uuid"
          },
          "clientReference": {
            "type": [
              "string",
              "null"
            ],
            "description": "Your reference; null for a transfer made in the merchant cabinet."
          },
          "amount": {
            "type": "number",
            "format": "decimal"
          },
          "currency": {
            "type": "string",
            "enum": [
              "USDT"
            ]
          },
          "direction": {
            "type": "string",
            "enum": [
              "payInToPayOut"
            ],
            "description": "Always W1 → W2."
          },
          "source": {
            "type": "string",
            "enum": [
              "api",
              "manual",
              "autoSettle",
              "payOutWalletFill"
            ],
            "description": "api for a transfer made through this API; manual for one made in the merchant cabinet or by ATLAS staff at the merchant's request; autoSettle for the pay-out balance top-up automatic settlement moves before a withdrawal (v2.17); payOutWalletFill for part of a pay-in credit moved to W2 when Atlas has set a pay-out balance fill level for the merchant."
          },
          "createdAt": {
            "type": "string",
            "format": "date-time"
          },
          "balances": {
            "$ref": "#/components/schemas/TransferBalances"
          }
        }
      },
      "TransferListResponse": {
        "type": "object",
        "required": [
          "items",
          "totalCount",
          "page",
          "pageSize"
        ],
        "properties": {
          "items": {
            "type": "array",
            "items": {
              "$ref": "#/components/schemas/TransferResponse"
            }
          },
          "totalCount": {
            "type": "integer"
          },
          "page": {
            "type": "integer"
          },
          "pageSize": {
            "type": "integer"
          }
        }
      },
      "CreateDisputeRequest": {
        "type": "object",
        "required": [
          "transactionType",
          "transactionId",
          "reason",
          "merchantOrderId",
          "disputeAmount"
        ],
        "properties": {
          "transactionType": {
            "oneOf": [
              {
                "$ref": "#/components/schemas/DisputeType"
              },
              {
                "type": "integer",
                "enum": [
                  0,
                  1
                ]
              }
            ]
          },
          "transactionId": {
            "type": "string",
            "format": "uuid"
          },
          "reason": {
            "type": "string",
            "maxLength": 2000
          },
          "merchantOrderId": {
            "type": "string",
            "maxLength": 128
          },
          "disputeAmount": {
            "type": "number",
            "format": "decimal",
            "description": "REQUIRED. The contested amount in the transaction currency.\nMay be partial (0 < disputeAmount ≤ transaction amount).\n",
            "examples": [
              750.0
            ]
          }
        }
      },
      "CreateDisputeResponse": {
        "type": "object",
        "required": [
          "disputeId",
          "displayId",
          "status",
          "evidenceDeadline"
        ],
        "properties": {
          "disputeId": {
            "type": "string",
            "format": "uuid"
          },
          "displayId": {
            "type": "string",
            "examples": [
              "DS00000001"
            ]
          },
          "status": {
            "$ref": "#/components/schemas/DisputeStatus"
          },
          "evidenceDeadline": {
            "type": "string",
            "format": "date-time"
          }
        }
      },
      "DisputeAttachment": {
        "type": "object",
        "properties": {
          "id": {
            "type": "string",
            "format": "uuid"
          },
          "fileName": {
            "type": "string"
          },
          "fileUrl": {
            "type": "string",
            "format": "uri"
          },
          "contentType": {
            "type": "string"
          },
          "fileSize": {
            "type": "integer"
          }
        }
      },
      "DisputeMessage": {
        "type": "object",
        "properties": {
          "id": {
            "type": "string",
            "format": "uuid"
          },
          "senderType": {
            "type": "string",
            "enum": [
              "System",
              "MerchantApi",
              "Trader",
              "Support"
            ]
          },
          "senderName": {
            "type": "string"
          },
          "body": {
            "type": "string"
          },
          "createdAt": {
            "type": "string",
            "format": "date-time"
          },
          "attachments": {
            "type": "array",
            "items": {
              "$ref": "#/components/schemas/DisputeAttachment"
            }
          }
        }
      },
      "DisputeDetail": {
        "type": "object",
        "properties": {
          "id": {
            "type": "string",
            "format": "uuid"
          },
          "displayId": {
            "type": "string"
          },
          "merchantOrderId": {
            "type": "string"
          },
          "disputeType": {
            "$ref": "#/components/schemas/DisputeType"
          },
          "transactionId": {
            "type": "string",
            "format": "uuid"
          },
          "amountFiat": {
            "type": "number",
            "format": "decimal"
          },
          "currency": {
            "$ref": "#/components/schemas/Currency"
          },
          "geo": {
            "$ref": "#/components/schemas/Geo"
          },
          "merchantReason": {
            "type": "string"
          },
          "status": {
            "$ref": "#/components/schemas/DisputeStatus"
          },
          "evidenceDeadline": {
            "type": "string",
            "format": "date-time"
          },
          "isOverdue": {
            "type": "boolean"
          },
          "resolution": {
            "type": [
              "string",
              "null"
            ]
          },
          "createdAt": {
            "type": "string",
            "format": "date-time"
          },
          "resolvedAt": {
            "type": [
              "string",
              "null"
            ],
            "format": "date-time"
          },
          "merchantThread": {
            "type": "array",
            "items": {
              "$ref": "#/components/schemas/DisputeMessage"
            }
          }
        }
      }
    },
    "responses": {
      "Unauthorized": {
        "description": "Authentication failure (missing / invalid headers).",
        "content": {
          "application/json": {
            "schema": {
              "$ref": "#/components/schemas/Error"
            },
            "examples": {
              "invalidSignature": {
                "value": {
                  "error": "INVALID_SIGNATURE",
                  "message": "Signature verification failed"
                }
              },
              "expiredRequest": {
                "value": {
                  "error": "EXPIRED_REQUEST",
                  "message": "Timestamp outside ±300s window"
                }
              }
            }
          }
        }
      },
      "BadRequest": {
        "description": "Validation or business-rule failure.",
        "content": {
          "application/json": {
            "schema": {
              "$ref": "#/components/schemas/Error"
            }
          }
        }
      },
      "ConcurrentUpdate": {
        "description": "The request collided with a concurrent request on the same data and was not completed, or waited about 5 s for other requests on the same merchant and gave up. Retry it with the same body; a create retried with the same merchantOrderId (or a transfer with the same clientReference) never makes a duplicate.",
        "content": {
          "application/json": {
            "schema": {
              "$ref": "#/components/schemas/Error"
            },
            "examples": {
              "concurrentUpdate": {
                "value": {
                  "error": "CONCURRENT_UPDATE",
                  "message": "A concurrent request changed the same data at the same moment and this request was not completed. Retry the request."
                }
              }
            }
          }
        }
      },
      "NotFound": {
        "description": "Resource not found or not owned by caller.",
        "content": {
          "application/json": {
            "schema": {
              "$ref": "#/components/schemas/Error"
            },
            "examples": {
              "notFound": {
                "value": {
                  "error": "NOT_FOUND",
                  "message": "PayIn not found"
                }
              }
            }
          }
        }
      }
    }
  },
  "paths": {
    "/api/merchant/pay-in": {
      "post": {
        "tags": [
          "PayIn"
        ],
        "operationId": "createPayIn",
        "summary": "Create PayIn",
        "description": "Reserves a banking destination for an inbound payment and returns the\nrecipient details that the merchant presents to the customer.\nIdempotent on `merchantOrderId`.\n",
        "requestBody": {
          "required": true,
          "content": {
            "application/json": {
              "schema": {
                "$ref": "#/components/schemas/CreatePayInRequest"
              },
              "examples": {
                "Argentina": {
                  "summary": "Argentina",
                  "value": {
                    "amount": 5000.0,
                    "currency": "ARS",
                    "geo": 1,
                    "expiresIn": 900,
                    "merchantOrderId": "order-12345",
                    "clientReference": "customer-7781"
                  }
                },
                "Venezuela": {
                  "summary": "Venezuela with paymentMethod",
                  "value": {
                    "amount": 1500.0,
                    "currency": "VES",
                    "geo": 2,
                    "expiresIn": 900,
                    "merchantOrderId": "order-ve-12345",
                    "clientReference": "customer-7781",
                    "paymentMethod": "ven_bank"
                  }
                },
                "India": {
                  "summary": "India — UPI (paymentMethod required)",
                  "value": {
                    "amount": 5000,
                    "currency": "INR",
                    "geo": 3,
                    "expiresIn": 900,
                    "merchantOrderId": "order-in-12345",
                    "clientReference": "customer-7781",
                    "paymentMethod": "upi"
                  }
                },
                "IndiaUpiIntent": {
                  "summary": "India — UPI Intent (v2.6; same rail as upi)",
                  "value": {
                    "amount": 3049,
                    "currency": "INR",
                    "geo": 3,
                    "expiresIn": 900,
                    "merchantOrderId": "order-in-intent-12345",
                    "clientReference": "customer-7781",
                    "paymentMethod": "upi_intent"
                  }
                },
                "IndiaUpiAppLinks": {
                  "summary": "India — UPI Intent with PhonePe / Paytm deep links (v2.14)",
                  "value": {
                    "amount": 3049,
                    "currency": "INR",
                    "geo": 3,
                    "expiresIn": 900,
                    "merchantOrderId": "order-in-intent-12345",
                    "clientReference": "customer-7781",
                    "paymentMethod": "upi_intent",
                    "includeUpiAppLinks": true
                  }
                },
                "SouthKorea": {
                  "summary": "South Korea — kor_bank (paymentMethod required)",
                  "value": {
                    "amount": 50000,
                    "currency": "KRW",
                    "geo": 4,
                    "expiresIn": 900,
                    "merchantOrderId": "order-kr-12345",
                    "clientReference": "customer-7781",
                    "paymentMethod": "kor_bank"
                  }
                },
                "IndiaHostedCheckout": {
                  "summary": "India — UPI with hosted checkout (v2.5)",
                  "value": {
                    "amount": 3049,
                    "currency": "INR",
                    "geo": 3,
                    "expiresIn": 900,
                    "merchantOrderId": "order-in-checkout-12345",
                    "clientReference": "customer-7781",
                    "paymentMethod": "upi",
                    "checkout": {
                      "returnUrl": "https://shop.example/pay/done?o={PAY_IN_ID}",
                      "cancelUrl": "https://shop.example/cart",
                      "locale": "hi"
                    }
                  }
                }
              }
            }
          }
        },
        "responses": {
          "201": {
            "description": "PayIn created.",
            "content": {
              "application/json": {
                "schema": {
                  "$ref": "#/components/schemas/CreatePayInResponse"
                },
                "examples": {
                  "Argentina": {
                    "value": {
                      "payInId": "1c75c312-93f3-4b34-903e-10fb38a56bec",
                      "displayId": "PI00000007",
                      "geo": "Argentina",
                      "ownerName": "Francisco Ezequies",
                      "bankName": "Prex",
                      "cbu": "0000177501057000189625",
                      "amount": 5000.0,
                      "currency": "ARS",
                      "expiresAt": "2026-04-14T13:28:36.837Z",
                      "requiresManualConfirmation": false,
                      "qrImageBase64": "data:image/png;base64,iVBORw0KGgo...",
                      "exchangeRate": 1000,
                      "commissionRate": 5,
                      "amountUsdt": 4.75
                    }
                  },
                  "Venezuela": {
                    "value": {
                      "payInId": "8b2d1f4e-7c9a-4123-a5d6-9e8f0b1c2d3e",
                      "displayId": "PI00000023",
                      "geo": "Venezuela",
                      "ownerName": "Carlos Rodríguez",
                      "bankName": "Banesco",
                      "recipientPhone": "+584121234567",
                      "recipientCedula": "V-12345678",
                      "amount": 1500.0,
                      "currency": "VES",
                      "expiresAt": "2026-05-04T15:00:00.000Z",
                      "requiresManualConfirmation": true,
                      "exchangeRate": 1000,
                      "commissionRate": 2,
                      "amountUsdt": 1.47
                    }
                  },
                  "India": {
                    "summary": "India — UPI (upiIntentLink is returned on the whole UPI rail since v2.6)",
                    "value": {
                      "payInId": "3f1a9c2b-5d7e-4f80-b1c2-d3e4f5a6b7c8",
                      "displayId": "PI00000031",
                      "geo": "India",
                      "ownerName": "Ravi Kumar",
                      "bankName": "HDFC Bank",
                      "upiVpa": "payer@axl",
                      "upiIntentLink": "upi://pay?pa=payer%40axl&pn=Ravi%20Kumar&am=4999.00&cu=INR",
                      "amount": 4999,
                      "requestedAmount": 5000,
                      "amountAdjustment": -1,
                      "currency": "INR",
                      "expiresAt": "2026-07-31T15:00:00.000Z",
                      "requiresManualConfirmation": true,
                      "qrImageBase64": "data:image/png;base64,iVBORw0KGgo...",
                      "qrPayload": "upi://pay?pa=payer@axl",
                      "exchangeRate": 105.5,
                      "commissionRate": 8.5,
                      "amountUsdt": 43.364929
                    }
                  },
                  "IndiaUpiIntent": {
                    "summary": "India — UPI Intent (v2.6); byte-identical to a plain upi response",
                    "value": {
                      "payInId": "6b7c8d9e-0f12-4a34-b5c6-d7e8f9a0b1c2",
                      "displayId": "PI00000991",
                      "geo": "India",
                      "ownerName": "Ravi Sharma",
                      "bankName": "Yes Bank",
                      "upiVpa": "ravi.sharma@ybl",
                      "upiIntentLink": "upi://pay?pa=ravi.sharma%40ybl&pn=Ravi%20Sharma&am=3049.00&cu=INR",
                      "amount": 3049,
                      "currency": "INR",
                      "expiresAt": "2026-09-09T09:15:00.000Z",
                      "requiresManualConfirmation": true,
                      "qrImageBase64": "data:image/png;base64,iVBORw0KGgo...",
                      "exchangeRate": 105.5,
                      "commissionRate": 8.5,
                      "amountUsdt": 26.443934
                    }
                  },
                  "IndiaUpiAppLinks": {
                    "summary": "India — UPI Intent with includeUpiAppLinks true (v2.14)",
                    "value": {
                      "payInId": "6b7c8d9e-0f12-4a34-b5c6-d7e8f9a0b1c2",
                      "displayId": "PI00000991",
                      "geo": "India",
                      "ownerName": "Ravi Sharma",
                      "bankName": "Yes Bank",
                      "upiVpa": "ravi.sharma@ybl",
                      "upiIntentLink": "upi://pay?pa=ravi.sharma%40ybl&pn=Ravi%20Sharma&am=3049.00&cu=INR",
                      "amount": 3049,
                      "requestedAmount": 3049,
                      "amountAdjustment": 0,
                      "currency": "INR",
                      "expiresAt": "2026-10-01T09:15:00.000Z",
                      "requiresManualConfirmation": true,
                      "qrImageBase64": "data:image/png;base64,iVBORw0KGgo...",
                      "upiAppLinks": {
                        "phonepe": "phonepe://native?data=eyJjb250YWN0Ijp7ImNic05hbWUiOiIiLCJuaWNrTmFtZSI6IiIsInZwYSI6InJhdmkuc2hhcm1hQHlibCIsInR5cGUiOiJWUEEifSwicDJwUGF5bWVudENoZWNrb3V0UGFyYW1zIjp7Im5vdGUiOiIiLCJpc0J5RGVmYXVsdEtub3duQ29udGFjdCI6dHJ1ZSwiZW5hYmxlU3BlZWNoVG9UZXh0IjpmYWxzZSwiYWxsb3dBbW91bnRFZGl0IjpmYWxzZSwic2hvd1FyQ29kZU9wdGlvbiI6ZmFsc2UsImRpc2FibGVWaWV3SGlzdG9yeSI6dHJ1ZSwic2hvdWxkU2hvd1Vuc2F2ZWRDb250YWN0QmFubmVyIjpmYWxzZSwiaXNSZWN1cnJpbmciOmZhbHNlLCJjaGVja291dFR5cGUiOiJERUZBVUxUIiwidHJhbnNhY3Rpb25Db250ZXh0IjoicDJwIiwiaW5pdGlhbEFtb3VudCI6MzA0OTAwLCJkaXNhYmxlTm90ZXNFZGl0Ijp0cnVlLCJzaG93S2V5Ym9hcmQiOmZhbHNlLCJjdXJyZW5jeSI6IklOUiIsInNob3VsZFNob3dNYXNrZWROdW1iZXIiOnRydWV9fQ%3D%3D&id=p2ppayment",
                        "paytm": "paytmmp://cash_wallet?pa=ravi.sharma%40ybl&am=3049.00&cu=INR&featuretype=money_transfer"
                      },
                      "exchangeRate": 105.5,
                      "commissionRate": 8.5,
                      "amountUsdt": 26.443934
                    }
                  },
                  "IndiaImps": {
                    "summary": "India — IMPS (no upiIntentLink; the key is omitted)",
                    "value": {
                      "payInId": "7c8d9e0f-1234-4b56-c7d8-e9f0a1b2c3d4",
                      "displayId": "PI00000992",
                      "geo": "India",
                      "ownerName": "Ravi Kumar",
                      "bankName": "HDFC Bank",
                      "accountNumber": "40145477128",
                      "ifscCode": "HDFC0001234",
                      "amount": 5000,
                      "currency": "INR",
                      "expiresAt": "2026-09-09T09:15:00.000Z",
                      "requiresManualConfirmation": true,
                      "exchangeRate": 105.5,
                      "commissionRate": 8.5,
                      "amountUsdt": 43.364929
                    }
                  },
                  "SouthKorea": {
                    "value": {
                      "payInId": "4a2b8d3c-6e8f-4a91-c2d3-e4f5a6b7c8d9",
                      "displayId": "PI00000032",
                      "geo": "SouthKorea",
                      "ownerName": "Kim Minseo",
                      "bankName": "KB Kookmin Bank",
                      "accountNumber": "110123456789",
                      "amount": 50000,
                      "currency": "KRW",
                      "expiresAt": "2026-08-07T15:00:00.000Z",
                      "requiresManualConfirmation": true,
                      "exchangeRate": 1390,
                      "commissionRate": 4,
                      "amountUsdt": 34.532374
                    }
                  },
                  "IndiaHostedCheckout": {
                    "value": {
                      "payInId": "5e6f7a8b-9c0d-41e2-b3f4-a5b6c7d8e9f0",
                      "displayId": "PI00000991",
                      "geo": "India",
                      "ownerName": "Ravi Sharma",
                      "bankName": "Yes Bank",
                      "upiVpa": "ravi.sharma@ybl",
                      "upiIntentLink": "upi://pay?pa=ravi.sharma%40ybl&pn=Ravi%20Sharma&am=3049.00&cu=INR",
                      "amount": 3049,
                      "currency": "INR",
                      "expiresAt": "2026-09-07T09:15:00.000Z",
                      "requiresManualConfirmation": true,
                      "qrImageBase64": "data:image/png;base64,iVBORw0KGgo...",
                      "qrPayload": "upi://pay?pa=ravi.sharma@ybl&pn=Ravi%20Sharma&am=3049.00&cu=INR",
                      "checkout": {
                        "url": "https://api.atlas-online.io/pay/AbC9fQ2...",
                        "expiresAt": "2026-09-07T09:15:00.000Z",
                        "locale": "hi",
                        "embedUrl": "https://api.atlas-online.io/embed/AbC9fQ2..."
                      },
                      "exchangeRate": 105.5,
                      "commissionRate": 8.5,
                      "amountUsdt": 26.443934
                    }
                  }
                }
              }
            }
          },
          "400": {
            "description": "Validation or business-rule failure.",
            "content": {
              "application/json": {
                "schema": {
                  "$ref": "#/components/schemas/Error"
                },
                "examples": {
                  "validationError": {
                    "value": {
                      "error": "VALIDATION_ERROR",
                      "message": "amount must be greater than zero"
                    }
                  },
                  "checkoutNotSupported": {
                    "summary": "checkout requested outside the India methods it supports (v2.6 scope)",
                    "value": {
                      "error": "CHECKOUT_NOT_SUPPORTED",
                      "message": "checkout is currently available for India (paymentMethod upi, upi_intent or imps) and South Korea (paymentMethod kor_bank or kakao_pay) only."
                    }
                  },
                  "checkoutNotSupportedRouting": {
                    "summary": "checkout requested on an order that would be routed to an external provider",
                    "value": {
                      "error": "CHECKOUT_NOT_SUPPORTED",
                      "message": "checkout is not available for this merchant's routing — the order would be served by an external provider. Retry without `checkout`."
                    }
                  },
                  "clientBlocked": {
                    "summary": "new pay-ins from this clientReference are blocked (v2.18); no pay-in, no checkout.url, do not retry",
                    "value": {
                      "error": "CLIENT_BLOCKED",
                      "message": "Pay-ins from this clientReference are blocked. Contact Atlas support."
                    }
                  }
                }
              }
            }
          },
          "401": {
            "$ref": "#/components/responses/Unauthorized"
          },
          "409": {
            "$ref": "#/components/responses/ConcurrentUpdate"
          }
        }
      }
    },
    "/api/merchant/pay-in/{payInId}/status": {
      "get": {
        "tags": [
          "PayIn"
        ],
        "operationId": "getPayInStatus",
        "summary": "Get PayIn status",
        "parameters": [
          {
            "in": "path",
            "name": "payInId",
            "required": true,
            "schema": {
              "type": "string",
              "format": "uuid"
            }
          }
        ],
        "responses": {
          "200": {
            "description": "Current PayIn status.",
            "content": {
              "application/json": {
                "schema": {
                  "$ref": "#/components/schemas/PayInStatusResponse"
                },
                "examples": {
                  "completed": {
                    "value": {
                      "payInId": "1c75c312-93f3-4b34-903e-10fb38a56bec",
                      "displayId": "PI00000007",
                      "status": "Completed",
                      "amount": 5000.0,
                      "requestedAmount": 5000.0,
                      "amountAdjustment": 0,
                      "originalAmount": 5000.0,
                      "currency": "ARS",
                      "createdAt": "2026-04-14T13:13:36.837Z",
                      "expiresAt": "2026-04-14T13:28:36.837Z",
                      "completedAt": "2026-04-14T13:13:50.651Z",
                      "expiredAt": null,
                      "payerName": "Juan Martinez",
                      "isManualConfirmation": false,
                      "geo": "Argentina",
                      "requiresManualConfirmation": false,
                      "utr": null,
                      "qrPayload": null,
                      "clientReference": "customer-7781",
                      "exchangeRate": 1000,
                      "commissionRate": 5,
                      "amountUsdt": 4.75
                    }
                  }
                }
              }
            }
          },
          "401": {
            "$ref": "#/components/responses/Unauthorized"
          },
          "404": {
            "$ref": "#/components/responses/NotFound"
          }
        }
      }
    },
    "/api/merchant/pay-in/status": {
      "get": {
        "tags": [
          "PayIn"
        ],
        "operationId": "getPayInStatusByMerchantOrderId",
        "summary": "Get PayIn status by merchantOrderId",
        "description": "v2.19. Finds a pay-in by the merchant's own `merchantOrderId` and returns\nexactly the body of `GET /api/merchant/pay-in/{payInId}/status`, `payInId` included.\n\nUse it when `POST /api/merchant/pay-in` timed out or the connection\ndropped, so you have no `payInId`. 200: the pay-in exists; use the\nreturned status and do not create it again. 404: it was not created;\nrepeat `POST /api/merchant/pay-in` with the SAME `merchantOrderId` and\nthe same body. Create is idempotent by `merchantOrderId`, so if the first\nrequest got through in the meantime the repeat returns the existing\npay-in (201) instead of creating a second one. If the repeat itself fails\n(timeout, 5xx, or a 400 you did not get before), look the order up again\nbefore the next attempt. A Failed or Expired pay-in is final: take the\npayment with a new `merchantOrderId`.\n\nThe lookup is scoped to the merchant of the API key: another merchant's\norder with the same `merchantOrderId` is not visible and gives 404. A\nfirst create refused with 400 (for example validation, NO_CARD_AVAILABLE,\nCLIENT_BLOCKED) creates no pay-in, so its `merchantOrderId` gives 404.\nIDEMPOTENCY_KEY_MISMATCH is the exception: the pay-in exists. `merchantOrderId` is unique per\nmerchant, so at most one pay-in matches.\n\nThe id is a query parameter, not a path segment, because ids can contain\n`/`, `.`, `?`, `#` and `%`: an encoded `/` (`%2F`) in a path segment is\nnot decoded by the server, and `.` / `..` segments are normalised away\nby HTTP clients and proxies. Authenticated with the standard merchant\nHMAC headers; the canonical string for this GET is the empty-body form\n`<X-Timestamp>.` (query parameters are NOT included in the signature).\n",
        "parameters": [
          {
            "in": "query",
            "name": "merchantOrderId",
            "required": true,
            "description": "The `merchantOrderId` sent on create. Matched exactly\n(case-sensitive, not trimmed) among the orders of the API key's\nmerchant. URL-encode it as a query component\n(`encodeURIComponent`, `urllib.parse.quote(v, safe='')`,\n`Uri.EscapeDataString`, `curl -G --data-urlencode`): `+` must be\nsent as `%2B` (a raw `+` is read as a space), `&` as `%26`, `#` as\n`%23`, `%` as `%25`.\n",
            "schema": {
              "type": "string",
              "minLength": 1,
              "maxLength": 200
            },
            "example": "order-12345"
          }
        ],
        "responses": {
          "200": {
            "description": "Current PayIn status, the same body as the by-id status endpoint.",
            "content": {
              "application/json": {
                "schema": {
                  "$ref": "#/components/schemas/PayInStatusResponse"
                },
                "examples": {
                  "completed": {
                    "value": {
                      "payInId": "1c75c312-93f3-4b34-903e-10fb38a56bec",
                      "displayId": "PI00000007",
                      "status": "Completed",
                      "amount": 5000.0,
                      "requestedAmount": 5000.0,
                      "amountAdjustment": 0,
                      "originalAmount": 5000.0,
                      "currency": "ARS",
                      "createdAt": "2026-04-14T13:13:36.837Z",
                      "expiresAt": "2026-04-14T13:28:36.837Z",
                      "completedAt": "2026-04-14T13:13:50.651Z",
                      "expiredAt": null,
                      "payerName": "Juan Martinez",
                      "isManualConfirmation": false,
                      "geo": "Argentina",
                      "requiresManualConfirmation": false,
                      "utr": null,
                      "qrPayload": null,
                      "clientReference": "customer-7781",
                      "exchangeRate": 1000,
                      "commissionRate": 5,
                      "amountUsdt": 4.75
                    }
                  }
                }
              }
            }
          },
          "400": {
            "description": "merchantOrderId is missing or blank.",
            "content": {
              "application/json": {
                "schema": {
                  "$ref": "#/components/schemas/Error"
                },
                "examples": {
                  "missingMerchantOrderId": {
                    "value": {
                      "error": "VALIDATION_ERROR",
                      "message": "merchantOrderId is required"
                    }
                  }
                }
              }
            }
          },
          "401": {
            "$ref": "#/components/responses/Unauthorized"
          },
          "404": {
            "description": "No order with this merchantOrderId on your merchant account (never created, first create refused with 400, or another merchant's).",
            "content": {
              "application/json": {
                "schema": {
                  "$ref": "#/components/schemas/Error"
                },
                "examples": {
                  "notFound": {
                    "value": {
                      "error": "NOT_FOUND",
                      "message": "PayIn not found"
                    }
                  }
                }
              }
            }
          }
        }
      }
    },
    "/api/merchant/pay-in/{payInId}/cancel": {
      "post": {
        "tags": [
          "PayIn"
        ],
        "operationId": "cancelPayIn",
        "summary": "Cancel a PayIn",
        "description": "v2.20. Cancels the merchant's own pay-in by `payInId`.\n\nUse it for a pay-in you no longer need: you sent the same order to\nseveral providers and kept whichever answered first, or you never showed\nthe payment details to the payer. Cancelling frees the order at once\ninstead of leaving it open until it expires.\n\n`WaitingPayment` or `Expired` ⇒ `Failed` for good: a late payment no\nlonger completes it. 200 returns the body of\n`GET /api/merchant/pay-in/{payInId}/status` with `status: Failed`. An\nalready `Failed` pay-in (for any reason) returns the same 200, so the\ncall is idempotent and safe to retry. No `pay_in.failed` webhook is sent\nfor a cancel; the 200 response is the confirmation. A cancelled pay-in is\nnot reopened by repeating the create; take the payment again with a new\n`merchantOrderId`.\n\nNo request body. Authenticated with the standard merchant HMAC headers;\nthe body is empty, so the canonical string is `<X-Timestamp>.`.\n",
        "parameters": [
          {
            "in": "path",
            "name": "payInId",
            "required": true,
            "schema": {
              "type": "string",
              "format": "uuid"
            }
          }
        ],
        "responses": {
          "200": {
            "description": "Cancelled (or already Failed). The same body as the status endpoint, status Failed.",
            "content": {
              "application/json": {
                "schema": {
                  "$ref": "#/components/schemas/PayInStatusResponse"
                },
                "examples": {
                  "cancelled": {
                    "value": {
                      "payInId": "5e7f1a2b-3c4d-4e5f-8a9b-0c1d2e3f4a5b",
                      "displayId": "PI00000041",
                      "status": "Failed",
                      "amount": 5000.0,
                      "requestedAmount": 5000.0,
                      "amountAdjustment": 0,
                      "originalAmount": 5000.0,
                      "currency": "ARS",
                      "createdAt": "2026-11-04T10:00:00.000Z",
                      "expiresAt": "2026-11-04T10:15:00.000Z",
                      "completedAt": null,
                      "expiredAt": null,
                      "payerName": null,
                      "isManualConfirmation": false,
                      "geo": "Argentina",
                      "requiresManualConfirmation": false,
                      "utr": null,
                      "qrPayload": null,
                      "clientReference": "customer-7781",
                      "exchangeRate": 1000,
                      "commissionRate": 5,
                      "amountUsdt": 4.75
                    }
                  }
                }
              }
            }
          },
          "401": {
            "$ref": "#/components/responses/Unauthorized"
          },
          "404": {
            "$ref": "#/components/responses/NotFound"
          },
          "409": {
            "description": "Not cancelled. PAYIN_COMPLETED: the pay-in is completed, credit it.\nPAYMENT_REPORTED: a payment was reported for the order (a payment\nreference on file, the payer's \"I have paid\" on the hosted page, or\nan open payment check or dispute); Atlas support settles it and the\norder goes on normally. Do not retry either. CANCEL_FAILED: Atlas\ncould not confirm the cancellation with the payment network right\nnow; the pay-in stays WaitingPayment (it can still complete or\nexpire); retry after a delay. CONCURRENT_UPDATE: the\nrequest collided with a concurrent change; retry after a short delay.\n",
            "content": {
              "application/json": {
                "schema": {
                  "$ref": "#/components/schemas/Error"
                },
                "examples": {
                  "completed": {
                    "value": {
                      "error": "PAYIN_COMPLETED",
                      "message": "PI00000041 is completed — a completed pay-in cannot be cancelled."
                    }
                  },
                  "paymentReported": {
                    "value": {
                      "error": "PAYMENT_REPORTED",
                      "message": "A payment was reported for PI00000041 — it is settled by Atlas support and cannot be cancelled."
                    }
                  },
                  "cancelFailed": {
                    "value": {
                      "error": "CANCEL_FAILED",
                      "message": "PI00000041 could not be cancelled right now — it stays open. Retry later."
                    }
                  },
                  "concurrentUpdate": {
                    "value": {
                      "error": "CONCURRENT_UPDATE",
                      "message": "A concurrent request changed the same data at the same moment and this request was not completed. Retry the request."
                    }
                  }
                }
              }
            }
          }
        }
      }
    },
    "/api/merchant/pay-in/cancel": {
      "post": {
        "tags": [
          "PayIn"
        ],
        "operationId": "cancelPayInByMerchantOrderId",
        "summary": "Cancel a PayIn by merchantOrderId",
        "description": "v2.20. The same cancel as `POST /api/merchant/pay-in/{payInId}/cancel`,\naddressed by the merchant's own `merchantOrderId`. The lookup is the one\nof `GET /api/merchant/pay-in/status?merchantOrderId=`: scoped to the API\nkey's merchant, exact match (case-sensitive, not trimmed), value\nURL-encoded as a query component. Query parameters are NOT included in\nthe signature.\n\nUse it for a pay-in you no longer need: you sent the same order to\nseveral providers and kept whichever answered first, or you never showed\nthe payment details to the payer. Cancelling frees the order at once\ninstead of leaving it open until it expires.\n\n`WaitingPayment` or `Expired` ⇒ `Failed` for good: a late payment no\nlonger completes it. 200 returns the body of\n`GET /api/merchant/pay-in/{payInId}/status` with `status: Failed`. An\nalready `Failed` pay-in (for any reason) returns the same 200, so the\ncall is idempotent and safe to retry. No `pay_in.failed` webhook is sent\nfor a cancel; the 200 response is the confirmation. A cancelled pay-in is\nnot reopened by repeating the create; take the payment again with a new\n`merchantOrderId`.\n\nNo request body. Authenticated with the standard merchant HMAC headers;\nthe body is empty, so the canonical string is `<X-Timestamp>.`.\n",
        "parameters": [
          {
            "in": "query",
            "name": "merchantOrderId",
            "required": true,
            "description": "The `merchantOrderId` sent on create. Matched exactly\n(case-sensitive, not trimmed) among the orders of the API key's\nmerchant. URL-encode it as a query component: `+` as `%2B`, `&` as\n`%26`, `#` as `%23`, `%` as `%25`.\n",
            "schema": {
              "type": "string",
              "minLength": 1,
              "maxLength": 200
            },
            "example": "order-12345"
          }
        ],
        "responses": {
          "200": {
            "description": "Cancelled (or already Failed). The same body as the status endpoint, status Failed.",
            "content": {
              "application/json": {
                "schema": {
                  "$ref": "#/components/schemas/PayInStatusResponse"
                }
              }
            }
          },
          "400": {
            "description": "merchantOrderId is missing or blank, or longer than 200 characters.",
            "content": {
              "application/json": {
                "schema": {
                  "$ref": "#/components/schemas/Error"
                },
                "examples": {
                  "missingMerchantOrderId": {
                    "value": {
                      "error": "VALIDATION_ERROR",
                      "message": "merchantOrderId is required"
                    }
                  },
                  "tooLong": {
                    "value": {
                      "error": "VALIDATION_ERROR",
                      "message": "merchantOrderId exceeds 200 character limit"
                    }
                  }
                }
              }
            }
          },
          "401": {
            "$ref": "#/components/responses/Unauthorized"
          },
          "404": {
            "description": "No pay-in with this merchantOrderId on your merchant account.",
            "content": {
              "application/json": {
                "schema": {
                  "$ref": "#/components/schemas/Error"
                },
                "examples": {
                  "notFound": {
                    "value": {
                      "error": "NOT_FOUND",
                      "message": "PayIn not found"
                    }
                  }
                }
              }
            }
          },
          "409": {
            "description": "Not cancelled. PAYIN_COMPLETED: the pay-in is completed, credit it.\nPAYMENT_REPORTED: a payment was reported for the order (a payment\nreference on file, the payer's \"I have paid\" on the hosted page, or\nan open payment check or dispute); Atlas support settles it and the\norder goes on normally. Do not retry either. CANCEL_FAILED: Atlas\ncould not confirm the cancellation with the payment network right\nnow; the pay-in stays WaitingPayment (it can still complete or\nexpire); retry after a delay. CONCURRENT_UPDATE: the\nrequest collided with a concurrent change; retry after a short delay.\n",
            "content": {
              "application/json": {
                "schema": {
                  "$ref": "#/components/schemas/Error"
                },
                "examples": {
                  "completed": {
                    "value": {
                      "error": "PAYIN_COMPLETED",
                      "message": "PI00000041 is completed — a completed pay-in cannot be cancelled."
                    }
                  },
                  "paymentReported": {
                    "value": {
                      "error": "PAYMENT_REPORTED",
                      "message": "A payment was reported for PI00000041 — it is settled by Atlas support and cannot be cancelled."
                    }
                  },
                  "cancelFailed": {
                    "value": {
                      "error": "CANCEL_FAILED",
                      "message": "PI00000041 could not be cancelled right now — it stays open. Retry later."
                    }
                  },
                  "concurrentUpdate": {
                    "value": {
                      "error": "CONCURRENT_UPDATE",
                      "message": "A concurrent request changed the same data at the same moment and this request was not completed. Retry the request."
                    }
                  }
                }
              }
            }
          }
        }
      }
    },
    "/api/merchant/pay-in/{payInId}/utr": {
      "post": {
        "tags": [
          "PayIn"
        ],
        "operationId": "submitPayInUtr",
        "summary": "Submit payer UTR",
        "description": "Pushes a Unique Transaction Reference supplied by the payer at checkout.\nRecords the UTR on the PayIn and returns the current status atomically.\nThe UTR is optional matching context: an India pay-in is confirmed when\nthe payment reaches the trader, with or without one, and this call never\nchanges the status. The UTR is stored only while the PayIn is\n`WaitingPayment` or `Expired`.\n",
        "parameters": [
          {
            "in": "path",
            "name": "payInId",
            "required": true,
            "schema": {
              "type": "string",
              "format": "uuid"
            }
          }
        ],
        "requestBody": {
          "required": true,
          "content": {
            "application/json": {
              "schema": {
                "$ref": "#/components/schemas/SubmitUtrRequest"
              }
            }
          }
        },
        "responses": {
          "200": {
            "description": "UTR recorded.",
            "content": {
              "application/json": {
                "schema": {
                  "$ref": "#/components/schemas/SubmitUtrResponse"
                }
              }
            }
          },
          "400": {
            "$ref": "#/components/responses/BadRequest"
          },
          "401": {
            "$ref": "#/components/responses/Unauthorized"
          },
          "409": {
            "$ref": "#/components/responses/ConcurrentUpdate"
          },
          "404": {
            "$ref": "#/components/responses/NotFound"
          }
        }
      }
    },
    "/api/merchant/pay-out": {
      "post": {
        "tags": [
          "PayOut"
        ],
        "operationId": "createPayOut",
        "summary": "Create PayOut",
        "description": "Submits an outbound payment from the merchant to a recipient.\n\n**Per-geo recipient object** — send ONLY the fields that belong to the geo.\nA field that does not belong to the geo is rejected with `400 VALIDATION_ERROR`.\n- Argentina: `recipientCbu` (required).\n- Venezuela: `recipientPhone` + `recipientCedula` + `recipientBank` + `recipientAccountNumber` (all required).\n- India (v2.2) — two rails, picked with `paymentMethod` (omitted ⇒ `imps`):\n  `imps` ⇒ `recipientAccountNumber` + `recipientBank` + `recipientIfsc` (all required);\n  `upi` (v2.6) ⇒ `recipientUpiVpa` (required, `handle@psp`) and none of the IMPS trio.\n  Whole INR amounts on both. On the UPI rail an Atlas trader pays the VPA from their\n  own UPI app and uploads the proof — the outcome arrives on the same webhooks, and\n  `recipientUpiVpa` is echoed on the response, the status and `pay_out.completed`.\n- SouthKorea (v2.3): `recipientAccountNumber` + `recipientBank` (both required). kor_bank only; whole KRW amounts.\n\nFunding: a covered pay-out reserves its amount in USDT plus the pay-out\ncommission from the pay-out wallet (`reservedBalance`); `amountUsdt` in\nthe response is the pay-out amount alone. A pay-out the pay-out wallet\ncannot cover is **ACCEPTED (201)**, never rejected with\n`INSUFFICIENT_BALANCE`: it is held in `AwaitingFunds` status for up to\n12 h, then released when funded or auto-expired (`pay_out.expired`). The\n201 body has no status and no webhook marks the hold, so read\n`GET /pay-out/{payOutId}/status`. Treat 201 as accepted, not settled —\nwait for the `pay_out.completed` or `pay_out.expired` webhook.\n\n`IDEMPOTENCY_KEY_MISMATCH` (400) applies to accounts with strict\nidempotency only: the `merchantOrderId` belongs to an earlier pay-out\nthat has not failed and the body differs. Otherwise the original\npay-out is returned with 201.\n",
        "requestBody": {
          "required": true,
          "content": {
            "application/json": {
              "schema": {
                "$ref": "#/components/schemas/CreatePayOutRequest"
              }
            }
          }
        },
        "responses": {
          "201": {
            "description": "PayOut created.",
            "content": {
              "application/json": {
                "schema": {
                  "$ref": "#/components/schemas/CreatePayOutResponse"
                },
                "examples": {
                  "created": {
                    "value": {
                      "payOutId": "660e8400-e29b-41d4-a716-446655440111",
                      "displayId": "PO00000001",
                      "amountFiat": 5000.0,
                      "amountUsdt": 4.85,
                      "currency": "ARS",
                      "expiresAt": "2026-04-14T16:00:00Z"
                    }
                  },
                  "createdIndiaUpi": {
                    "summary": "India — UPI rail (v2.6); recipientUpiVpa echoed back",
                    "value": {
                      "payOutId": "770e8400-e29b-41d4-a716-446655440222",
                      "displayId": "PO00000042",
                      "amountFiat": 10000,
                      "amountUsdt": 102.5,
                      "currency": "INR",
                      "expiresAt": "2026-09-09T16:00:00Z",
                      "recipientUpiVpa": "ravi.kumar@ybl"
                    }
                  }
                }
              }
            }
          },
          "400": {
            "$ref": "#/components/responses/BadRequest"
          },
          "401": {
            "$ref": "#/components/responses/Unauthorized"
          },
          "409": {
            "$ref": "#/components/responses/ConcurrentUpdate"
          }
        }
      }
    },
    "/api/merchant/pay-out/{payOutId}/status": {
      "get": {
        "tags": [
          "PayOut"
        ],
        "operationId": "getPayOutStatus",
        "summary": "Get PayOut status",
        "parameters": [
          {
            "in": "path",
            "name": "payOutId",
            "required": true,
            "schema": {
              "type": "string",
              "format": "uuid"
            }
          }
        ],
        "responses": {
          "200": {
            "description": "Current PayOut status.",
            "content": {
              "application/json": {
                "schema": {
                  "$ref": "#/components/schemas/PayOutStatusResponse"
                }
              }
            }
          },
          "401": {
            "$ref": "#/components/responses/Unauthorized"
          },
          "404": {
            "$ref": "#/components/responses/NotFound"
          }
        }
      }
    },
    "/api/merchant/pay-out/status": {
      "get": {
        "tags": [
          "PayOut"
        ],
        "operationId": "getPayOutStatusByMerchantOrderId",
        "summary": "Get PayOut status by merchantOrderId",
        "description": "v2.19. Finds a pay-out by the merchant's own `merchantOrderId` and returns\nexactly the body of `GET /api/merchant/pay-out/{payOutId}/status` (that body has no `payOutId`; `displayId` identifies the pay-out).\n\nUse it when `POST /api/merchant/pay-out` timed out or the connection\ndropped, so you have no `payOutId`. 200: the pay-out exists; use the\nreturned status and do not create it again. 404: it was not created;\nrepeat `POST /api/merchant/pay-out` with the SAME `merchantOrderId` and\nthe same body. Create is idempotent by `merchantOrderId`, so if the first\nrequest got through in the meantime the repeat returns the existing\npay-out (201) instead of creating a second one. If the repeat itself fails\n(timeout, 5xx, or a 400 you did not get before), look the order up again\nbefore the next attempt. A Failed or Expired pay-out is final: send the\nmoney with a new `merchantOrderId`.\n\nThe lookup is scoped to the merchant of the API key: another merchant's\norder with the same `merchantOrderId` is not visible and gives 404. A\nfirst create refused with 400 (for example validation,\nAMOUNT_ABOVE_MAXIMUM) creates no pay-out, so its `merchantOrderId` gives\n404. IDEMPOTENCY_KEY_MISMATCH is the exception: the pay-out exists. `merchantOrderId` is unique per\nmerchant, so at most one pay-out matches.\n\nThe id is a query parameter, not a path segment, because ids can contain\n`/`, `.`, `?`, `#` and `%`: an encoded `/` (`%2F`) in a path segment is\nnot decoded by the server, and `.` / `..` segments are normalised away\nby HTTP clients and proxies. Authenticated with the standard merchant\nHMAC headers; the canonical string for this GET is the empty-body form\n`<X-Timestamp>.` (query parameters are NOT included in the signature).\n",
        "parameters": [
          {
            "in": "query",
            "name": "merchantOrderId",
            "required": true,
            "description": "The `merchantOrderId` sent on create. Matched exactly\n(case-sensitive, not trimmed) among the orders of the API key's\nmerchant. URL-encode it as a query component\n(`encodeURIComponent`, `urllib.parse.quote(v, safe='')`,\n`Uri.EscapeDataString`, `curl -G --data-urlencode`): `+` must be\nsent as `%2B` (a raw `+` is read as a space), `&` as `%26`, `#` as\n`%23`, `%` as `%25`.\n",
            "schema": {
              "type": "string",
              "minLength": 1,
              "maxLength": 200
            },
            "example": "payout-67890"
          }
        ],
        "responses": {
          "200": {
            "description": "Current PayOut status, the same body as the by-id status endpoint.",
            "content": {
              "application/json": {
                "schema": {
                  "$ref": "#/components/schemas/PayOutStatusResponse"
                }
              }
            }
          },
          "400": {
            "description": "merchantOrderId is missing or blank.",
            "content": {
              "application/json": {
                "schema": {
                  "$ref": "#/components/schemas/Error"
                },
                "examples": {
                  "missingMerchantOrderId": {
                    "value": {
                      "error": "VALIDATION_ERROR",
                      "message": "merchantOrderId is required"
                    }
                  }
                }
              }
            }
          },
          "401": {
            "$ref": "#/components/responses/Unauthorized"
          },
          "404": {
            "description": "No order with this merchantOrderId on your merchant account (never created, first create refused with 400, or another merchant's).",
            "content": {
              "application/json": {
                "schema": {
                  "$ref": "#/components/schemas/Error"
                },
                "examples": {
                  "notFound": {
                    "value": {
                      "error": "NOT_FOUND",
                      "message": "PayOut not found"
                    }
                  }
                }
              }
            }
          }
        }
      }
    },
    "/api/merchant/banks": {
      "get": {
        "tags": [
          "Catalog"
        ],
        "operationId": "getMerchantBanks",
        "summary": "List banks for a geo",
        "description": "Returns the canonical bank directory for a given geo. Used by\nmerchant integrations that render a bank dropdown for PayOut creation.\nArgentina has no fixed list (free-text bank field) and India needs none\n(the IFSC code fully addresses the bank) — both return an empty array;\nVenezuela and SouthKorea return banks with stable `code` values.\n\nAuthenticated with the standard merchant HMAC headers\n(`X-Api-Key`, `X-Timestamp`, `X-Signature`). The canonical string for\nthis GET request is the standard empty-body form `<X-Timestamp>.`\n(query parameters are NOT included in the signature).\n",
        "parameters": [
          {
            "in": "query",
            "name": "geo",
            "required": true,
            "schema": {
              "$ref": "#/components/schemas/Geo"
            }
          }
        ],
        "responses": {
          "200": {
            "description": "Bank/wallet directory for the geo.",
            "content": {
              "application/json": {
                "schema": {
                  "type": "array",
                  "items": {
                    "$ref": "#/components/schemas/VenezuelaBank"
                  }
                },
                "examples": {
                  "Venezuela": {
                    "value": [
                      {
                        "code": "0102",
                        "name": "Banco de Venezuela",
                        "kind": "Bank"
                      },
                      {
                        "code": "0134",
                        "name": "Banesco",
                        "kind": "Bank"
                      }
                    ]
                  },
                  "SouthKorea": {
                    "value": [
                      {
                        "code": "kb_kookmin_bank",
                        "name": "KB Kookmin Bank",
                        "kind": "Bank"
                      },
                      {
                        "code": "shinhan_bank",
                        "name": "Shinhan Bank",
                        "kind": "Bank"
                      },
                      {
                        "code": "kakao_bank",
                        "name": "KakaoBank",
                        "kind": "Bank"
                      }
                    ]
                  },
                  "ArgentinaOrIndia": {
                    "value": []
                  }
                }
              }
            }
          },
          "400": {
            "$ref": "#/components/responses/BadRequest"
          },
          "401": {
            "$ref": "#/components/responses/Unauthorized"
          }
        }
      }
    },
    "/api/merchant/payment-methods": {
      "get": {
        "tags": [
          "Catalog"
        ],
        "operationId": "getPaymentMethods",
        "summary": "List available payment methods for a geo",
        "description": "Returns the payment methods available to this merchant for the requested\ngeo, in `{ code, name, type }` shape. Pass `code` as `paymentMethod` on\nPayIn create (optional for AR/VE, REQUIRED for the multi-rail geos India\nand SouthKorea). Since v2.2 `type` is per-method — India returns `upi`,\n`upi_intent` (v2.6) and `imps`, SouthKorea returns `kor_bank` and\n`kakao_pay` under one geo. `type` is the RAIL, so several methods can\nshare one: India's `upi` and `upi_intent` are both `type: upi`.\n\nAuthenticated with the standard merchant HMAC headers. The canonical\nstring for this GET is `<X-Timestamp>.` (empty body).\n",
        "parameters": [
          {
            "in": "query",
            "name": "geo",
            "required": true,
            "schema": {
              "$ref": "#/components/schemas/Geo"
            }
          }
        ],
        "responses": {
          "200": {
            "description": "Available payment methods for the geo.",
            "content": {
              "application/json": {
                "schema": {
                  "type": "array",
                  "items": {
                    "$ref": "#/components/schemas/PaymentMethod"
                  }
                },
                "examples": {
                  "Venezuela": {
                    "value": [
                      {
                        "name": "Banesco",
                        "type": "ven_bank"
                      },
                      {
                        "name": "Pago Móvil",
                        "type": "ven_bank"
                      },
                      {
                        "name": "Zinli",
                        "type": "ven_bank_account"
                      }
                    ]
                  },
                  "Argentina": {
                    "value": [
                      {
                        "name": "CVU transfer",
                        "type": "cvu"
                      }
                    ]
                  },
                  "India": {
                    "value": [
                      {
                        "code": "imps",
                        "name": "IMPS",
                        "type": "imps"
                      },
                      {
                        "code": "upi",
                        "name": "UPI",
                        "type": "upi"
                      },
                      {
                        "code": "upi_intent",
                        "name": "UPI Intent",
                        "type": "upi"
                      }
                    ]
                  },
                  "SouthKorea": {
                    "value": [
                      {
                        "code": "kakao_pay",
                        "name": "Kakao Pay",
                        "type": "kakao_pay"
                      },
                      {
                        "code": "kor_bank",
                        "name": "Bank Transfer",
                        "type": "kor_bank"
                      }
                    ]
                  }
                }
              }
            }
          },
          "400": {
            "$ref": "#/components/responses/BadRequest"
          },
          "401": {
            "$ref": "#/components/responses/Unauthorized"
          }
        }
      }
    },
    "/api/merchant/balance": {
      "get": {
        "tags": [
          "Finance"
        ],
        "operationId": "getMerchantBalance",
        "summary": "Get balance",
        "description": "USDT position across the three merchant wallets (W1 available / W2 pay-out /\nreserved) plus the withdrawal currently in flight. GET requests sign the\ncanonical string `<X-Timestamp>.` (empty body).\n",
        "responses": {
          "200": {
            "description": "Current merchant balance.",
            "content": {
              "application/json": {
                "schema": {
                  "$ref": "#/components/schemas/MerchantBalanceResponse"
                }
              }
            }
          },
          "401": {
            "$ref": "#/components/responses/Unauthorized"
          }
        }
      }
    },
    "/api/merchant/withdrawals": {
      "post": {
        "tags": [
          "Finance"
        ],
        "operationId": "createWithdrawal",
        "summary": "Request withdrawal",
        "description": "Withdraw USDT from the pay-in wallet (W1) to the merchant's configured\nwallet address. The amount is deducted from W1 immediately and a\nwithdrawal is created in status Pending. Withdrawals are reviewed and\npaid out by the platform's finance team: once the USDT transfer is made\nthe withdrawal becomes Completed and carries its on-chain txHash; a\ndeclined one becomes Failed (failureReason set) and the amount is\nrefunded to W1. The wallet address is fixed at request time.\nA withdrawal requested here is never sent automatically; only one\nrequested for you at the threshold may be (status Processing, v2.13).\nOnly one withdrawal may be in flight at a time — a second request is\nrejected with WITHDRAWAL_IN_FLIGHT, which also makes retries safe.\n",
        "requestBody": {
          "required": false,
          "content": {
            "application/json": {
              "schema": {
                "$ref": "#/components/schemas/CreateWithdrawalRequest"
              },
              "examples": {
                "partial": {
                  "value": {
                    "amount": 100.0
                  }
                },
                "whole": {
                  "value": {}
                }
              }
            }
          }
        },
        "responses": {
          "201": {
            "description": "Withdrawal created (Pending).",
            "content": {
              "application/json": {
                "schema": {
                  "$ref": "#/components/schemas/WithdrawalResponse"
                }
              }
            }
          },
          "400": {
            "description": "Validation or business-rule failure.",
            "content": {
              "application/json": {
                "schema": {
                  "$ref": "#/components/schemas/Error"
                },
                "examples": {
                  "inFlight": {
                    "value": {
                      "error": "WITHDRAWAL_IN_FLIGHT",
                      "message": "A withdrawal is already in flight (Pending or Processing). Wait for it to complete before requesting another."
                    }
                  },
                  "noWallet": {
                    "value": {
                      "error": "NO_WALLET_ADDRESS",
                      "message": "Merchant has no wallet address configured."
                    }
                  },
                  "insufficient": {
                    "value": {
                      "error": "INSUFFICIENT_BALANCE",
                      "message": "Insufficient merchant balance. Available: 50 USDT, required: 100 USDT, shortfall: 50 USDT."
                    }
                  },
                  "nonPositive": {
                    "value": {
                      "error": "VALIDATION_ERROR",
                      "message": "amount must be greater than zero."
                    }
                  }
                }
              }
            }
          },
          "401": {
            "$ref": "#/components/responses/Unauthorized"
          },
          "409": {
            "$ref": "#/components/responses/ConcurrentUpdate"
          }
        }
      },
      "get": {
        "tags": [
          "Finance"
        ],
        "operationId": "listWithdrawals",
        "summary": "List withdrawals",
        "parameters": [
          {
            "in": "query",
            "name": "page",
            "schema": {
              "type": "integer",
              "minimum": 1,
              "default": 1
            }
          },
          {
            "in": "query",
            "name": "pageSize",
            "schema": {
              "type": "integer",
              "minimum": 1,
              "maximum": 100,
              "default": 20
            }
          },
          {
            "in": "query",
            "name": "status",
            "schema": {
              "$ref": "#/components/schemas/WithdrawalStatus"
            }
          }
        ],
        "responses": {
          "200": {
            "description": "Withdrawals, newest first.",
            "content": {
              "application/json": {
                "schema": {
                  "$ref": "#/components/schemas/WithdrawalListResponse"
                }
              }
            }
          },
          "400": {
            "$ref": "#/components/responses/BadRequest"
          },
          "401": {
            "$ref": "#/components/responses/Unauthorized"
          }
        }
      }
    },
    "/api/merchant/withdrawals/{withdrawalId}": {
      "get": {
        "tags": [
          "Finance"
        ],
        "operationId": "getWithdrawal",
        "summary": "Get withdrawal",
        "parameters": [
          {
            "in": "path",
            "name": "withdrawalId",
            "required": true,
            "schema": {
              "type": "string",
              "format": "uuid"
            }
          }
        ],
        "responses": {
          "200": {
            "description": "Current withdrawal state.",
            "content": {
              "application/json": {
                "schema": {
                  "$ref": "#/components/schemas/WithdrawalResponse"
                }
              }
            }
          },
          "401": {
            "$ref": "#/components/responses/Unauthorized"
          },
          "404": {
            "$ref": "#/components/responses/NotFound"
          }
        }
      }
    },
    "/api/merchant/transfers": {
      "post": {
        "tags": [
          "Finance"
        ],
        "operationId": "createTransfer",
        "summary": "Transfer to the pay-out wallet",
        "description": "Moves USDT from the pay-in wallet (W1, availableBalance) to the pay-out\nwallet (W2, payOutBalance), the same move as the transfer in the merchant\ncabinet. One direction only (W1 → W2). The move is synchronous: the 201\nresponse is final and there is no webhook. Pay-outs held for funds\n(AwaitingFunds) that W2 now covers are released right after the transfer,\noldest first. `clientReference` is the idempotency key: repeat it with the\nsame amount to get the original transfer back (200, nothing moves again).\n",
        "requestBody": {
          "required": true,
          "content": {
            "application/json": {
              "schema": {
                "$ref": "#/components/schemas/CreateTransferRequest"
              },
              "examples": {
                "default": {
                  "value": {
                    "amount": 250.0,
                    "clientReference": "fund-2026-10-12-001"
                  }
                }
              }
            }
          }
        },
        "responses": {
          "201": {
            "description": "Transfer booked.",
            "content": {
              "application/json": {
                "schema": {
                  "$ref": "#/components/schemas/TransferResponse"
                }
              }
            }
          },
          "200": {
            "description": "Idempotent replay. The clientReference was already booked with this amount; the original transfer is returned and nothing moves.",
            "content": {
              "application/json": {
                "schema": {
                  "$ref": "#/components/schemas/TransferResponse"
                }
              }
            }
          },
          "400": {
            "description": "Validation or business-rule failure.",
            "content": {
              "application/json": {
                "schema": {
                  "$ref": "#/components/schemas/Error"
                },
                "examples": {
                  "insufficient": {
                    "value": {
                      "error": "INSUFFICIENT_BALANCE",
                      "message": "Insufficient merchant balance. Available: 120 USDT, required: 250 USDT, shortfall: 130 USDT."
                    }
                  },
                  "validation": {
                    "value": {
                      "error": "VALIDATION_ERROR",
                      "message": "clientReference is required."
                    }
                  }
                }
              }
            }
          },
          "409": {
            "description": "The clientReference was already used for a transfer of a different amount (IDEMPOTENCY_KEY_MISMATCH), or the request collided with a concurrent request on the same data and was not completed (CONCURRENT_UPDATE, retry it with the same body).",
            "content": {
              "application/json": {
                "schema": {
                  "$ref": "#/components/schemas/Error"
                },
                "examples": {
                  "mismatch": {
                    "value": {
                      "error": "IDEMPOTENCY_KEY_MISMATCH",
                      "message": "clientReference 'fund-2026-10-12-001' was already used for a transfer of 250 USDT. Use a fresh clientReference for a new transfer."
                    }
                  },
                  "concurrentUpdate": {
                    "value": {
                      "error": "CONCURRENT_UPDATE",
                      "message": "A concurrent request changed the same data at the same moment and this request was not completed. Retry the request."
                    }
                  }
                }
              }
            }
          },
          "401": {
            "$ref": "#/components/responses/Unauthorized"
          }
        }
      },
      "get": {
        "tags": [
          "Finance"
        ],
        "operationId": "listTransfers",
        "summary": "List transfers",
        "description": "Every W1 → W2 transfer, newest first, including the ones made in the merchant cabinet (source manual, clientReference null) and the automatic settlement top-ups (source autoSettle, clientReference null).",
        "parameters": [
          {
            "in": "query",
            "name": "page",
            "schema": {
              "type": "integer",
              "minimum": 1,
              "default": 1
            }
          },
          {
            "in": "query",
            "name": "pageSize",
            "schema": {
              "type": "integer",
              "minimum": 1,
              "maximum": 100,
              "default": 20
            }
          }
        ],
        "responses": {
          "200": {
            "description": "Transfers, newest first.",
            "content": {
              "application/json": {
                "schema": {
                  "$ref": "#/components/schemas/TransferListResponse"
                }
              }
            }
          },
          "401": {
            "$ref": "#/components/responses/Unauthorized"
          }
        }
      }
    },
    "/api/merchant/transfers/{transferId}": {
      "get": {
        "tags": [
          "Finance"
        ],
        "operationId": "getTransfer",
        "summary": "Get transfer",
        "parameters": [
          {
            "in": "path",
            "name": "transferId",
            "required": true,
            "schema": {
              "type": "string",
              "format": "uuid"
            }
          }
        ],
        "responses": {
          "200": {
            "description": "The transfer.",
            "content": {
              "application/json": {
                "schema": {
                  "$ref": "#/components/schemas/TransferResponse"
                }
              }
            }
          },
          "401": {
            "$ref": "#/components/responses/Unauthorized"
          },
          "404": {
            "$ref": "#/components/responses/NotFound"
          }
        }
      }
    },
    "/api/merchant/disputes": {
      "post": {
        "tags": [
          "Disputes"
        ],
        "operationId": "openDispute",
        "summary": "Open dispute",
        "description": "Contests a completed PayIn or PayOut. The platform holds the underlying\nfunds until the dispute is resolved. A 24-hour evidence deadline applies.\n`disputeAmount` may be the full or a partial amount of the transaction.\n",
        "requestBody": {
          "required": true,
          "content": {
            "application/json": {
              "schema": {
                "$ref": "#/components/schemas/CreateDisputeRequest"
              },
              "examples": {
                "default": {
                  "value": {
                    "transactionType": 0,
                    "transactionId": "1c75c312-93f3-4b34-903e-10fb38a56bec",
                    "reason": "Client claims payment was sent but balance not credited.",
                    "merchantOrderId": "order-12345",
                    "disputeAmount": 750.0
                  }
                }
              }
            }
          }
        },
        "responses": {
          "201": {
            "description": "Dispute opened.",
            "content": {
              "application/json": {
                "schema": {
                  "$ref": "#/components/schemas/CreateDisputeResponse"
                }
              }
            }
          },
          "400": {
            "$ref": "#/components/responses/BadRequest"
          },
          "401": {
            "$ref": "#/components/responses/Unauthorized"
          },
          "409": {
            "$ref": "#/components/responses/ConcurrentUpdate"
          }
        }
      }
    },
    "/api/merchant/disputes/{id}": {
      "get": {
        "tags": [
          "Disputes"
        ],
        "operationId": "getDispute",
        "summary": "Get dispute",
        "parameters": [
          {
            "in": "path",
            "name": "id",
            "required": true,
            "schema": {
              "type": "string",
              "format": "uuid"
            }
          }
        ],
        "responses": {
          "200": {
            "description": "Dispute envelope with the merchant-visible message thread.",
            "content": {
              "application/json": {
                "schema": {
                  "$ref": "#/components/schemas/DisputeDetail"
                }
              }
            }
          },
          "401": {
            "$ref": "#/components/responses/Unauthorized"
          },
          "404": {
            "$ref": "#/components/responses/NotFound"
          }
        }
      }
    },
    "/api/merchant/disputes/{id}/messages": {
      "post": {
        "tags": [
          "Disputes"
        ],
        "operationId": "postDisputeMessage",
        "summary": "Post message or evidence",
        "description": "Posts a text message and up to 10 attachment files to the dispute thread.\nOnly the first 10 files are stored; files after the tenth are dropped\nwithout an error. The whole request may be up to 50 MB.\n\nSigning: HMAC-SHA256 over the bytes of `\"{timestamp}.\"` followed by the\nencoded multipart body exactly as sent (boundaries and file bytes\nincluded), with no text decoding. Encode the form before signing so the\nboundary is fixed.\n\nA dispute that does not exist or belongs to another merchant gets\n`404 NOT_FOUND`, as on the other dispute endpoints; nothing is stored.\nA resolved dispute gets 400 `{ \"error\": \"Cannot add messages to a resolved dispute\" }`\n(a readable sentence, no `message`). Files are stored only after these\nchecks pass. A missing `body` field or a non-UUID `id` gets a 400\nvalidation problem document; a request above 50 MB gets 413.\n",
        "parameters": [
          {
            "in": "path",
            "name": "id",
            "required": true,
            "schema": {
              "type": "string",
              "format": "uuid"
            }
          }
        ],
        "requestBody": {
          "required": true,
          "content": {
            "multipart/form-data": {
              "schema": {
                "type": "object",
                "required": [
                  "body"
                ],
                "properties": {
                  "body": {
                    "type": "string",
                    "description": "Message text."
                  },
                  "files": {
                    "type": "array",
                    "description": "Up to 10 attachments per request, combined size up to 50 MB. Files after the tenth are dropped without an error.",
                    "maxItems": 10,
                    "items": {
                      "type": "string",
                      "format": "binary"
                    }
                  }
                }
              }
            }
          }
        },
        "responses": {
          "200": {
            "description": "Message accepted.",
            "content": {
              "application/json": {
                "schema": {
                  "type": "object",
                  "properties": {
                    "message": {
                      "type": "string",
                      "examples": [
                        "Message sent"
                      ]
                    }
                  }
                }
              }
            }
          },
          "400": {
            "description": "Message refused. The body is `{ \"error\": \"<text>\" }` without a code.",
            "content": {
              "application/json": {
                "schema": {
                  "type": "object",
                  "required": [
                    "error"
                  ],
                  "properties": {
                    "error": {
                      "type": "string",
                      "examples": [
                        "Cannot add messages to a resolved dispute"
                      ]
                    }
                  }
                }
              }
            }
          },
          "401": {
            "$ref": "#/components/responses/Unauthorized"
          },
          "404": {
            "$ref": "#/components/responses/NotFound"
          },
          "413": {
            "description": "Request larger than 50 MB. No JSON body."
          }
        }
      },
      "get": {
        "tags": [
          "Disputes"
        ],
        "operationId": "getDisputeMessages",
        "summary": "Get dispute messages",
        "parameters": [
          {
            "in": "path",
            "name": "id",
            "required": true,
            "schema": {
              "type": "string",
              "format": "uuid"
            }
          }
        ],
        "responses": {
          "200": {
            "description": "Merchant-visible thread.",
            "content": {
              "application/json": {
                "schema": {
                  "type": "object",
                  "properties": {
                    "messages": {
                      "type": "array",
                      "items": {
                        "$ref": "#/components/schemas/DisputeMessage"
                      }
                    }
                  }
                }
              }
            }
          },
          "401": {
            "$ref": "#/components/responses/Unauthorized"
          },
          "404": {
            "$ref": "#/components/responses/NotFound"
          }
        }
      }
    }
  },
  "webhooks": {
    "pay_in.completed": {
      "post": {
        "summary": "pay_in.completed",
        "description": "Triggered when a PayIn transitions to `Completed`.",
        "requestBody": {
          "content": {
            "application/json": {
              "schema": {
                "type": "object",
                "required": [
                  "event",
                  "payInId",
                  "displayId",
                  "merchantOrderId",
                  "amount",
                  "currency",
                  "status",
                  "completedAt"
                ],
                "properties": {
                  "event": {
                    "const": "pay_in.completed"
                  },
                  "payInId": {
                    "type": "string",
                    "format": "uuid"
                  },
                  "displayId": {
                    "type": "string"
                  },
                  "merchantOrderId": {
                    "type": "string"
                  },
                  "amount": {
                    "type": "number",
                    "format": "decimal",
                    "description": "What the payer transferred."
                  },
                  "requestedAmount": {
                    "type": "number",
                    "format": "decimal",
                    "description": "What you asked for, and are credited for (v2.11)."
                  },
                  "amountAdjustment": {
                    "type": "number",
                    "format": "decimal",
                    "description": "`amount` minus `requestedAmount`; on India UPI within ± the range configured for the merchant (may be fractional, can be negative); 0 when not moved (v2.11)."
                  },
                  "originalAmount": {
                    "type": "number",
                    "format": "decimal",
                    "description": "What you originally asked for; equals `requestedAmount` unless support closed the order on a lower amount the payer actually sent (then `amount`/`requestedAmount` are the received amount). Always present (v2.12)."
                  },
                  "currency": {
                    "$ref": "#/components/schemas/Currency"
                  },
                  "amountUsdt": {
                    "type": "number",
                    "format": "decimal"
                  },
                  "status": {
                    "const": "Completed"
                  },
                  "geo": {
                    "$ref": "#/components/schemas/Geo"
                  },
                  "payerName": {
                    "type": [
                      "string",
                      "null"
                    ]
                  },
                  "utr": {
                    "type": [
                      "string",
                      "null"
                    ]
                  },
                  "completedAt": {
                    "type": "string",
                    "format": "date-time"
                  },
                  "isManualConfirmation": {
                    "type": "boolean"
                  }
                }
              }
            }
          }
        },
        "responses": {
          "2XX": {
            "description": "Acknowledged."
          }
        }
      }
    },
    "pay_in.expired": {
      "post": {
        "summary": "pay_in.expired",
        "requestBody": {
          "content": {
            "application/json": {
              "schema": {
                "type": "object",
                "required": [
                  "event",
                  "payInId",
                  "displayId",
                  "merchantOrderId",
                  "status",
                  "expiredAt"
                ],
                "properties": {
                  "event": {
                    "const": "pay_in.expired"
                  },
                  "payInId": {
                    "type": "string",
                    "format": "uuid"
                  },
                  "displayId": {
                    "type": "string"
                  },
                  "merchantOrderId": {
                    "type": "string"
                  },
                  "amount": {
                    "type": "number",
                    "format": "decimal"
                  },
                  "requestedAmount": {
                    "type": "number",
                    "format": "decimal",
                    "description": "What you asked for (v2.11)."
                  },
                  "amountAdjustment": {
                    "type": "number",
                    "format": "decimal",
                    "description": "`amount` minus `requestedAmount`; on India UPI within ± the range configured for the merchant (may be fractional, can be negative); 0 when not moved (v2.11)."
                  },
                  "currency": {
                    "$ref": "#/components/schemas/Currency"
                  },
                  "status": {
                    "const": "Expired"
                  },
                  "expiredAt": {
                    "type": "string",
                    "format": "date-time"
                  }
                }
              }
            }
          }
        },
        "responses": {
          "2XX": {
            "description": "Acknowledged."
          }
        }
      }
    },
    "pay_in.failed": {
      "post": {
        "summary": "pay_in.failed",
        "description": "Triggered when a PayIn transitions to `Failed`.",
        "requestBody": {
          "content": {
            "application/json": {
              "schema": {
                "type": "object",
                "required": [
                  "event",
                  "payInId",
                  "displayId",
                  "merchantOrderId",
                  "status"
                ],
                "properties": {
                  "event": {
                    "const": "pay_in.failed"
                  },
                  "payInId": {
                    "type": "string",
                    "format": "uuid"
                  },
                  "displayId": {
                    "type": "string"
                  },
                  "merchantOrderId": {
                    "type": "string"
                  },
                  "amount": {
                    "type": "number",
                    "format": "decimal"
                  },
                  "requestedAmount": {
                    "type": "number",
                    "format": "decimal",
                    "description": "What you asked for (v2.11)."
                  },
                  "amountAdjustment": {
                    "type": "number",
                    "format": "decimal",
                    "description": "`amount` minus `requestedAmount`; on India UPI within ± the range configured for the merchant (may be fractional, can be negative); 0 when not moved (v2.11)."
                  },
                  "currency": {
                    "$ref": "#/components/schemas/Currency"
                  },
                  "status": {
                    "const": "Failed"
                  },
                  "reason": {
                    "type": [
                      "string",
                      "null"
                    ],
                    "description": "Human-readable failure reason."
                  }
                }
              }
            }
          }
        },
        "responses": {
          "2XX": {
            "description": "Acknowledged."
          }
        }
      }
    },
    "pay_out.completed": {
      "post": {
        "summary": "pay_out.completed",
        "requestBody": {
          "content": {
            "application/json": {
              "schema": {
                "type": "object",
                "properties": {
                  "event": {
                    "const": "pay_out.completed"
                  },
                  "payOutId": {
                    "type": "string",
                    "format": "uuid"
                  },
                  "displayId": {
                    "type": "string"
                  },
                  "merchantOrderId": {
                    "type": "string"
                  },
                  "amount": {
                    "type": "number",
                    "format": "decimal"
                  },
                  "currency": {
                    "$ref": "#/components/schemas/Currency"
                  },
                  "amountUsdt": {
                    "type": "number",
                    "format": "decimal"
                  },
                  "status": {
                    "const": "Completed"
                  },
                  "geo": {
                    "$ref": "#/components/schemas/Geo"
                  },
                  "recipientCbu": {
                    "type": [
                      "string",
                      "null"
                    ]
                  },
                  "recipientPhone": {
                    "type": [
                      "string",
                      "null"
                    ]
                  },
                  "recipientCedula": {
                    "type": [
                      "string",
                      "null"
                    ]
                  },
                  "recipientBank": {
                    "type": [
                      "string",
                      "null"
                    ]
                  },
                  "recipientAccountNumber": {
                    "type": [
                      "string",
                      "null"
                    ]
                  },
                  "recipientIfsc": {
                    "type": [
                      "string",
                      "null"
                    ],
                    "description": "India IFSC branch code (v2.2). Null on an India UPI pay-out — the key is still present."
                  },
                  "recipientUpiVpa": {
                    "type": "string",
                    "description": "India UPI beneficiary VPA (v2.6), lower-cased, emitted\nimmediately after `recipientIfsc`. Present ONLY on an India\nUPI pay-out — the key is OMITTED for every other geo/rail,\nso an Argentina / Venezuela / SouthKorea / India-IMPS\npayload is byte-identical to v2.5.\n",
                    "examples": [
                      "ravi.kumar@ybl"
                    ]
                  },
                  "recipientName": {
                    "type": "string"
                  },
                  "completedAt": {
                    "type": "string",
                    "format": "date-time"
                  }
                }
              }
            }
          }
        },
        "responses": {
          "2XX": {
            "description": "Acknowledged."
          }
        }
      }
    },
    "pay_out.failed": {
      "post": {
        "summary": "pay_out.failed",
        "requestBody": {
          "content": {
            "application/json": {
              "schema": {
                "type": "object",
                "properties": {
                  "event": {
                    "const": "pay_out.failed"
                  },
                  "payOutId": {
                    "type": "string",
                    "format": "uuid"
                  },
                  "displayId": {
                    "type": "string"
                  },
                  "merchantOrderId": {
                    "type": "string"
                  },
                  "amount": {
                    "type": "number",
                    "format": "decimal"
                  },
                  "currency": {
                    "$ref": "#/components/schemas/Currency"
                  },
                  "status": {
                    "const": "Failed"
                  },
                  "reason": {
                    "type": "string"
                  }
                }
              }
            }
          }
        },
        "responses": {
          "2XX": {
            "description": "Acknowledged."
          }
        }
      }
    },
    "pay_out.expired": {
      "post": {
        "summary": "pay_out.expired",
        "requestBody": {
          "content": {
            "application/json": {
              "schema": {
                "type": "object",
                "properties": {
                  "event": {
                    "const": "pay_out.expired"
                  },
                  "payOutId": {
                    "type": "string",
                    "format": "uuid"
                  },
                  "displayId": {
                    "type": "string"
                  },
                  "merchantOrderId": {
                    "type": "string"
                  },
                  "amount": {
                    "type": "number",
                    "format": "decimal"
                  },
                  "currency": {
                    "$ref": "#/components/schemas/Currency"
                  },
                  "status": {
                    "const": "Expired"
                  },
                  "expiredAt": {
                    "type": "string",
                    "format": "date-time"
                  }
                }
              }
            }
          }
        },
        "responses": {
          "2XX": {
            "description": "Acknowledged."
          }
        }
      }
    },
    "dispute.opened": {
      "post": {
        "summary": "dispute.opened",
        "requestBody": {
          "content": {
            "application/json": {
              "schema": {
                "type": "object",
                "properties": {
                  "event": {
                    "const": "dispute.opened"
                  },
                  "disputeId": {
                    "type": "string",
                    "format": "uuid"
                  },
                  "displayId": {
                    "type": "string"
                  },
                  "transactionType": {
                    "$ref": "#/components/schemas/DisputeType"
                  },
                  "transactionId": {
                    "type": "string",
                    "format": "uuid"
                  },
                  "merchantOrderId": {
                    "type": "string"
                  },
                  "amount": {
                    "type": "number",
                    "format": "decimal"
                  },
                  "currency": {
                    "$ref": "#/components/schemas/Currency"
                  },
                  "status": {
                    "const": "PendingEvidence"
                  },
                  "evidenceDeadline": {
                    "type": "string",
                    "format": "date-time"
                  }
                }
              }
            }
          }
        },
        "responses": {
          "2XX": {
            "description": "Acknowledged."
          }
        }
      }
    },
    "dispute.resolved": {
      "post": {
        "summary": "dispute.resolved",
        "requestBody": {
          "content": {
            "application/json": {
              "schema": {
                "type": "object",
                "properties": {
                  "event": {
                    "const": "dispute.resolved"
                  },
                  "disputeId": {
                    "type": "string",
                    "format": "uuid"
                  },
                  "displayId": {
                    "type": "string"
                  },
                  "merchantOrderId": {
                    "type": "string"
                  },
                  "amount": {
                    "type": "number",
                    "format": "decimal"
                  },
                  "currency": {
                    "$ref": "#/components/schemas/Currency"
                  },
                  "status": {
                    "type": "string",
                    "enum": [
                      "ResolvedForTrader",
                      "ResolvedForClient",
                      "EvidenceExpired"
                    ]
                  },
                  "resolution": {
                    "type": [
                      "string",
                      "null"
                    ]
                  },
                  "resolvedInFavorOf": {
                    "type": "string",
                    "enum": [
                      "trader",
                      "client"
                    ],
                    "description": "`client` is the merchant (sent for ResolvedForClient and EvidenceExpired)."
                  },
                  "resolvedAt": {
                    "type": "string",
                    "format": "date-time"
                  }
                }
              }
            }
          }
        },
        "responses": {
          "2XX": {
            "description": "Acknowledged."
          }
        }
      }
    }
  }
}
