{
  "openapi": "3.1.0",
  "info": {
    "title": "Coinland Pay API",
    "version": "1.0.0",
    "description": "Accept payments from Coinland customers on your own site. You create a\n**checkout session** server-side, send the customer to the hosted widget\n(or open it with the embed script), the customer signs in on Coinland's\norigin and confirms — and the amount moves from their Coinland balance to\nyour Coinland business account instantly, off-chain, with no network fee.\n\nMoney also moves the other way: a **payout** sends coin from your business\nwallet to a Coinland customer, and a **refund** returns part or all of a\npayment you took. Both settle synchronously and both need a key of the\npayout class.\n\n## Authentication\nEvery request carries a secret key:\n\n```\nAuthorization: Bearer clpay_live_<64 hex>      the CHECKOUT class\nAuthorization: Bearer clpay_payout_<64 hex>    the PAYOUT class\n```\n\nKeys are minted (and revoked) in your business console, one class at a\ntime. The key is shown ONCE at creation — store it like a password,\nserver-side only. Never ship it in a browser, app binary, or repository.\n\nThe two classes are separate credentials with separate reach, so a leaked\ncheckout key cannot move money out:\n\n* a CHECKOUT key reaches everything under `/sessions`, `/payments`,\n  `/receipts` and `/me`;\n* a PAYOUT key reaches `/payouts`, `/payments/{id}/refund`, and the reads\n  it needs to reconcile — `GET /payments`, `GET /payments/{id}`,\n  `GET /me`. Nothing else.\n\nPresenting the wrong class for a route is `PAY_WRONG_KEY_KIND` (403), not\nan authentication failure: the key is valid, it is the other one you want.\nYou may hold up to 5 enabled keys of EACH class.\n\n## Idempotency\n`reference_id` (your order id, or your payout id) is your idempotency key\non every write: retrying with the same `reference_id` and an identical\npayload returns the original record; the same `reference_id` with a\nDIFFERENT payload is refused with `PAY_DUPLICATE_REFERENCE` (409). Never\nmint a fresh id to retry the same operation.\n\nThis matters most on `POST /payouts`, where the record IS a completed\ntransfer: a timeout tells you nothing about whether money moved, and\nresending the identical request is the only safe way to find out.\n\n## Errors\nNon-2xx responses carry `{\"statusCode\": n, \"errors\": {\"error\": [\"CODE\"]}}`.\nCodes are stable machine tokens; render your own copy.\n\n## Webhooks (Coinland → you)\nPOSTs to your registered https URL, signed with your webhook secret:\n\n```\nx-pay-signature: t=<unix seconds>,v1=<hex HMAC-SHA256(secret, \"{t}.{rawBody}\")>\n```\n\nRefuse timestamps older than 5 minutes; compare signatures in constant\ntime; deduplicate by `event_id`. Payloads are HINTS — ids and status,\nnever amounts to act on. Always fetch `GET /payments/{id}` for\nauthoritative state before fulfilling an order. Delivery retries back off\nover ~24 hours.\n\nEvent types: `payment.completed`, `session.expired`,\n`payout.completed`.\n\n## Receipt tokens\nEvery completed payment carries an unforgeable receipt token:\n\n```\nv1.<base64url(payload JSON)>.<base64url(HMAC-SHA256(webhook_secret, \"v1.\" + base64url(payload)))>\n```\n\nPayload fields, in the canonical order the signature is computed over:\n`payment_id`, `receipt_no`, `merchant_id`, `reference_id`, `currency`,\n`amount`, `charged_amount`, `fee_bearer`, `net_amount`, `paid_at`. Verify\noffline with your webhook secret, or call `POST /receipts/verify`. A\ncustomer cannot fabricate this token without your secret — but treat the\nWEBHOOK + API as the authority for fulfilment, the token as portable\nproof.\n",
    "contact": {
      "name": "Coinland"
    }
  },
  "servers": [
    {
      "url": "https://mycoinland.rateme.page",
      "description": "Production"
    }
  ],
  "security": [
    {
      "bearerKey": []
    }
  ],
  "tags": [
    {
      "name": "Sessions",
      "description": "Checkout sessions — the offer you create, the widget the customer pays."
    },
    {
      "name": "Payments",
      "description": "Completed payments and their receipts."
    },
    {
      "name": "Payouts",
      "description": "Money out — paying a customer, and refunding a payment."
    },
    {
      "name": "Account",
      "description": "Your merchant profile."
    }
  ],
  "paths": {
    "/api/pay/v1/sessions": {
      "post": {
        "operationId": "createSession",
        "tags": [
          "Sessions"
        ],
        "summary": "Create a checkout session",
        "description": "Create server-side, then send the customer to `checkout_url` (or pass\n`id` to the embed script).\n\nPrice the order in ONE of two ways — send `amounts` or `price_usd`,\nnever both and never neither (`PAY_AMOUNT_INVALID`, 422):\n\n* `amounts` — you name the figure in each coin you accept, and the\n  customer picks one. Coinland converts nothing: you price, the\n  customer pays exactly that.\n* `price_usd` — you name one USD figure and Coinland quotes it in\n  every coin you accept, at the live rate, at creation time. Those\n  quotes ARE the rate lock and the session's own `expires_at` is its\n  window; there is no separate rate-lock timer. A coin with no live\n  rate is omitted from the quotes; if nothing can be priced the\n  request is refused with `PAY_RATE_UNAVAILABLE` (503).\n",
        "requestBody": {
          "required": true,
          "content": {
            "application/json": {
              "schema": {
                "type": "object",
                "required": [
                  "reference_id",
                  "title"
                ],
                "properties": {
                  "reference_id": {
                    "type": "string",
                    "maxLength": 128,
                    "description": "Your order id — the idempotency key."
                  },
                  "title": {
                    "type": "string",
                    "maxLength": 128,
                    "description": "What the customer is paying for (widget headline)."
                  },
                  "description": {
                    "type": "string",
                    "maxLength": 512
                  },
                  "amounts": {
                    "type": "array",
                    "minItems": 1,
                    "maxItems": 10,
                    "description": "One entry per coin you price this order in. Mutually\nexclusive with `price_usd`.\n",
                    "items": {
                      "$ref": "#/components/schemas/Amount"
                    }
                  },
                  "price_usd": {
                    "type": "string",
                    "description": "One USD figure, as a positive decimal string with at most\n8 decimal places, quoted into every coin you accept at\ncreation. Mutually exclusive with `amounts`.\n"
                  },
                  "return_url": {
                    "type": "string",
                    "format": "uri",
                    "description": "https URL the customer is redirected to after paying, with\n`?receipt=<token>&payment_id=<id>` appended. Hint-only —\nfulfil on the webhook/API, not the redirect.\n"
                  },
                  "cancel_url": {
                    "type": "string",
                    "format": "uri",
                    "description": "https URL for \"back to store\" when the customer cancels."
                  },
                  "metadata": {
                    "type": "object",
                    "additionalProperties": true,
                    "description": "Free-form passthrough, echoed on reads and webhooks."
                  },
                  "ttl_minutes": {
                    "type": "integer",
                    "minimum": 5,
                    "maximum": 1440,
                    "description": "Overrides your configured session lifetime."
                  }
                }
              }
            }
          }
        },
        "responses": {
          "201": {
            "description": "The session",
            "content": {
              "application/json": {
                "schema": {
                  "$ref": "#/components/schemas/Session"
                }
              }
            }
          },
          "409": {
            "description": "`PAY_DUPLICATE_REFERENCE` — same reference_id, different payload",
            "content": {
              "application/json": {
                "schema": {
                  "$ref": "#/components/schemas/ErrorEnvelope"
                }
              }
            }
          },
          "422": {
            "description": "`PAY_CURRENCY_NOT_ACCEPTED` | `PAY_AMOUNT_INVALID` — the latter also\ncovers sending both `amounts` and `price_usd`, or neither.\n",
            "content": {
              "application/json": {
                "schema": {
                  "$ref": "#/components/schemas/ErrorEnvelope"
                }
              }
            }
          },
          "503": {
            "description": "`PAY_RATE_UNAVAILABLE` — `price_usd` was given but no accepted coin\ncould be priced. The request is well formed; retry it rather than\nchanging it.\n",
            "content": {
              "application/json": {
                "schema": {
                  "$ref": "#/components/schemas/ErrorEnvelope"
                }
              }
            }
          }
        }
      }
    },
    "/api/pay/v1/sessions/{id}": {
      "get": {
        "operationId": "getSession",
        "tags": [
          "Sessions"
        ],
        "summary": "Fetch a session",
        "description": "Authoritative state. When completed, the payment summary is embedded.",
        "parameters": [
          {
            "$ref": "#/components/parameters/SessionId"
          }
        ],
        "responses": {
          "200": {
            "description": "The session",
            "content": {
              "application/json": {
                "schema": {
                  "$ref": "#/components/schemas/Session"
                }
              }
            }
          },
          "404": {
            "description": "`PAY_SESSION_NOT_FOUND`",
            "content": {
              "application/json": {
                "schema": {
                  "$ref": "#/components/schemas/ErrorEnvelope"
                }
              }
            }
          }
        }
      }
    },
    "/api/pay/v1/sessions/{id}/cancel": {
      "post": {
        "operationId": "cancelSession",
        "tags": [
          "Sessions"
        ],
        "summary": "Cancel an open session",
        "description": "Idempotent — cancelling a cancelled session returns it unchanged. A\ncompleted session cannot be cancelled (`PAY_SESSION_STATE`, 409):\nmoney that has moved stays moved.\n",
        "parameters": [
          {
            "$ref": "#/components/parameters/SessionId"
          }
        ],
        "responses": {
          "200": {
            "description": "The cancelled session",
            "content": {
              "application/json": {
                "schema": {
                  "$ref": "#/components/schemas/Session"
                }
              }
            }
          },
          "409": {
            "description": "`PAY_SESSION_STATE` — already completed",
            "content": {
              "application/json": {
                "schema": {
                  "$ref": "#/components/schemas/ErrorEnvelope"
                }
              }
            }
          }
        }
      }
    },
    "/api/pay/v1/payments": {
      "get": {
        "operationId": "listPayments",
        "tags": [
          "Payments"
        ],
        "summary": "List payments",
        "description": "Newest first, cursor-paginated.",
        "parameters": [
          {
            "name": "limit",
            "in": "query",
            "schema": {
              "type": "integer",
              "minimum": 1,
              "maximum": 100,
              "default": 25
            }
          },
          {
            "name": "cursor",
            "in": "query",
            "description": "`next_cursor` from the previous page.",
            "schema": {
              "type": "string"
            }
          },
          {
            "name": "currency",
            "in": "query",
            "description": "Filter by coin slug (e.g. `usdt`).",
            "schema": {
              "type": "string"
            }
          },
          {
            "name": "from",
            "in": "query",
            "description": "ISO 8601 lower bound on `paid_at`.",
            "schema": {
              "type": "string",
              "format": "date-time"
            }
          },
          {
            "name": "to",
            "in": "query",
            "schema": {
              "type": "string",
              "format": "date-time"
            }
          }
        ],
        "responses": {
          "200": {
            "description": "A page of payments",
            "content": {
              "application/json": {
                "schema": {
                  "type": "object",
                  "required": [
                    "data"
                  ],
                  "properties": {
                    "data": {
                      "type": "array",
                      "items": {
                        "$ref": "#/components/schemas/Payment"
                      }
                    },
                    "next_cursor": {
                      "type": [
                        "string",
                        "null"
                      ]
                    }
                  }
                }
              }
            }
          }
        }
      }
    },
    "/api/pay/v1/payments/{id}": {
      "get": {
        "operationId": "getPayment",
        "tags": [
          "Payments"
        ],
        "summary": "Fetch a payment",
        "description": "The authoritative record — what webhooks tell you to come read.",
        "parameters": [
          {
            "name": "id",
            "in": "path",
            "required": true,
            "description": "Payment id (UUID) or receipt number (`CLP-…`).",
            "schema": {
              "type": "string"
            }
          }
        ],
        "responses": {
          "200": {
            "description": "The payment",
            "content": {
              "application/json": {
                "schema": {
                  "$ref": "#/components/schemas/Payment"
                }
              }
            }
          },
          "404": {
            "description": "`PAY_SESSION_NOT_FOUND`",
            "content": {
              "application/json": {
                "schema": {
                  "$ref": "#/components/schemas/ErrorEnvelope"
                }
              }
            }
          }
        }
      }
    },
    "/api/pay/v1/payouts": {
      "post": {
        "operationId": "createPayout",
        "tags": [
          "Payouts"
        ],
        "summary": "Pay a customer",
        "description": "Send coin from your business wallet to a Coinland customer. Requires a\nPAYOUT-class key.\n\n**This is synchronous. A `201` means the money has moved** — your\nbusiness wallet is debited `debited_amount` and the recipient's spot\nbalance is credited exactly `amount`, in one transaction, before the\nresponse is written. There is no pending state to poll.\n\nName the recipient in exactly ONE of two ways (sending both, or\nneither, is `PAY_RECIPIENT_INVALID`):\n\n* `payer_id` — the opaque handle carried on every payment that\n  customer has made to you. This is the ordinary path: you already\n  hold the handle, nothing has to be looked up, and no email is sent\n  over the wire.\n* `recipient_token` + `recipient_confirm` — the email path, for paying\n  someone who has never paid you. Call\n  `POST /payouts/recipients/lookup` first, show the `masked_name` it\n  returns to a human, and echo that string back VERBATIM as\n  `recipient_confirm`. Available only if Coinland has set your payout\n  scope to `any`.\n\nThe fee is yours: the recipient receives exactly `amount` and your\nwallet is debited `amount + fee_amount`, at the same tier rate your\npayments are charged at. Retrying with the same `reference_id` and an\nidentical payload returns the original payout with `201` — it does not\npay twice.\n",
        "requestBody": {
          "required": true,
          "content": {
            "application/json": {
              "schema": {
                "type": "object",
                "required": [
                  "reference_id",
                  "currency",
                  "amount"
                ],
                "properties": {
                  "reference_id": {
                    "type": "string",
                    "maxLength": 128,
                    "description": "Your id for this payout — the idempotency key."
                  },
                  "currency": {
                    "type": "string",
                    "description": "Coin slug (e.g. `usdt`). Any enabled crypto your business\nwallet holds — it does NOT have to be in your accepted\ncheckout set. Toman is refused: this rail is crypto only.\n"
                  },
                  "amount": {
                    "type": "string",
                    "description": "What the RECIPIENT receives, as a positive decimal string.\nThe fee is added on top of it, not taken out of it.\n"
                  },
                  "payer_id": {
                    "type": "string",
                    "format": "uuid",
                    "description": "The recipient's handle, from a payment's `payer_id`.\nMutually exclusive with `recipient_token`.\n"
                  },
                  "recipient_token": {
                    "type": "string",
                    "maxLength": 2048,
                    "description": "The token from `POST /payouts/recipients/lookup`. Valid 10\nminutes and only for the merchant that minted it.\n"
                  },
                  "recipient_confirm": {
                    "type": "string",
                    "maxLength": 128,
                    "description": "The `masked_name` from that same lookup, echoed back\ncharacter for character. Required with `recipient_token`.\n"
                  },
                  "comment": {
                    "type": "string",
                    "maxLength": 512,
                    "description": "A note for your own records and the recipient's notification."
                  }
                }
              },
              "examples": {
                "byHandle": {
                  "summary": "By payer handle (the ordinary path)",
                  "value": {
                    "reference_id": "payout-2291",
                    "currency": "usdt",
                    "amount": "25.00",
                    "payer_id": "7c1e5b90-3f42-4a86-9d05-2b8e4c1f6a37",
                    "comment": "Cashback for order 10492"
                  }
                },
                "byLookup": {
                  "summary": "By email lookup, after confirming the masked name",
                  "value": {
                    "reference_id": "payout-2292",
                    "currency": "usdt",
                    "amount": "40.50",
                    "recipient_token": "v1.eyJtZXJjaGFudElkIjo0Miwi….9f3c1d60ab72",
                    "recipient_confirm": "A**** B****"
                  }
                }
              }
            }
          }
        },
        "responses": {
          "201": {
            "description": "The completed payout",
            "content": {
              "application/json": {
                "schema": {
                  "$ref": "#/components/schemas/Payout"
                },
                "examples": {
                  "completed": {
                    "value": {
                      "id": "9e3c7a41-0b52-4f18-8d6a-3c7e1f9b40d5",
                      "reference_id": "payout-2291",
                      "kind": "payout",
                      "status": "completed",
                      "currency": "usdt",
                      "amount": "25.00",
                      "debited_amount": "25.125",
                      "fee_amount": "0.125",
                      "fee_percent": "0.5",
                      "usd_value": "25.00",
                      "payer_id": "7c1e5b90-3f42-4a86-9d05-2b8e4c1f6a37",
                      "payment_id": null,
                      "comment": "Cashback for order 10492",
                      "created_at": "2026-08-11T09:31:04.000Z",
                      "settled_at": "2026-08-11T09:31:04.000Z"
                    }
                  }
                }
              }
            }
          },
          "403": {
            "description": "`MERCHANT_PAYOUTS_DISABLED` — payouts are not armed for you (the\nrail is off, your business is suspended, payouts are not switched\non for your account, or your limits are unset). |\n`PAY_WRONG_KEY_KIND` — you presented a checkout key.\n",
            "content": {
              "application/json": {
                "schema": {
                  "$ref": "#/components/schemas/ErrorEnvelope"
                }
              }
            }
          },
          "409": {
            "description": "`PAY_DUPLICATE_REFERENCE` — same reference_id, different payload",
            "content": {
              "application/json": {
                "schema": {
                  "$ref": "#/components/schemas/ErrorEnvelope"
                }
              }
            }
          },
          "422": {
            "description": "`PAY_RECIPIENT_INVALID` — the recipient cannot be paid, or you sent\nboth recipient paths, or neither. One code covers every recipient\nfailure so the endpoint cannot be used to probe who has an account.\n| `PAY_PAYOUT_LIMIT` — over your per-payout maximum or your\ntrailing-24-hour ceiling (the error response carries only the\ncode; check the payout against both limits). |\n`PAY_AMOUNT_INVALID` | `PAY_CURRENCY_NOT_ACCEPTED` — the coin is\nunknown, fiat, or disabled platform-wide; payout currencies do NOT\nhave to be in your accepted checkout set. |\n`INSUFFICIENT_BALANCE` — your business wallet does not cover\n`amount + fee`.\n",
            "content": {
              "application/json": {
                "schema": {
                  "$ref": "#/components/schemas/ErrorEnvelope"
                }
              }
            }
          },
          "503": {
            "description": "`PAY_RATE_UNAVAILABLE` — the coin has no live USD rate, so the\nlimits cannot be enforced. A payout is refused rather than sent\nunmetered. Retry rather than change the request.\n",
            "content": {
              "application/json": {
                "schema": {
                  "$ref": "#/components/schemas/ErrorEnvelope"
                }
              }
            }
          }
        }
      },
      "get": {
        "operationId": "listPayouts",
        "tags": [
          "Payouts"
        ],
        "summary": "List payouts and refunds",
        "description": "Newest first, cursor-paginated. Requires a PAYOUT-class key.",
        "parameters": [
          {
            "name": "limit",
            "in": "query",
            "schema": {
              "type": "integer",
              "minimum": 1,
              "maximum": 100,
              "default": 25
            }
          },
          {
            "name": "cursor",
            "in": "query",
            "description": "`next_cursor` from the previous page.",
            "schema": {
              "type": "string"
            }
          },
          {
            "name": "kind",
            "in": "query",
            "description": "Return only payouts, or only refunds.",
            "schema": {
              "type": "string",
              "enum": [
                "payout",
                "refund"
              ]
            }
          },
          {
            "name": "from",
            "in": "query",
            "description": "ISO 8601 lower bound on `created_at`.",
            "schema": {
              "type": "string",
              "format": "date-time"
            }
          },
          {
            "name": "to",
            "in": "query",
            "description": "ISO 8601 upper bound on `created_at`.",
            "schema": {
              "type": "string",
              "format": "date-time"
            }
          }
        ],
        "responses": {
          "200": {
            "description": "A page of payouts",
            "content": {
              "application/json": {
                "schema": {
                  "type": "object",
                  "required": [
                    "data"
                  ],
                  "properties": {
                    "data": {
                      "type": "array",
                      "items": {
                        "$ref": "#/components/schemas/Payout"
                      }
                    },
                    "next_cursor": {
                      "type": [
                        "string",
                        "null"
                      ]
                    }
                  }
                }
              }
            }
          }
        }
      }
    },
    "/api/pay/v1/payouts/recipients/lookup": {
      "post": {
        "operationId": "lookupPayoutRecipient",
        "tags": [
          "Payouts"
        ],
        "summary": "Look up a payout recipient by email",
        "description": "Turn an email address into a short-lived, signed `recipient_token` you\ncan spend on `POST /payouts`. Requires a PAYOUT-class key, and is open\nonly to merchants whose payout scope Coinland has set to `any` —\notherwise it answers `MERCHANT_PAYOUTS_DISABLED` (403). An address\nthat cannot be paid — unknown, ineligible, or not fully verified —\nanswers `PAY_RECIPIENT_INVALID` (422), one code for every such case so\nthe endpoint cannot be used to probe who has an account.\n\nThe token is valid for 10 minutes, is bound to the merchant that\nminted it, and carries the resolved recipient inside its signature —\nthe email itself never rides the payout request.\n\n`masked_name` is a recognition aid, not an identification: show it to a\nhuman, have them confirm it is who they meant, and pass it back as\n`recipient_confirm`. A payout whose confirm does not match is refused.\n\nThis endpoint is deliberately not an address-existence oracle. An\nunknown address, a disabled account and an account that has not\ncompleted identity verification all answer the same\n`PAY_RECIPIENT_INVALID`, and lookups are metered per merchant.\n",
        "requestBody": {
          "required": true,
          "content": {
            "application/json": {
              "schema": {
                "type": "object",
                "required": [
                  "email"
                ],
                "properties": {
                  "email": {
                    "type": "string",
                    "format": "email",
                    "maxLength": 320
                  }
                }
              },
              "examples": {
                "lookup": {
                  "value": {
                    "email": "customer@example.com"
                  }
                }
              }
            }
          }
        },
        "responses": {
          "200": {
            "description": "A recipient token",
            "content": {
              "application/json": {
                "schema": {
                  "type": "object",
                  "required": [
                    "recipient_token",
                    "masked_name",
                    "expires_at"
                  ],
                  "properties": {
                    "recipient_token": {
                      "type": "string",
                      "description": "Spend this on `POST /payouts` within its window."
                    },
                    "masked_name": {
                      "type": "string",
                      "description": "The recipient's name, masked. Echo it back verbatim as\n`recipient_confirm`.\n"
                    },
                    "expires_at": {
                      "type": "string",
                      "format": "date-time"
                    }
                  }
                },
                "examples": {
                  "found": {
                    "value": {
                      "recipient_token": "v1.eyJtZXJjaGFudElkIjo0Miwi….9f3c1d60ab72",
                      "masked_name": "A**** B****",
                      "expires_at": "2026-08-11T09:41:22.000Z"
                    }
                  }
                }
              }
            }
          },
          "403": {
            "description": "`MERCHANT_PAYOUTS_DISABLED` — payouts are not armed for you, or\nyour payout scope does not include the email path. |\n`PAY_WRONG_KEY_KIND`\n",
            "content": {
              "application/json": {
                "schema": {
                  "$ref": "#/components/schemas/ErrorEnvelope"
                }
              }
            }
          },
          "422": {
            "description": "`PAY_RECIPIENT_INVALID` — no eligible recipient at that address.\nOne code for every reason, on purpose.\n",
            "content": {
              "application/json": {
                "schema": {
                  "$ref": "#/components/schemas/ErrorEnvelope"
                }
              }
            }
          },
          "429": {
            "description": "`PAY_LOOKUP_THROTTLED` — your lookup budget for the minute or the\nday is spent. Back off; the payout create itself is unaffected.\n",
            "content": {
              "application/json": {
                "schema": {
                  "$ref": "#/components/schemas/ErrorEnvelope"
                }
              }
            }
          }
        }
      }
    },
    "/api/pay/v1/payouts/{id}": {
      "get": {
        "operationId": "getPayout",
        "tags": [
          "Payouts"
        ],
        "summary": "Fetch a payout",
        "description": "The authoritative record — what `payout.completed` tells you to come\nread. Requires a PAYOUT-class key.\n",
        "parameters": [
          {
            "name": "id",
            "in": "path",
            "required": true,
            "description": "Payout id (UUID) or your `reference_id`.",
            "schema": {
              "type": "string"
            }
          }
        ],
        "responses": {
          "200": {
            "description": "The payout",
            "content": {
              "application/json": {
                "schema": {
                  "$ref": "#/components/schemas/Payout"
                }
              }
            }
          },
          "404": {
            "description": "`PAY_SESSION_NOT_FOUND`",
            "content": {
              "application/json": {
                "schema": {
                  "$ref": "#/components/schemas/ErrorEnvelope"
                }
              }
            }
          }
        }
      }
    },
    "/api/pay/v1/payments/{id}/refund": {
      "post": {
        "operationId": "refundPayment",
        "tags": [
          "Payouts"
        ],
        "summary": "Refund a payment",
        "description": "Return coin to the customer who paid you. Requires a PAYOUT-class key.\nLike a payout, it settles synchronously and answers with the same\nobject, under `kind: \"refund\"`.\n\nYou name only the amount. **The recipient and the currency come from\nthe payment row**, never from the request — a refund travels back the\nway the money came, and there is no field with which to send it\nsomewhere else.\n\nPartial refunds are allowed and can be repeated; what is capped is the\nCUMULATIVE total, at the payment's `charged_amount`. Going over is\n`PAY_REFUND_EXCEEDS_PAYMENT` (422).\n\n**Refunds are fee-exempt** — `fee_amount` is `\"0\"` and\n`debited_amount == amount`. Coinland keeps the fee charged on the\noriginal payment and takes nothing further, so refunding costs you the\ncoin and nothing else. The original fee is not returned.\n",
        "parameters": [
          {
            "name": "id",
            "in": "path",
            "required": true,
            "description": "Payment id (UUID) or receipt number (`CLP-…`).",
            "schema": {
              "type": "string"
            }
          }
        ],
        "requestBody": {
          "required": true,
          "content": {
            "application/json": {
              "schema": {
                "type": "object",
                "required": [
                  "reference_id",
                  "amount"
                ],
                "properties": {
                  "reference_id": {
                    "type": "string",
                    "maxLength": 128,
                    "description": "Your id for this refund — the idempotency key."
                  },
                  "amount": {
                    "type": "string",
                    "description": "How much to return, as a positive decimal string. May be\nless than the payment; cumulative refunds may not exceed\nits `charged_amount`.\n"
                  },
                  "comment": {
                    "type": "string",
                    "maxLength": 512
                  }
                }
              },
              "examples": {
                "partial": {
                  "value": {
                    "reference_id": "refund-10492-1",
                    "amount": "10.00",
                    "comment": "One item returned"
                  }
                }
              }
            }
          }
        },
        "responses": {
          "201": {
            "description": "The completed refund",
            "content": {
              "application/json": {
                "schema": {
                  "$ref": "#/components/schemas/Payout"
                }
              }
            }
          },
          "403": {
            "description": "`MERCHANT_PAYOUTS_DISABLED` | `PAY_WRONG_KEY_KIND`",
            "content": {
              "application/json": {
                "schema": {
                  "$ref": "#/components/schemas/ErrorEnvelope"
                }
              }
            }
          },
          "404": {
            "description": "`PAY_SESSION_NOT_FOUND` — no completed payment with that id under your account",
            "content": {
              "application/json": {
                "schema": {
                  "$ref": "#/components/schemas/ErrorEnvelope"
                }
              }
            }
          },
          "409": {
            "description": "`PAY_DUPLICATE_REFERENCE` — same reference_id, different payload",
            "content": {
              "application/json": {
                "schema": {
                  "$ref": "#/components/schemas/ErrorEnvelope"
                }
              }
            }
          },
          "422": {
            "description": "`PAY_REFUND_EXCEEDS_PAYMENT` — this refund would take the\ncumulative total past the payment's `charged_amount`. Track your\nown refunds of a payment against its `charged_amount`; the error\nresponse carries only the code. |\n`PAY_PAYOUT_LIMIT` — the trailing-24-hour ceiling (a refund is\nexempt from the per-payout maximum, never from the daily one). |\n`PAY_AMOUNT_INVALID` | `INSUFFICIENT_BALANCE`\n",
            "content": {
              "application/json": {
                "schema": {
                  "$ref": "#/components/schemas/ErrorEnvelope"
                }
              }
            }
          },
          "503": {
            "description": "`PAY_RATE_UNAVAILABLE` — no live USD rate, so the ceiling cannot be enforced",
            "content": {
              "application/json": {
                "schema": {
                  "$ref": "#/components/schemas/ErrorEnvelope"
                }
              }
            }
          }
        }
      }
    },
    "/api/pay/v1/receipts/verify": {
      "post": {
        "operationId": "verifyReceipt",
        "tags": [
          "Payments"
        ],
        "summary": "Verify a receipt token",
        "description": "Checks the token's signature against YOUR webhook secret and that the\npayment exists with matching facts. Prefer offline verification\n(same HMAC, your secret, no network hop) — this endpoint is for\nstacks that would rather not implement it.\n",
        "requestBody": {
          "required": true,
          "content": {
            "application/json": {
              "schema": {
                "type": "object",
                "required": [
                  "receipt"
                ],
                "properties": {
                  "receipt": {
                    "type": "string",
                    "description": "The `v1.….…` token."
                  }
                }
              }
            }
          }
        },
        "responses": {
          "200": {
            "description": "Valid — the payment it proves",
            "content": {
              "application/json": {
                "schema": {
                  "type": "object",
                  "required": [
                    "valid",
                    "payment"
                  ],
                  "properties": {
                    "valid": {
                      "type": "boolean",
                      "const": true
                    },
                    "payment": {
                      "$ref": "#/components/schemas/Payment"
                    }
                  }
                }
              }
            }
          },
          "422": {
            "description": "`PAY_RECEIPT_INVALID` — bad signature, altered fields, or unknown payment",
            "content": {
              "application/json": {
                "schema": {
                  "$ref": "#/components/schemas/ErrorEnvelope"
                }
              }
            }
          }
        }
      }
    },
    "/api/pay/v1/me": {
      "get": {
        "operationId": "getMe",
        "tags": [
          "Account"
        ],
        "summary": "Your merchant profile",
        "description": "Who this key belongs to and what it can take — the first call to make\nwhen wiring an integration.\n",
        "responses": {
          "200": {
            "description": "The profile",
            "content": {
              "application/json": {
                "schema": {
                  "type": "object",
                  "required": [
                    "id",
                    "display_name",
                    "status",
                    "accepted_currencies"
                  ],
                  "properties": {
                    "id": {
                      "type": "string",
                      "format": "uuid"
                    },
                    "display_name": {
                      "type": "string"
                    },
                    "display_name_fa": {
                      "type": "string"
                    },
                    "logo_url": {
                      "type": [
                        "string",
                        "null"
                      ]
                    },
                    "status": {
                      "type": "string",
                      "enum": [
                        "active",
                        "suspended"
                      ]
                    },
                    "accepted_currencies": {
                      "type": "array",
                      "items": {
                        "type": "string"
                      },
                      "description": "Coin slugs your checkout sessions may price in."
                    },
                    "webhook_url": {
                      "type": [
                        "string",
                        "null"
                      ]
                    }
                  }
                }
              }
            }
          }
        }
      }
    }
  },
  "components": {
    "securitySchemes": {
      "bearerKey": {
        "type": "http",
        "scheme": "bearer",
        "description": "`Authorization: Bearer clpay_live_<64 hex>` for the checkout routes,\n`Authorization: Bearer clpay_payout_<64 hex>` for the payout routes.\nThe wrong class on a route is `PAY_WRONG_KEY_KIND` (403).\n"
      }
    },
    "parameters": {
      "SessionId": {
        "name": "id",
        "in": "path",
        "required": true,
        "description": "Session id (UUID) or your `reference_id`.",
        "schema": {
          "type": "string"
        }
      }
    },
    "schemas": {
      "Amount": {
        "type": "object",
        "required": [
          "currency",
          "amount"
        ],
        "properties": {
          "currency": {
            "type": "string",
            "description": "Coin slug (e.g. `usdt`, `btc`) — must be in your accepted set."
          },
          "amount": {
            "type": "string",
            "description": "Decimal string in coin units (e.g. `\"10.5\"`). Strings, never floats."
          },
          "usd_value": {
            "type": [
              "string",
              "null"
            ],
            "readOnly": true,
            "description": "RESPONSE ONLY. The USD figure this coin amount was quoted from on a\n`price_usd` session; null on a per-coin session. Never send it.\n"
          }
        }
      },
      "Session": {
        "type": "object",
        "required": [
          "id",
          "reference_id",
          "status",
          "title",
          "amounts",
          "pricing_mode",
          "checkout_url",
          "expires_at",
          "created_at"
        ],
        "properties": {
          "id": {
            "type": "string",
            "format": "uuid"
          },
          "reference_id": {
            "type": "string"
          },
          "status": {
            "type": "string",
            "enum": [
              "open",
              "completed",
              "expired",
              "canceled"
            ]
          },
          "title": {
            "type": "string"
          },
          "description": {
            "type": [
              "string",
              "null"
            ]
          },
          "amounts": {
            "type": "array",
            "description": "On a `price_usd` session these are the QUOTES taken at creation, one\nper accepted coin that could be priced. They are the rate lock.\n",
            "items": {
              "$ref": "#/components/schemas/Amount"
            }
          },
          "pricing_mode": {
            "type": "string",
            "enum": [
              "usd",
              "per_coin"
            ],
            "description": "How this session was priced."
          },
          "price_usd": {
            "type": [
              "string",
              "null"
            ],
            "description": "The USD figure you sent; null on a per-coin session."
          },
          "checkout_url": {
            "type": "string",
            "format": "uri",
            "description": "Where to send the customer (or open via the embed script)."
          },
          "return_url": {
            "type": [
              "string",
              "null"
            ]
          },
          "cancel_url": {
            "type": [
              "string",
              "null"
            ]
          },
          "metadata": {
            "type": [
              "object",
              "null"
            ]
          },
          "payment": {
            "oneOf": [
              {
                "$ref": "#/components/schemas/Payment"
              },
              {
                "type": "null"
              }
            ],
            "description": "Present once `status` is `completed`."
          },
          "expires_at": {
            "type": "string",
            "format": "date-time"
          },
          "created_at": {
            "type": "string",
            "format": "date-time"
          }
        }
      },
      "Payment": {
        "type": "object",
        "required": [
          "id",
          "receipt_no",
          "session_id",
          "reference_id",
          "status",
          "currency",
          "amount",
          "charged_amount",
          "fee_amount",
          "fee_bearer",
          "net_amount",
          "receipt",
          "paid_at"
        ],
        "properties": {
          "id": {
            "type": "string",
            "format": "uuid"
          },
          "receipt_no": {
            "type": "string",
            "description": "Human-friendly receipt number (`CLP-…`) — also usable as a lookup id."
          },
          "session_id": {
            "type": "string",
            "format": "uuid"
          },
          "reference_id": {
            "type": "string"
          },
          "status": {
            "type": "string",
            "enum": [
              "completed"
            ],
            "description": "Only completed payments are visible on this API."
          },
          "currency": {
            "type": "string"
          },
          "amount": {
            "type": "string",
            "description": "The ORDER VALUE you priced (decimal string). NOT necessarily what\nthe payer was debited — under `fee_bearer: customer` that is\n`charged_amount`, which is larger.\n"
          },
          "charged_amount": {
            "type": "string",
            "description": "What the CUSTOMER was actually debited."
          },
          "fee_amount": {
            "type": "string",
            "description": "Coinland's fee on this payment."
          },
          "fee_bearer": {
            "type": "string",
            "enum": [
              "merchant",
              "customer"
            ],
            "description": "Who carries the fee. `merchant` — the fee comes out of your credit,\n`charged_amount == amount`. `customer` — the fee is added to the\npayer's total, `charged_amount == amount + fee_amount`.\n`charged_amount - net_amount == fee_amount` holds in both.\n"
          },
          "net_amount": {
            "type": "string",
            "description": "What your business account received."
          },
          "usd_value": {
            "type": [
              "string",
              "null"
            ],
            "description": "USD value at payment time; null when unavailable."
          },
          "payer_id": {
            "type": [
              "string",
              "null"
            ],
            "format": "uuid",
            "description": "The paying customer's opaque handle — stable, scoped to YOUR\nbusiness, and the same value every time that customer pays you.\nStore it: it is what you pass as `payer_id` to `POST /payouts` to\npay them back or send them a reward. It is NOT a Coinland user id\nand is meaningless to any other business.\n"
          },
          "metadata": {
            "type": [
              "object",
              "null"
            ]
          },
          "receipt": {
            "type": "string",
            "description": "The signed receipt token (`v1.….…`)."
          },
          "paid_at": {
            "type": "string",
            "format": "date-time"
          }
        }
      },
      "Payout": {
        "type": "object",
        "description": "Money OUT — one payout or one refund, already settled. The same object\nserves both, distinguished by `kind`.\n",
        "required": [
          "id",
          "reference_id",
          "kind",
          "status",
          "currency",
          "amount",
          "debited_amount",
          "fee_amount",
          "usd_value",
          "created_at"
        ],
        "properties": {
          "id": {
            "type": "string",
            "format": "uuid"
          },
          "reference_id": {
            "type": "string",
            "description": "Your id for this payout, and its idempotency key."
          },
          "kind": {
            "type": "string",
            "enum": [
              "payout",
              "refund"
            ],
            "description": "`payout` — you sent a customer money. `refund` — you returned part\nor all of a payment they made to you.\n"
          },
          "status": {
            "type": "string",
            "enum": [
              "completed"
            ],
            "description": "A payout row is written only once it has settled, so there is no\npending state to poll and no failure state to handle.\n"
          },
          "currency": {
            "type": "string"
          },
          "amount": {
            "type": "string",
            "description": "What the RECIPIENT received, exactly. The fee never comes out of\nthis figure.\n"
          },
          "debited_amount": {
            "type": "string",
            "description": "What your business wallet was debited: `amount + fee_amount`.\n`debited_amount - amount == fee_amount` holds exactly.\n"
          },
          "fee_amount": {
            "type": "string",
            "description": "Coinland's fee on this payout. Always `\"0\"` on a refund."
          },
          "fee_percent": {
            "type": [
              "string",
              "null"
            ],
            "description": "The tier rate this payout was charged at, snapshotted."
          },
          "usd_value": {
            "type": "string",
            "description": "USD value at settlement — the figure your payout limits are\nmeasured against. Never null: a payout that cannot be valued is\nrefused rather than sent unmetered.\n"
          },
          "payer_id": {
            "type": [
              "string",
              "null"
            ],
            "format": "uuid",
            "description": "The recipient's handle, reusable on a later payout."
          },
          "payment_id": {
            "type": [
              "string",
              "null"
            ],
            "format": "uuid",
            "description": "The refunded payment's id; null when `kind` is `payout`."
          },
          "comment": {
            "type": [
              "string",
              "null"
            ]
          },
          "created_at": {
            "type": "string",
            "format": "date-time"
          },
          "settled_at": {
            "type": [
              "string",
              "null"
            ],
            "format": "date-time"
          }
        }
      },
      "ErrorEnvelope": {
        "type": "object",
        "required": [
          "statusCode",
          "errors"
        ],
        "properties": {
          "statusCode": {
            "type": "integer"
          },
          "errors": {
            "type": "object",
            "required": [
              "error"
            ],
            "properties": {
              "error": {
                "type": "array",
                "items": {
                  "type": "string"
                },
                "description": "Stable machine codes (e.g. `PAY_SESSION_EXPIRED`)."
              }
            }
          }
        }
      }
    }
  }
}
