{
  "openapi": "3.1.0",
  "info": {
    "title": "Paysell Merchant API",
    "version": "1.0.0",
    "summary": "Accept TON and USDT payments on the TON network.",
    "description": "Accept TON and USDT (TON network) payments. You create an invoice, send the buyer\nto `payment_url`, and get a signed webhook once the money is confirmed on chain\nand credited to your balance.\n\n## In one minute\n\n1. Create a shop in your account area and issue an API key (`sk_live_…`).\n2. `POST /invoices` with `asset`, `amount` (a **string**, in the coin's normal\n   units) and your own `order_id`.\n3. Redirect the buyer to `payment_url` from the response.\n4. Wait for the `payment.credited` webhook. Verify its signature, then release\n   the goods **only** when `data.status` is `paid` or `overpaid`.\n\n## Rules that catch people out\n\n* **Amounts go out as decimal strings in normal units** (`\"1.5\"`), never as JSON\n  numbers and never in the smallest unit. Amounts inside webhooks go the other\n  way: integers in the smallest unit, as strings (`amount`, `fee`, `credited`,\n  `paid_minor`).\n* **The shop is taken from the API key.** No request carries a shop id.\n* **Idempotency is a body field**, `idempotency_key`, not an HTTP header. Without\n  it a retried request creates a second invoice.\n* **There is no sandbox and no refund endpoint.** Test with small real amounts;\n  refunds go through support.\n\n## More documentation\n\n* Human documentation: https://paysell.me/docs\n* Everything in one markdown file, for LLMs: https://paysell.me/llms-full.txt\n* Short index for LLMs: https://paysell.me/llms.txt\n",
    "contact": {
      "name": "Paysell",
      "url": "https://paysell.me/docs"
    },
    "license": {
      "name": "Paysell API and Developer Terms",
      "url": "https://paysell.me/legal/api-terms"
    },
    "termsOfService": "https://paysell.me/legal/terms-of-service"
  },
  "servers": [
    {
      "url": "https://paysell.me/api/merchant/v1",
      "description": "Production. There is no sandbox."
    }
  ],
  "externalDocs": {
    "description": "Full documentation",
    "url": "https://paysell.me/docs"
  },
  "tags": [
    {
      "name": "Invoices",
      "description": "Create, read and cancel invoices. Every call is authenticated with a shop API key."
    },
    {
      "name": "Checkout",
      "description": "The one endpoint with no authentication: it powers the hosted payment page a buyer sees. Safe to expose because an invoice id is a random UUID and the response carries nothing beyond what the buyer is about to pay."
    }
  ],
  "security": [
    {
      "bearerAuth": []
    }
  ],
  "paths": {
    "/invoices": {
      "post": {
        "tags": [
          "Invoices"
        ],
        "summary": "Create an invoice",
        "description": "Opens an invoice and returns the address and the link to send the buyer to.\n\nThe shop comes from the API key, so the body never carries a shop id. The invoice is born `pending`; every later change reaches you by webhook, or by reading the invoice back.\n\nSend `idempotency_key` and a retry of the same request returns the same invoice instead of opening a second one. Without it, a retry after a lost response creates a duplicate.",
        "operationId": "createInvoice",
        "requestBody": {
          "required": true,
          "content": {
            "application/json": {
              "schema": {
                "$ref": "#/components/schemas/InvoiceCreate"
              },
              "example": {
                "asset": "USDT_TON",
                "amount": "5",
                "order_id": "order-1042",
                "description": "Pro subscription",
                "ttl_minutes": 120,
                "idempotency_key": "order-1042-attempt-1"
              }
            }
          }
        },
        "responses": {
          "201": {
            "description": "The invoice, freshly opened.",
            "content": {
              "application/json": {
                "schema": {
                  "$ref": "#/components/schemas/InvoiceView"
                },
                "example": {
                  "invoice_id": "12c22c1a-a496-4c1e-abe3-72661ef8706e",
                  "payment_url": "https://paysell.me/pay/12c22c1a-a496-4c1e-abe3-72661ef8706e",
                  "address": "UQAvDJp7QDwqRcuNQBiK2GhBt71Xh1_UMYPCzMkQAoBPmZKl",
                  "asset": "USDT_TON",
                  "amount": "5",
                  "amount_minor": "5000000",
                  "status": "pending",
                  "paid": "0",
                  "paid_minor": "0",
                  "order_id": "order-1042",
                  "description": "Pro subscription",
                  "expires_at": "2026-09-06T17:20:55Z",
                  "created_at": "2026-09-06T15:20:55Z"
                }
              }
            }
          },
          "401": {
            "description": "The API key is missing, malformed, unknown or revoked. All four answer alike.",
            "content": {
              "application/json": {
                "schema": {
                  "$ref": "#/components/schemas/Error"
                },
                "examples": {
                  "error": {
                    "summary": "invalid_api_key",
                    "value": {
                      "detail": {
                        "code": "invalid_api_key",
                        "message": "Invalid or revoked API key."
                      }
                    }
                  }
                }
              }
            }
          },
          "422": {
            "description": "Either the body failed our own validation (then `detail` is a list of pydantic errors) or the amount is outside the invoice limits (then `detail` is `{code, message}` with `code: \"invalid_input\"`, and the message names both the value sent and the limit).",
            "content": {
              "application/json": {
                "schema": {
                  "oneOf": [
                    {
                      "$ref": "#/components/schemas/Error"
                    },
                    {
                      "$ref": "#/components/schemas/HTTPValidationError"
                    }
                  ]
                },
                "examples": {
                  "error": {
                    "summary": "invalid_input",
                    "value": {
                      "detail": {
                        "code": "invalid_input",
                        "message": "invoice amount below the minimum: 0.010000 USDT_TON, minimum 3.000000 USDT_TON"
                      }
                    }
                  },
                  "validation": {
                    "summary": "the body itself did not validate",
                    "value": {
                      "detail": [
                        {
                          "type": "string_type",
                          "loc": [
                            "body",
                            "amount"
                          ],
                          "msg": "Input should be a valid string",
                          "input": 5
                        }
                      ]
                    }
                  }
                }
              }
            }
          },
          "429": {
            "description": "A rate limit: 120 API requests per minute per key, 60 invoices per hour per shop, or too many invoices open at once. The response carries `Retry-After` in seconds.",
            "content": {
              "application/json": {
                "schema": {
                  "$ref": "#/components/schemas/Error"
                },
                "examples": {
                  "error": {
                    "summary": "too_many_requests",
                    "value": {
                      "detail": {
                        "code": "too_many_requests",
                        "message": "Too many requests. Slow down."
                      }
                    }
                  }
                }
              }
            },
            "headers": {
              "Retry-After": {
                "description": "Seconds to wait before retrying. Wait it out; a tight retry loop only pushes the window further.",
                "schema": {
                  "type": "integer"
                }
              }
            }
          },
          "502": {
            "description": "The processing core could not be reached. The invoice may or may not have been created — retry with the same `idempotency_key`.",
            "content": {
              "application/json": {
                "schema": {
                  "$ref": "#/components/schemas/Error"
                },
                "examples": {
                  "error": {
                    "summary": "cbc_unreachable",
                    "value": {
                      "detail": {
                        "code": "cbc_unreachable",
                        "message": "Payment processing is unavailable right now."
                      }
                    }
                  }
                }
              }
            }
          }
        }
      }
    },
    "/invoices/{invoice_id}": {
      "get": {
        "tags": [
          "Invoices"
        ],
        "summary": "Read an invoice",
        "description": "The current state of one invoice. Use it on a thank-you page, as a fallback when a webhook was missed, or in a reconciliation sweep.\n\nAn invoice belonging to another shop answers `404`, not `403`: the two cases are indistinguishable on purpose, so an id cannot be probed for existence.",
        "operationId": "getInvoice",
        "parameters": [
          {
            "name": "invoice_id",
            "in": "path",
            "required": true,
            "schema": {
              "type": "string",
              "title": "Invoice Id"
            }
          }
        ],
        "responses": {
          "200": {
            "description": "The invoice.",
            "content": {
              "application/json": {
                "schema": {
                  "$ref": "#/components/schemas/InvoiceView"
                },
                "example": {
                  "invoice_id": "12c22c1a-a496-4c1e-abe3-72661ef8706e",
                  "payment_url": "https://paysell.me/pay/12c22c1a-a496-4c1e-abe3-72661ef8706e",
                  "address": "UQAvDJp7QDwqRcuNQBiK2GhBt71Xh1_UMYPCzMkQAoBPmZKl",
                  "asset": "USDT_TON",
                  "amount": "5",
                  "amount_minor": "5000000",
                  "status": "pending",
                  "paid": "0",
                  "paid_minor": "0",
                  "order_id": "order-1042",
                  "description": "Pro subscription",
                  "expires_at": "2026-09-06T17:20:55Z",
                  "created_at": "2026-09-06T15:20:55Z"
                }
              }
            }
          },
          "401": {
            "description": "The API key is missing, malformed, unknown or revoked. All four answer alike.",
            "content": {
              "application/json": {
                "schema": {
                  "$ref": "#/components/schemas/Error"
                },
                "examples": {
                  "error": {
                    "summary": "invalid_api_key",
                    "value": {
                      "detail": {
                        "code": "invalid_api_key",
                        "message": "Invalid or revoked API key."
                      }
                    }
                  }
                }
              }
            }
          },
          "404": {
            "description": "No such invoice, or it belongs to another shop.",
            "content": {
              "application/json": {
                "schema": {
                  "$ref": "#/components/schemas/Error"
                },
                "examples": {
                  "error": {
                    "summary": "not_found",
                    "value": {
                      "detail": {
                        "code": "not_found",
                        "message": "invoice not found"
                      }
                    }
                  }
                }
              }
            }
          },
          "429": {
            "description": "A rate limit: 120 API requests per minute per key, 60 invoices per hour per shop, or too many invoices open at once. The response carries `Retry-After` in seconds.",
            "content": {
              "application/json": {
                "schema": {
                  "$ref": "#/components/schemas/Error"
                },
                "examples": {
                  "error": {
                    "summary": "too_many_requests",
                    "value": {
                      "detail": {
                        "code": "too_many_requests",
                        "message": "Too many requests. Slow down."
                      }
                    }
                  }
                }
              }
            },
            "headers": {
              "Retry-After": {
                "description": "Seconds to wait before retrying. Wait it out; a tight retry loop only pushes the window further.",
                "schema": {
                  "type": "integer"
                }
              }
            }
          },
          "502": {
            "description": "The processing core could not be reached. The invoice may or may not have been created — retry with the same `idempotency_key`.",
            "content": {
              "application/json": {
                "schema": {
                  "$ref": "#/components/schemas/Error"
                },
                "examples": {
                  "error": {
                    "summary": "cbc_unreachable",
                    "value": {
                      "detail": {
                        "code": "cbc_unreachable",
                        "message": "Payment processing is unavailable right now."
                      }
                    }
                  }
                }
              }
            }
          }
        }
      }
    },
    "/invoices/{invoice_id}/cancel": {
      "post": {
        "tags": [
          "Invoices"
        ],
        "summary": "Cancel an invoice",
        "description": "Closes an invoice that is still open (`pending` or `underpaid`) and releases its receiving address. Use it when a customer abandons checkout — addresses are a finite resource.\n\nMoney already received on an `underpaid` invoice stays on your balance: cancelling closes the invoice, it does not return coins. An invoice that is no longer open answers `409`.",
        "operationId": "cancelInvoice",
        "parameters": [
          {
            "name": "invoice_id",
            "in": "path",
            "required": true,
            "schema": {
              "type": "string",
              "title": "Invoice Id"
            }
          }
        ],
        "responses": {
          "200": {
            "description": "The invoice.",
            "content": {
              "application/json": {
                "schema": {
                  "$ref": "#/components/schemas/InvoiceView"
                },
                "example": {
                  "invoice_id": "12c22c1a-a496-4c1e-abe3-72661ef8706e",
                  "payment_url": "https://paysell.me/pay/12c22c1a-a496-4c1e-abe3-72661ef8706e",
                  "address": "UQAvDJp7QDwqRcuNQBiK2GhBt71Xh1_UMYPCzMkQAoBPmZKl",
                  "asset": "USDT_TON",
                  "amount": "5",
                  "amount_minor": "5000000",
                  "status": "pending",
                  "paid": "0",
                  "paid_minor": "0",
                  "order_id": "order-1042",
                  "description": "Pro subscription",
                  "expires_at": "2026-09-06T17:20:55Z",
                  "created_at": "2026-09-06T15:20:55Z"
                }
              }
            }
          },
          "401": {
            "description": "The API key is missing, malformed, unknown or revoked. All four answer alike.",
            "content": {
              "application/json": {
                "schema": {
                  "$ref": "#/components/schemas/Error"
                },
                "examples": {
                  "error": {
                    "summary": "invalid_api_key",
                    "value": {
                      "detail": {
                        "code": "invalid_api_key",
                        "message": "Invalid or revoked API key."
                      }
                    }
                  }
                }
              }
            }
          },
          "404": {
            "description": "No such invoice, or it belongs to another shop.",
            "content": {
              "application/json": {
                "schema": {
                  "$ref": "#/components/schemas/Error"
                },
                "examples": {
                  "error": {
                    "summary": "not_found",
                    "value": {
                      "detail": {
                        "code": "not_found",
                        "message": "invoice not found"
                      }
                    }
                  }
                }
              }
            }
          },
          "409": {
            "description": "The invoice is in a state that forbids this action, such as cancelling a paid invoice.",
            "content": {
              "application/json": {
                "schema": {
                  "$ref": "#/components/schemas/Error"
                },
                "examples": {
                  "error": {
                    "summary": "conflict",
                    "value": {
                      "detail": {
                        "code": "conflict",
                        "message": "invoice is closed: state \"paid\""
                      }
                    }
                  }
                }
              }
            }
          },
          "429": {
            "description": "A rate limit: 120 API requests per minute per key, 60 invoices per hour per shop, or too many invoices open at once. The response carries `Retry-After` in seconds.",
            "content": {
              "application/json": {
                "schema": {
                  "$ref": "#/components/schemas/Error"
                },
                "examples": {
                  "error": {
                    "summary": "too_many_requests",
                    "value": {
                      "detail": {
                        "code": "too_many_requests",
                        "message": "Too many requests. Slow down."
                      }
                    }
                  }
                }
              }
            },
            "headers": {
              "Retry-After": {
                "description": "Seconds to wait before retrying. Wait it out; a tight retry loop only pushes the window further.",
                "schema": {
                  "type": "integer"
                }
              }
            }
          },
          "502": {
            "description": "The processing core could not be reached. The invoice may or may not have been created — retry with the same `idempotency_key`.",
            "content": {
              "application/json": {
                "schema": {
                  "$ref": "#/components/schemas/Error"
                },
                "examples": {
                  "error": {
                    "summary": "cbc_unreachable",
                    "value": {
                      "detail": {
                        "code": "cbc_unreachable",
                        "message": "Payment processing is unavailable right now."
                      }
                    }
                  }
                }
              }
            }
          }
        }
      }
    },
    "/public/invoices/{invoice_id}": {
      "get": {
        "tags": [
          "Checkout"
        ],
        "summary": "Read an invoice as the buyer",
        "description": "What the hosted payment page at `/pay/{invoice_id}` reads. No authentication — the buyer has no account here.\n\nNarrower than the merchant view: no `order_id`, no `created_at`, plus the shop's own name and site so the page is recognisable. Rate limited per IP; poll it no more often than every few seconds.",
        "operationId": "getPublicInvoice",
        "parameters": [
          {
            "name": "invoice_id",
            "in": "path",
            "required": true,
            "schema": {
              "type": "string",
              "title": "Invoice Id"
            }
          }
        ],
        "responses": {
          "200": {
            "description": "The invoice.",
            "content": {
              "application/json": {
                "schema": {
                  "$ref": "#/components/schemas/PublicInvoiceView"
                },
                "example": {
                  "invoice_id": "12c22c1a-a496-4c1e-abe3-72661ef8706e",
                  "address": "UQAvDJp7QDwqRcuNQBiK2GhBt71Xh1_UMYPCzMkQAoBPmZKl",
                  "asset": "USDT_TON",
                  "amount": "5",
                  "amount_minor": "5000000",
                  "status": "pending",
                  "paid": "0",
                  "paid_minor": "0",
                  "description": "Pro subscription",
                  "expires_at": "2026-09-06T17:20:55Z",
                  "shop_name": "Flower shop",
                  "shop_url": "https://flowers.example"
                }
              }
            }
          },
          "404": {
            "description": "No such invoice, or it belongs to another shop.",
            "content": {
              "application/json": {
                "schema": {
                  "$ref": "#/components/schemas/Error"
                },
                "examples": {
                  "error": {
                    "summary": "not_found",
                    "value": {
                      "detail": {
                        "code": "not_found",
                        "message": "invoice not found"
                      }
                    }
                  }
                }
              }
            }
          },
          "429": {
            "description": "A rate limit: 120 API requests per minute per key, 60 invoices per hour per shop, or too many invoices open at once. The response carries `Retry-After` in seconds.",
            "content": {
              "application/json": {
                "schema": {
                  "$ref": "#/components/schemas/Error"
                },
                "examples": {
                  "error": {
                    "summary": "too_many_requests",
                    "value": {
                      "detail": {
                        "code": "too_many_requests",
                        "message": "Too many requests. Slow down."
                      }
                    }
                  }
                }
              }
            },
            "headers": {
              "Retry-After": {
                "description": "Seconds to wait before retrying. Wait it out; a tight retry loop only pushes the window further.",
                "schema": {
                  "type": "integer"
                }
              }
            }
          }
        },
        "security": []
      }
    }
  },
  "webhooks": {
    "payment.credited": {
      "post": {
        "operationId": "paymentCredited",
        "summary": "A payment was credited to your balance",
        "description": "The transfer is confirmed on chain, our fee is taken, and the rest is on your balance.\n\nRelease the goods only when `data.status` is `paid` or `overpaid`. On `underpaid` the invoice is still open and waiting for the rest at the same address; on `asset_mismatch` the money is yours but the invoice is not paid.\n\nWe POST this to the webhook URL saved on your API key.\n\n**Signature.** Every delivery is signed with the webhook secret shown once when the key was\ncreated: `HMAC-SHA256(secret, \"{timestamp}.{raw_body}\")`, hex, prefixed with `sha256=`, in\n`X-Paysell-Signature`. Verify it against the **raw body bytes** — parse and re-serialise the\nJSON and the bytes change — and compare in constant time. Reject deliveries whose\n`X-Paysell-Timestamp` is more than 300 seconds from your own clock: without that window a\ncaptured request stays valid forever.\n\n**Answer.** Any 2xx within 10 seconds, connection included. Answer first, do the slow work\nafterwards. Anything else — 4xx, 5xx, a redirect, a hang — counts as a failed attempt.\n\n**Retries.** 1 min → 5 min → 15 min → 1 h → 6 h → 24 h after the first attempt: seven attempts\nover roughly 31 hours, then the delivery is marked `dropped` and can be re-sent by hand from the\naccount area. The same event can therefore arrive twice; deduplicate on `event_id`.\n\n**Only two event types are forwarded**, and only when the shop has a webhook URL configured.\n",
        "parameters": [
          {
            "name": "X-Paysell-Event",
            "in": "header",
            "required": true,
            "schema": {
              "type": "string",
              "enum": [
                "payment.credited",
                "payment.rejected"
              ]
            },
            "description": "The event type. Same value as `type` in the body."
          },
          {
            "name": "X-Paysell-Event-Id",
            "in": "header",
            "required": true,
            "schema": {
              "type": "string",
              "format": "uuid"
            },
            "description": "Unique per event. This is the value to deduplicate on; the same event can arrive more than once."
          },
          {
            "name": "X-Paysell-Timestamp",
            "in": "header",
            "required": true,
            "schema": {
              "type": "string"
            },
            "description": "When we signed, in unix seconds. It is inside the signed string, so it cannot be edited without breaking the signature. Reject anything more than 300 seconds away from your own clock."
          },
          {
            "name": "X-Paysell-Signature",
            "in": "header",
            "required": true,
            "schema": {
              "type": "string",
              "pattern": "^sha256=[0-9a-f]{64}$"
            },
            "description": "`sha256=` followed by the hex HMAC-SHA256 of `\"{timestamp}.{raw_body}\"` under your webhook secret. Verify it over the raw bytes, in constant time, before acting."
          },
          {
            "name": "X-Paysell-Delivery",
            "in": "header",
            "required": false,
            "schema": {
              "type": "string",
              "format": "uuid"
            },
            "description": "The event id again, under its old name. Kept so existing logs keep working; new code reads `X-Paysell-Event-Id`."
          }
        ],
        "requestBody": {
          "required": true,
          "content": {
            "application/json": {
              "schema": {
                "$ref": "#/components/schemas/WebhookCreditedEvent"
              },
              "example": {
                "event_id": "99f74f58-efbb-4af1-b0a3-76b0073f9e6b",
                "type": "payment.credited",
                "data": {
                  "invoice_id": "12c22c1a-a496-4c1e-abe3-72661ef8706e",
                  "order_id": "order-1042",
                  "asset": "USDT_TON",
                  "amount": "5000000",
                  "credited": "4905000",
                  "fee": "95000",
                  "status": "paid",
                  "paid_minor": "5000000",
                  "tx_hash": "97a1f0c3b2e4d5a6b7c8d9e0f1a2b3c4d5e6f7a8b9c0d1e2f3a4b5c6d7e8f9a0"
                }
              }
            }
          }
        },
        "responses": {
          "2XX": {
            "description": "Anything 2xx means received. Nothing in your response body is read."
          },
          "default": {
            "description": "Any other status, a redirect or a hang counts as a failed attempt and is retried."
          }
        }
      }
    },
    "payment.rejected": {
      "post": {
        "operationId": "paymentRejected",
        "summary": "A deposit held for review was declined",
        "description": "A deposit that went to manual review was declined by an operator. The money will **not** reach your balance and the invoice is not paid. Do not release the goods; if the invoice was already paid by an earlier transfer, this event is about the extra deposit, not about that payment.\n\nWe POST this to the webhook URL saved on your API key.\n\n**Signature.** Every delivery is signed with the webhook secret shown once when the key was\ncreated: `HMAC-SHA256(secret, \"{timestamp}.{raw_body}\")`, hex, prefixed with `sha256=`, in\n`X-Paysell-Signature`. Verify it against the **raw body bytes** — parse and re-serialise the\nJSON and the bytes change — and compare in constant time. Reject deliveries whose\n`X-Paysell-Timestamp` is more than 300 seconds from your own clock: without that window a\ncaptured request stays valid forever.\n\n**Answer.** Any 2xx within 10 seconds, connection included. Answer first, do the slow work\nafterwards. Anything else — 4xx, 5xx, a redirect, a hang — counts as a failed attempt.\n\n**Retries.** 1 min → 5 min → 15 min → 1 h → 6 h → 24 h after the first attempt: seven attempts\nover roughly 31 hours, then the delivery is marked `dropped` and can be re-sent by hand from the\naccount area. The same event can therefore arrive twice; deduplicate on `event_id`.\n\n**Only two event types are forwarded**, and only when the shop has a webhook URL configured.\n",
        "parameters": [
          {
            "name": "X-Paysell-Event",
            "in": "header",
            "required": true,
            "schema": {
              "type": "string",
              "enum": [
                "payment.credited",
                "payment.rejected"
              ]
            },
            "description": "The event type. Same value as `type` in the body."
          },
          {
            "name": "X-Paysell-Event-Id",
            "in": "header",
            "required": true,
            "schema": {
              "type": "string",
              "format": "uuid"
            },
            "description": "Unique per event. This is the value to deduplicate on; the same event can arrive more than once."
          },
          {
            "name": "X-Paysell-Timestamp",
            "in": "header",
            "required": true,
            "schema": {
              "type": "string"
            },
            "description": "When we signed, in unix seconds. It is inside the signed string, so it cannot be edited without breaking the signature. Reject anything more than 300 seconds away from your own clock."
          },
          {
            "name": "X-Paysell-Signature",
            "in": "header",
            "required": true,
            "schema": {
              "type": "string",
              "pattern": "^sha256=[0-9a-f]{64}$"
            },
            "description": "`sha256=` followed by the hex HMAC-SHA256 of `\"{timestamp}.{raw_body}\"` under your webhook secret. Verify it over the raw bytes, in constant time, before acting."
          },
          {
            "name": "X-Paysell-Delivery",
            "in": "header",
            "required": false,
            "schema": {
              "type": "string",
              "format": "uuid"
            },
            "description": "The event id again, under its old name. Kept so existing logs keep working; new code reads `X-Paysell-Event-Id`."
          }
        ],
        "requestBody": {
          "required": true,
          "content": {
            "application/json": {
              "schema": {
                "$ref": "#/components/schemas/WebhookRejectedEvent"
              },
              "example": {
                "event_id": "0a1b2c3d-4e5f-4a6b-8c9d-0e1f2a3b4c5d",
                "type": "payment.rejected",
                "data": {
                  "invoice_id": "12c22c1a-a496-4c1e-abe3-72661ef8706e",
                  "order_id": "order-1042",
                  "asset": "USDT_TON",
                  "amount": "5000000",
                  "tx_hash": "97a1f0c3b2e4d5a6b7c8d9e0f1a2b3c4d5e6f7a8b9c0d1e2f3a4b5c6d7e8f9a0",
                  "reason": "could not be matched to any order"
                }
              }
            }
          }
        },
        "responses": {
          "2XX": {
            "description": "Anything 2xx means received."
          },
          "default": {
            "description": "Anything else counts as a failed attempt and is retried."
          }
        }
      }
    }
  },
  "components": {
    "securitySchemes": {
      "bearerAuth": {
        "type": "http",
        "scheme": "bearer",
        "description": "Your shop's API key, as `Authorization: Bearer sk_live_…`.\n\nIssued in the account area and shown once — we store only a one-way hash. The shop is derived from the key, which is why no request takes a shop id, and a key can only ever act on its own shop. Server-side only: anything in browser JavaScript is public."
      }
    },
    "schemas": {
      "Error": {
        "type": "object",
        "title": "Error",
        "description": "The shape of every error we or the processing core produce. Branch on `detail.code`, never on `detail.message`: the wording can change, the code cannot.",
        "properties": {
          "detail": {
            "type": "object",
            "required": [
              "code",
              "message"
            ],
            "properties": {
              "code": {
                "type": "string",
                "description": "Machine-readable reason.",
                "enum": [
                  "invalid_api_key",
                  "not_found",
                  "conflict",
                  "invalid_input",
                  "too_many_requests",
                  "cbc_unreachable",
                  "cbc_error"
                ]
              },
              "message": {
                "type": "string",
                "description": "Human-readable explanation. Do not parse it."
              },
              "details": {
                "type": [
                  "array",
                  "null"
                ],
                "items": {
                  "type": "string"
                },
                "description": "Field-level detail, when the core has any."
              }
            }
          }
        },
        "required": [
          "detail"
        ]
      },
      "HTTPValidationError": {
        "properties": {
          "detail": {
            "items": {
              "$ref": "#/components/schemas/ValidationError"
            },
            "type": "array",
            "title": "Detail"
          }
        },
        "type": "object",
        "title": "HTTPValidationError"
      },
      "InvoiceCreate": {
        "properties": {
          "asset": {
            "type": "string",
            "pattern": "^(TON|USDT_TON)$",
            "title": "Asset",
            "description": "The coin to be paid in. `USDT_TON` is Tether on the TON network, `TON` is the native coin."
          },
          "amount": {
            "type": "string",
            "pattern": "^[0-9]+(\\.[0-9]+)?$",
            "title": "Amount",
            "description": "Amount in the coin's normal units, as a string, like on an exchange: \"5\" is 5 USDT, \"1.5\" is 1.5 TON. No more decimal places than the coin has (TON 9, USDT 6). Between 0.1 and 7000 TON, or between 3 and 10000 USDT: the upper bound catches the common mistake of sending smallest units (\"5000000\") instead of normal ones (\"5\")."
          },
          "order_id": {
            "anyOf": [
              {
                "type": "string",
                "maxLength": 200
              },
              {
                "type": "null"
              }
            ],
            "title": "Order Id",
            "description": "Your own order reference, up to 200 characters. Echoed back in every webhook — this is how you match a payment to an order."
          },
          "description": {
            "anyOf": [
              {
                "type": "string",
                "maxLength": 1000
              },
              {
                "type": "null"
              }
            ],
            "title": "Description",
            "description": "Shown to the buyer on the payment page. Up to 1000 characters."
          },
          "ttl_minutes": {
            "anyOf": [
              {
                "type": "integer",
                "maximum": 1440.0,
                "minimum": 1.0
              },
              {
                "type": "null"
              }
            ],
            "title": "Ttl Minutes",
            "description": "How long the invoice stays payable, 1 to 1440 minutes. Omit it and the core's default applies (2 hours today). An underpaid invoice keeps its address for another 24 hours on top."
          },
          "idempotency_key": {
            "anyOf": [
              {
                "type": "string",
                "maxLength": 200
              },
              {
                "type": "null"
              }
            ],
            "title": "Idempotency Key",
            "description": "Send the same value when retrying the same request and you get the same invoice back instead of a second one. A body field, not the `Idempotency-Key` header — the header is not read here. Retries with the same key do not count against the hourly invoice cap."
          }
        },
        "type": "object",
        "required": [
          "asset",
          "amount"
        ],
        "title": "InvoiceCreate",
        "description": "What you send to open an invoice. No shop id: the shop comes from the API key."
      },
      "InvoiceView": {
        "properties": {
          "invoice_id": {
            "type": "string",
            "title": "Invoice Id",
            "description": "Our id for this invoice. Store it against your order — it identifies the payment everywhere else."
          },
          "payment_url": {
            "anyOf": [
              {
                "type": "string"
              },
              {
                "type": "null"
              }
            ],
            "title": "Payment Url",
            "description": "Where to send the buyer: the hosted payment page with amount, address, QR code, countdown and live status. Null only on a deployment with no checkout page configured."
          },
          "address": {
            "type": "string",
            "title": "Address",
            "description": "The receiving address, in non-bounceable form (`UQ…` on mainnet, `0Q…` on testnet). If you render your own checkout, print it exactly as given: re-encoding it makes coins sent to a not-yet-deployed wallet bounce back to the sender."
          },
          "asset": {
            "type": "string",
            "title": "Asset",
            "description": "The coin this invoice asks for."
          },
          "amount": {
            "type": "string",
            "title": "Amount",
            "description": "The amount asked for, in the coin's normal units, exactly as you sent it. Show this one."
          },
          "amount_minor": {
            "type": "string",
            "title": "Amount Minor",
            "description": "The same amount as an integer in the coin's smallest unit, as a string. Compute with this one."
          },
          "status": {
            "type": "string",
            "title": "Status",
            "description": "`pending` when freshly created. Later `underpaid`, `paid`, `overpaid`, `expired` or `cancelled`. Release the goods on `paid` or `overpaid` only."
          },
          "paid": {
            "anyOf": [
              {
                "type": "string"
              },
              {
                "type": "null"
              }
            ],
            "title": "Paid",
            "description": "How much has arrived on this invoice so far, in normal units. Only confirmed transfers in the invoice's own coin are counted."
          },
          "paid_minor": {
            "anyOf": [
              {
                "type": "string"
              },
              {
                "type": "null"
              }
            ],
            "title": "Paid Minor",
            "description": "The same, as an integer in the smallest unit. The field that matters on `underpaid`: the status says less arrived, this says how much less. An `expired` invoice can carry a non-zero value — an underpayment whose grace period ran out; that money stayed with you."
          },
          "order_id": {
            "anyOf": [
              {
                "type": "string"
              },
              {
                "type": "null"
              }
            ],
            "title": "Order Id",
            "description": "Your own reference, as you sent it."
          },
          "description": {
            "anyOf": [
              {
                "type": "string"
              },
              {
                "type": "null"
              }
            ],
            "title": "Description",
            "description": "The description you sent, as shown to the buyer."
          },
          "expires_at": {
            "type": "string",
            "title": "Expires At",
            "description": "When the invoice stops being payable, RFC 3339. An `underpaid` invoice keeps its address for 24 hours past this."
          },
          "created_at": {
            "type": "string",
            "title": "Created At",
            "description": "When the invoice was opened, RFC 3339."
          }
        },
        "type": "object",
        "required": [
          "invoice_id",
          "address",
          "asset",
          "amount",
          "amount_minor",
          "status",
          "expires_at",
          "created_at"
        ],
        "title": "InvoiceView",
        "description": "An invoice as its merchant sees it."
      },
      "PublicInvoiceView": {
        "properties": {
          "invoice_id": {
            "type": "string",
            "title": "Invoice Id",
            "description": "The invoice being paid."
          },
          "address": {
            "type": "string",
            "title": "Address",
            "description": "The receiving address, non-bounceable, to be shown exactly as given."
          },
          "asset": {
            "type": "string",
            "title": "Asset",
            "description": "The coin to send. Sending anything else on the TON network loses the money."
          },
          "amount": {
            "type": "string",
            "title": "Amount",
            "description": "The amount asked for, in normal units."
          },
          "amount_minor": {
            "type": "string",
            "title": "Amount Minor",
            "description": "The same amount in the smallest unit — what a `ton://transfer` link's `amount` takes."
          },
          "status": {
            "type": "string",
            "title": "Status",
            "description": "`pending`, `underpaid`, `paid`, `overpaid`, `expired` or `cancelled`."
          },
          "paid": {
            "anyOf": [
              {
                "type": "string"
              },
              {
                "type": "null"
              }
            ],
            "title": "Paid",
            "description": "How much has arrived so far, normal units."
          },
          "paid_minor": {
            "anyOf": [
              {
                "type": "string"
              },
              {
                "type": "null"
              }
            ],
            "title": "Paid Minor",
            "description": "How much has arrived so far, smallest unit. Subtract from `amount_minor` for what is still owed."
          },
          "description": {
            "anyOf": [
              {
                "type": "string"
              },
              {
                "type": "null"
              }
            ],
            "title": "Description",
            "description": "What the buyer is paying for."
          },
          "expires_at": {
            "type": "string",
            "title": "Expires At",
            "description": "When the invoice stops being payable. Add 24 hours when the status is `underpaid`."
          },
          "shop_name": {
            "anyOf": [
              {
                "type": "string"
              },
              {
                "type": "null"
              }
            ],
            "title": "Shop Name",
            "description": "The merchant's shop name, so the page is recognisable rather than a bare address."
          },
          "shop_url": {
            "anyOf": [
              {
                "type": "string"
              },
              {
                "type": "null"
              }
            ],
            "title": "Shop Url",
            "description": "The merchant's storefront, for the \"back to the shop\" link."
          }
        },
        "type": "object",
        "required": [
          "invoice_id",
          "address",
          "asset",
          "amount",
          "amount_minor",
          "status",
          "expires_at"
        ],
        "title": "PublicInvoiceView",
        "description": "What the buyer's payment page is allowed to see. Deliberately narrower than the merchant view: no `order_id`, no `created_at`."
      },
      "ValidationError": {
        "properties": {
          "loc": {
            "items": {
              "anyOf": [
                {
                  "type": "string"
                },
                {
                  "type": "integer"
                }
              ]
            },
            "type": "array",
            "title": "Location"
          },
          "msg": {
            "type": "string",
            "title": "Message"
          },
          "type": {
            "type": "string",
            "title": "Error Type"
          },
          "input": {
            "title": "Input"
          },
          "ctx": {
            "type": "object",
            "title": "Context"
          }
        },
        "type": "object",
        "required": [
          "loc",
          "msg",
          "type"
        ],
        "title": "ValidationError"
      },
      "WebhookCreditedData": {
        "type": "object",
        "title": "WebhookCreditedData",
        "description": "All money here is an integer in the coin's smallest unit, as a string (TON 9 decimals, USDT_TON 6) — the opposite of the REST API, which takes and returns normal units in `amount`.",
        "properties": {
          "invoice_id": {
            "type": [
              "string",
              "null"
            ],
            "format": "uuid",
            "description": "The invoice this payment landed on."
          },
          "order_id": {
            "type": [
              "string",
              "null"
            ],
            "description": "Your own reference, exactly as you sent it. Absent if you sent none."
          },
          "asset": {
            "type": [
              "string",
              "null"
            ],
            "enum": [
              "TON",
              "USDT_TON",
              null
            ],
            "description": "The coin that actually arrived — not necessarily the coin of the invoice."
          },
          "amount": {
            "type": [
              "string",
              "null"
            ],
            "description": "What arrived in this transfer, smallest unit.",
            "examples": [
              "5000000"
            ]
          },
          "credited": {
            "type": [
              "string",
              "null"
            ],
            "description": "What landed on your balance: `amount − fee`.",
            "examples": [
              "4905000"
            ]
          },
          "fee": {
            "type": [
              "string",
              "null"
            ],
            "description": "Our fee on this transfer.",
            "examples": [
              "95000"
            ]
          },
          "status": {
            "type": [
              "string",
              "null"
            ],
            "enum": [
              "pending",
              "underpaid",
              "paid",
              "overpaid",
              "expired",
              "cancelled",
              null
            ],
            "description": "The invoice's status now. Release the goods only on `paid` or `overpaid`. Never trust the arrival of the call itself."
          },
          "paid_minor": {
            "type": [
              "string",
              "null"
            ],
            "description": "Total received on this invoice so far, in the invoice's coin, smallest unit. The field that matters on `underpaid`: the status says less arrived, this says how much less.",
            "examples": [
              "5000000"
            ]
          },
          "tx_hash": {
            "type": [
              "string",
              "null"
            ],
            "description": "The on-chain transaction."
          },
          "asset_mismatch": {
            "type": "boolean",
            "const": true,
            "description": "Present, and always `true`, only when the coin that arrived is not the coin of the invoice. One deposit address serves both TON and USDT, so a USDT invoice can be paid in TON: the money is credited to you, but the invoice stays unpaid."
          },
          "invoice_asset": {
            "type": "string",
            "description": "Comes with `asset_mismatch`: the coin the invoice actually asks for."
          }
        },
        "required": [
          "invoice_id",
          "order_id",
          "asset",
          "amount",
          "credited",
          "fee",
          "status",
          "paid_minor",
          "tx_hash"
        ]
      },
      "WebhookCreditedEvent": {
        "type": "object",
        "title": "WebhookCreditedEvent",
        "description": "Body of a `payment.credited` delivery. The bytes you receive are the bytes that were signed.",
        "required": [
          "event_id",
          "type",
          "data"
        ],
        "properties": {
          "event_id": {
            "type": "string",
            "format": "uuid",
            "description": "Unique per event, repeated in `X-Paysell-Event-Id`. Deduplicate on it."
          },
          "type": {
            "type": "string",
            "const": "payment.credited"
          },
          "data": {
            "$ref": "#/components/schemas/WebhookCreditedData"
          }
        }
      },
      "WebhookRejectedData": {
        "type": "object",
        "title": "WebhookRejectedData",
        "description": "Fewer fields than a credited payment, and not out of thrift: a rejected deposit has no `credited` and no `fee`, and the invoice's status does not change — it stays unpaid.",
        "properties": {
          "invoice_id": {
            "type": [
              "string",
              "null"
            ],
            "format": "uuid"
          },
          "order_id": {
            "type": [
              "string",
              "null"
            ],
            "description": "Your own reference, looked up from the invoice."
          },
          "asset": {
            "type": [
              "string",
              "null"
            ],
            "enum": [
              "TON",
              "USDT_TON",
              null
            ]
          },
          "amount": {
            "type": [
              "string",
              "null"
            ],
            "description": "What arrived on chain, smallest unit."
          },
          "tx_hash": {
            "type": [
              "string",
              "null"
            ]
          },
          "reason": {
            "type": [
              "string",
              "null"
            ],
            "description": "Why the operator declined to credit it. You will have to explain this to the buyer."
          }
        },
        "required": [
          "invoice_id",
          "order_id",
          "asset",
          "amount",
          "tx_hash",
          "reason"
        ]
      },
      "WebhookRejectedEvent": {
        "type": "object",
        "title": "WebhookRejectedEvent",
        "description": "Body of a `payment.rejected` delivery.",
        "required": [
          "event_id",
          "type",
          "data"
        ],
        "properties": {
          "event_id": {
            "type": "string",
            "format": "uuid"
          },
          "type": {
            "type": "string",
            "const": "payment.rejected"
          },
          "data": {
            "$ref": "#/components/schemas/WebhookRejectedData"
          }
        }
      }
    }
  }
}
