{
  "openapi": "3.1.0",
  "info": {
    "title": "MyStars FaaS — Fulfilment API",
    "version": "1.12.0",
    "summary": "Buy Telegram Stars & Premium for any @username, paid in GRAM (ex TON) or USDT (TON).",
    "description": "MyStars FaaS is a public B2B API for buying and reselling **Telegram Stars** and **Telegram Premium**, delivered to any Telegram `@username` and paid in **GRAM (ex TON)** or **USDT (TON)**.\n\nQuote a price, check the recipient, create an order, then pay the returned on-chain address. MyStars holds the payment, fulfils delivery through Fragment, and notifies you with a signed webhook when the order is delivered or reversed.\n\n## Getting an API key\n\nKeys are issued inside our Telegram bot — no dashboard, no signup form. Open [@my_stars_tg_bot](https://t.me/my_stars_tg_bot), tap **API access**, and copy your secret. Send it in the `X-Api-Key` header on every request.\n\n## Typed SDKs\n\nSkip raw HTTP with an official client: `npm install @mystars-tg/faas-sdk` (TypeScript) or `pip install mystars-faas` (Python). Each wraps every call with retries, idempotency, typed errors, and on-chain payment builders.\n\n## Documentation\n\nFull guides — quick start, rate limits, idempotency, webhooks, and reversal rules — live at the [developer portal](https://mystars.tg/docs).\n",
    "contact": {
      "name": "MyStars API support",
      "url": "https://t.me/Mystars_support_bot"
    },
    "license": {
      "name": "Proprietary",
      "url": "https://mystars.tg/terms"
    }
  },
  "externalDocs": {
    "description": "MyStars FaaS API documentation",
    "url": "https://mystars.tg/docs"
  },
  "servers": [
    {
      "url": "https://api.mystars.tg",
      "description": "Production"
    }
  ],
  "security": [
    {
      "ApiKeyAuth": []
    }
  ],
  "tags": [
    {
      "name": "Pricing",
      "description": "Quote a price; list supported payment currencies and products."
    },
    {
      "name": "Recipients",
      "description": "Resolve a recipient and check delivery eligibility before ordering."
    },
    {
      "name": "Orders",
      "description": "Create, inspect, list and cancel fulfilment orders."
    }
  ],
  "paths": {
    "/v1/orders": {
      "post": {
        "tags": [
          "Orders"
        ],
        "operationId": "createOrder",
        "summary": "Create an order",
        "description": "Pre-flight the recipient, quote the price, and create an order in\n`awaiting_payment`. The response `payment` block tells you exactly how\nmuch to send, to which address, and with which `memo` (the order id).\nAn ineligible recipient returns `422 recipient_ineligible` and creates\n**no** order (you are never charged for an undeliverable recipient). If\neligibility cannot be verified right now (a transient upstream blip), the\ncall returns a **retryable `503`** and creates no order — retry shortly\nwith the same `Idempotency-Key`.\n",
        "parameters": [
          {
            "$ref": "#/components/parameters/IdempotencyKey"
          }
        ],
        "requestBody": {
          "required": true,
          "content": {
            "application/json": {
              "schema": {
                "$ref": "#/components/schemas/CreateOrderRequest"
              },
              "examples": {
                "stars": {
                  "summary": "500 Stars to @durov, paid in GRAM",
                  "value": {
                    "type": "stars",
                    "recipient": {
                      "username": "durov"
                    },
                    "quantity": 500,
                    "payment_currency": "ton",
                    "callback_url": "https://example.com/webhooks/mystars"
                  }
                },
                "premium": {
                  "summary": "3 months of Premium, paid in USDT",
                  "value": {
                    "type": "premium",
                    "recipient": {
                      "username": "durov"
                    },
                    "months": 3,
                    "payment_currency": "usdt_ton"
                  }
                }
              }
            }
          }
        },
        "x-codeSamples": [
          {
            "lang": "shell",
            "label": "cURL",
            "source": "curl -X POST https://api.mystars.tg/v1/orders \\\n  -H \"X-Api-Key: $MYSTARS_API_KEY\" \\\n  -H \"Idempotency-Key: $(uuidgen)\" \\\n  -H \"Content-Type: application/json\" \\\n  -d '{\"type\":\"stars\",\"recipient\":{\"username\":\"durov\"},\"quantity\":500,\"payment_currency\":\"ton\"}'\n"
          },
          {
            "lang": "typescript",
            "label": "TypeScript SDK",
            "source": "import { MyStarsClient } from \"@mystars-tg/faas-sdk\";\n\nconst client = MyStarsClient.production(process.env.MYSTARS_API_KEY!);\n\n// Pass a STABLE idempotencyKey = your own order id so a retry returns\n// the SAME order instead of creating a duplicate.\nconst order = await client.createOrder(\n  { type: \"stars\", recipient: { username: \"durov\" }, quantity: 500, payment_currency: \"ton\" },\n  { idempotencyKey: `order-${myOrderId}` },\n);\nconsole.log(order.payment); // amount, pay_to_address, memo\n"
          },
          {
            "lang": "python",
            "label": "Python SDK",
            "source": "import os\n\nfrom mystars_faas import MyStarsClient\n\nclient = MyStarsClient.production(os.environ[\"MYSTARS_API_KEY\"])\n\n# Pass a STABLE idempotency_key = your own order id so a retry returns\n# the SAME order instead of creating a duplicate.\norder = client.create_order(\n    type=\"stars\",\n    recipient=\"durov\",\n    quantity=500,\n    payment_currency=\"ton\",\n    idempotency_key=f\"order-{my_order_id}\",\n)\nprint(order.payment)  # amount, pay_to_address, memo\n"
          },
          {
            "lang": "python",
            "label": "Python (HTTP)",
            "source": "import uuid\n\nimport requests\n\nresp = requests.post(\n    \"https://api.mystars.tg/v1/orders\",\n    headers={\n        \"X-Api-Key\": MYSTARS_API_KEY,\n        \"Idempotency-Key\": str(uuid.uuid4()),\n    },\n    json={\n        \"type\": \"stars\",\n        \"recipient\": {\"username\": \"durov\"},\n        \"quantity\": 500,\n        \"payment_currency\": \"ton\",\n    },\n)\nresp.raise_for_status()\nprint(resp.json())\n"
          }
        ],
        "responses": {
          "200": {
            "description": "Idempotent replay — the same key + body returns the original order.",
            "content": {
              "application/json": {
                "schema": {
                  "$ref": "#/components/schemas/CreatedOrder"
                }
              }
            }
          },
          "201": {
            "description": "Order created (or replayed on an idempotent retry → 200).",
            "content": {
              "application/json": {
                "schema": {
                  "$ref": "#/components/schemas/CreatedOrder"
                }
              }
            }
          },
          "400": {
            "$ref": "#/components/responses/BadRequest"
          },
          "401": {
            "$ref": "#/components/responses/Unauthorized"
          },
          "409": {
            "description": "Idempotency-Key reused with a different body.",
            "content": {
              "application/json": {
                "schema": {
                  "$ref": "#/components/schemas/Error"
                }
              }
            }
          },
          "422": {
            "$ref": "#/components/responses/RecipientIneligible"
          },
          "429": {
            "$ref": "#/components/responses/RateLimited"
          },
          "503": {
            "$ref": "#/components/responses/Unavailable"
          }
        }
      },
      "get": {
        "tags": [
          "Orders"
        ],
        "operationId": "listOrders",
        "summary": "List your orders",
        "description": "Tenant-scoped list, newest first, keyset-paginated. Pass the returned\n`next_cursor` back as `?cursor=` to page; a null `next_cursor` is the\nlast page.\n",
        "parameters": [
          {
            "name": "status",
            "in": "query",
            "required": false,
            "schema": {
              "$ref": "#/components/schemas/OrderStatus"
            },
            "description": "Filter by order status."
          },
          {
            "name": "limit",
            "in": "query",
            "required": false,
            "schema": {
              "type": "integer",
              "minimum": 1,
              "maximum": 100,
              "default": 50
            }
          },
          {
            "name": "cursor",
            "in": "query",
            "required": false,
            "schema": {
              "type": "string"
            },
            "description": "Opaque cursor from a previous page's `next_cursor`."
          }
        ],
        "x-codeSamples": [
          {
            "lang": "shell",
            "label": "cURL",
            "source": "curl \"https://api.mystars.tg/v1/orders?status=awaiting_payment&limit=50\" \\\n  -H \"X-Api-Key: $MYSTARS_API_KEY\"\n"
          },
          {
            "lang": "typescript",
            "label": "TypeScript SDK",
            "source": "import { MyStarsClient } from \"@mystars-tg/faas-sdk\";\n\nconst client = MyStarsClient.production(process.env.MYSTARS_API_KEY!);\n\n// Auto-paginating async iterator — the cursor is handled for you.\nfor await (const order of client.listOrders({ status: \"awaiting_payment\", limit: 50 })) {\n  console.log(order.order_id, order.status);\n}\n"
          },
          {
            "lang": "python",
            "label": "Python SDK",
            "source": "import os\n\nfrom mystars_faas import MyStarsClient\n\nclient = MyStarsClient.production(os.environ[\"MYSTARS_API_KEY\"])\n\npage = client.list_orders(status=\"awaiting_payment\", limit=50)\nfor order in page.orders:\n    print(order.order_id, order.status)\n# page.next_cursor → pass back as cursor= for the next page (None = last).\n"
          },
          {
            "lang": "python",
            "label": "Python (HTTP)",
            "source": "import requests\n\nresp = requests.get(\n    \"https://api.mystars.tg/v1/orders\",\n    headers={\"X-Api-Key\": MYSTARS_API_KEY},\n    params={\"status\": \"awaiting_payment\", \"limit\": 50},\n)\nresp.raise_for_status()\nprint(resp.json())\n"
          }
        ],
        "responses": {
          "200": {
            "description": "A page of orders.",
            "content": {
              "application/json": {
                "schema": {
                  "type": "object",
                  "required": [
                    "orders",
                    "next_cursor"
                  ],
                  "properties": {
                    "orders": {
                      "type": "array",
                      "items": {
                        "$ref": "#/components/schemas/Order"
                      }
                    },
                    "next_cursor": {
                      "type": [
                        "string",
                        "null"
                      ],
                      "description": "Pass back as `?cursor=`; null on the last page."
                    }
                  }
                }
              }
            }
          },
          "400": {
            "$ref": "#/components/responses/BadRequest"
          },
          "401": {
            "$ref": "#/components/responses/Unauthorized"
          },
          "429": {
            "$ref": "#/components/responses/RateLimited"
          }
        }
      }
    },
    "/v1/orders/{id}": {
      "get": {
        "tags": [
          "Orders"
        ],
        "operationId": "getOrder",
        "summary": "Get an order",
        "description": "Fetch one order by id. Orders are tenant-isolated: another tenant's id\n(or an unknown/malformed id) returns `404` — never leaking its existence.\n",
        "parameters": [
          {
            "$ref": "#/components/parameters/OrderId"
          }
        ],
        "x-codeSamples": [
          {
            "lang": "shell",
            "label": "cURL",
            "source": "curl https://api.mystars.tg/v1/orders/7c9e6679-7425-40de-944b-e07fc1f90ae7 \\\n  -H \"X-Api-Key: $MYSTARS_API_KEY\"\n"
          },
          {
            "lang": "typescript",
            "label": "TypeScript SDK",
            "source": "import { MyStarsClient } from \"@mystars-tg/faas-sdk\";\n\nconst client = MyStarsClient.production(process.env.MYSTARS_API_KEY!);\n\nconst order = await client.getOrder(\"7c9e6679-7425-40de-944b-e07fc1f90ae7\");\nconsole.log(order.status);\n"
          },
          {
            "lang": "python",
            "label": "Python SDK",
            "source": "import os\n\nfrom mystars_faas import MyStarsClient\n\nclient = MyStarsClient.production(os.environ[\"MYSTARS_API_KEY\"])\n\norder = client.get_order(\"7c9e6679-7425-40de-944b-e07fc1f90ae7\")\nprint(order.status)\n"
          },
          {
            "lang": "python",
            "label": "Python (HTTP)",
            "source": "import requests\n\norder_id = \"7c9e6679-7425-40de-944b-e07fc1f90ae7\"\nresp = requests.get(\n    f\"https://api.mystars.tg/v1/orders/{order_id}\",\n    headers={\"X-Api-Key\": MYSTARS_API_KEY},\n)\nresp.raise_for_status()\nprint(resp.json())\n"
          }
        ],
        "responses": {
          "200": {
            "description": "The order.",
            "content": {
              "application/json": {
                "schema": {
                  "$ref": "#/components/schemas/Order"
                }
              }
            }
          },
          "401": {
            "$ref": "#/components/responses/Unauthorized"
          },
          "404": {
            "$ref": "#/components/responses/NotFound"
          },
          "429": {
            "$ref": "#/components/responses/RateLimited"
          }
        }
      }
    },
    "/v1/orders/{id}/cancel": {
      "post": {
        "tags": [
          "Orders"
        ],
        "operationId": "cancelOrder",
        "summary": "Cancel an order",
        "description": "Cancel an order that is still `awaiting_payment`. Any other state returns\n`409` (you can't cancel an order that's already paid or processing).\n",
        "parameters": [
          {
            "$ref": "#/components/parameters/OrderId"
          }
        ],
        "x-codeSamples": [
          {
            "lang": "shell",
            "label": "cURL",
            "source": "curl -X POST https://api.mystars.tg/v1/orders/7c9e6679-7425-40de-944b-e07fc1f90ae7/cancel \\\n  -H \"X-Api-Key: $MYSTARS_API_KEY\"\n"
          },
          {
            "lang": "typescript",
            "label": "TypeScript SDK",
            "source": "import { MyStarsClient } from \"@mystars-tg/faas-sdk\";\n\nconst client = MyStarsClient.production(process.env.MYSTARS_API_KEY!);\n\nconst result = await client.cancelOrder(\"7c9e6679-7425-40de-944b-e07fc1f90ae7\");\nconsole.log(result.status); // \"cancelled\"\n"
          },
          {
            "lang": "python",
            "label": "Python SDK",
            "source": "import os\n\nfrom mystars_faas import MyStarsClient\n\nclient = MyStarsClient.production(os.environ[\"MYSTARS_API_KEY\"])\n\nresult = client.cancel_order(\"7c9e6679-7425-40de-944b-e07fc1f90ae7\")\nprint(result[\"status\"])  # \"cancelled\"\n"
          },
          {
            "lang": "python",
            "label": "Python (HTTP)",
            "source": "import requests\n\norder_id = \"7c9e6679-7425-40de-944b-e07fc1f90ae7\"\nresp = requests.post(\n    f\"https://api.mystars.tg/v1/orders/{order_id}/cancel\",\n    headers={\"X-Api-Key\": MYSTARS_API_KEY},\n)\nresp.raise_for_status()\nprint(resp.json())\n"
          }
        ],
        "responses": {
          "200": {
            "description": "Order cancelled.",
            "content": {
              "application/json": {
                "schema": {
                  "type": "object",
                  "required": [
                    "order_id",
                    "status"
                  ],
                  "properties": {
                    "order_id": {
                      "type": "string",
                      "format": "uuid"
                    },
                    "status": {
                      "type": "string",
                      "enum": [
                        "cancelled"
                      ]
                    }
                  }
                }
              }
            }
          },
          "401": {
            "$ref": "#/components/responses/Unauthorized"
          },
          "404": {
            "$ref": "#/components/responses/NotFound"
          },
          "409": {
            "description": "The order is not in `awaiting_payment` and cannot be cancelled.",
            "content": {
              "application/json": {
                "schema": {
                  "$ref": "#/components/schemas/Error"
                }
              }
            }
          },
          "429": {
            "$ref": "#/components/responses/RateLimited"
          }
        }
      }
    },
    "/v1/recipients/check": {
      "post": {
        "tags": [
          "Recipients"
        ],
        "operationId": "checkRecipient",
        "summary": "Check a recipient",
        "description": "Resolve a `@username` and check whether they can receive the requested\nitem, before you create an order. Read-only and fail-open — a transient\noracle hiccup resolves to `eligible: true` rather than blocking you.\n\n**Why `type` is required.** The check runs against the very product you\nintend to order, so it must know which one. Pass `type: stars` to resolve a\n**Stars** recipient, or `type: premium` to resolve a **Premium-gift**\nrecipient (the Premium path also takes `months` — 3, 6 or 12). Always check\nwith the same `type` you'll use in `POST /v1/orders`: a Stars check does not\nprove a Premium gift will be accepted, and vice-versa.\n\n> ⚠️ **A recipient who already has an active Premium subscription cannot be\n> gifted Premium.** Telegram blocks gifting a Premium subscription to anyone\n> whose subscription is still active (for example, an annual plan that has\n> not expired yet) — this is **Telegram's restriction, not ours**. The check\n> surfaces it as `eligible: false` with `reason: \"already_subscribed\"` and\n> Telegram's verbatim wording in `telegram_message`. The same recipient also\n> makes `POST /v1/orders` fail with `422 recipient_ineligible`: no order is\n> created and you are not charged. Pre-flighting Premium recipients here is\n> the cheapest way to tell your user *before* they pay.\n\n**Rate limit:** this endpoint carries a tighter per-tenant cap of 60\nrequests/min (in addition to the standard per-tenant budget), because each\ncall performs a live upstream lookup. If you exceed this,\nyou receive a `429` and should back off for the remainder of the minute.\n",
        "requestBody": {
          "required": true,
          "content": {
            "application/json": {
              "schema": {
                "$ref": "#/components/schemas/RecipientCheckRequest"
              }
            }
          }
        },
        "x-codeSamples": [
          {
            "lang": "shell",
            "label": "cURL",
            "source": "curl -X POST https://api.mystars.tg/v1/recipients/check \\\n  -H \"X-Api-Key: $MYSTARS_API_KEY\" \\\n  -H \"Content-Type: application/json\" \\\n  -d '{\"type\":\"stars\",\"recipient\":{\"username\":\"durov\"}}'\n"
          },
          {
            "lang": "typescript",
            "label": "TypeScript SDK",
            "source": "import { MyStarsClient } from \"@mystars-tg/faas-sdk\";\n\nconst client = MyStarsClient.production(process.env.MYSTARS_API_KEY!);\n\nconst check = await client.checkRecipient({\n  type: \"stars\",\n  recipient: { username: \"durov\" },\n});\nif (!check.eligible) throw new Error(check.reason ?? \"ineligible\");\n"
          },
          {
            "lang": "python",
            "label": "Python SDK",
            "source": "import os\n\nfrom mystars_faas import MyStarsClient\n\nclient = MyStarsClient.production(os.environ[\"MYSTARS_API_KEY\"])\n\ncheck = client.check_recipient(\"durov\", type=\"stars\")\nif not check.eligible:\n    raise SystemExit(check.telegram_message)\n"
          },
          {
            "lang": "python",
            "label": "Python (HTTP)",
            "source": "import requests\n\nresp = requests.post(\n    \"https://api.mystars.tg/v1/recipients/check\",\n    headers={\"X-Api-Key\": MYSTARS_API_KEY},\n    json={\"type\": \"stars\", \"recipient\": {\"username\": \"durov\"}},\n)\nresp.raise_for_status()\nprint(resp.json())\n"
          }
        ],
        "responses": {
          "200": {
            "description": "Resolution + eligibility result.",
            "content": {
              "application/json": {
                "schema": {
                  "$ref": "#/components/schemas/RecipientCheckResult"
                }
              }
            }
          },
          "400": {
            "$ref": "#/components/responses/BadRequest"
          },
          "401": {
            "$ref": "#/components/responses/Unauthorized"
          },
          "429": {
            "$ref": "#/components/responses/RateLimited"
          }
        }
      }
    },
    "/v1/pricing": {
      "get": {
        "tags": [
          "Pricing"
        ],
        "operationId": "getPricing",
        "summary": "Quote a price",
        "description": "Get a quote for an item in a payment currency. The `amount` is the full,\nall-in total you'll send on-chain (in `currency`) — there's nothing else to\nadd.\n\nThe quote echoes back **what it priced** — `type` plus `quantity` (for\nStars) or `months` (for Premium); the field that doesn't apply is `null` —\nso the `amount` is self-describing and you never have to correlate it to your\nrequest.\n\nThe response also carries `quoted_at` + `valid_until` (a re-quote hint —\nthe price tracks the market and is recomputed about every minute; it is\nlocked only when you create an order) and `usdt_per_ton` (the current\npublic GRAM↔USDT rate, for your own conversion; `null` if\nmomentarily unavailable).\n\n**Rate limit:** this endpoint carries a tighter per-tenant cap of 60\nrequests/min (in addition to the standard per-tenant budget). If you\nexceed this, you receive a `429` and should back off for the remainder of\nthe minute.\n",
        "parameters": [
          {
            "name": "type",
            "in": "query",
            "required": true,
            "schema": {
              "type": "string",
              "enum": [
                "stars",
                "premium"
              ]
            }
          },
          {
            "name": "quantity",
            "in": "query",
            "required": false,
            "description": "Required when `type=stars` — the number of Stars (50–1000000).",
            "schema": {
              "type": "integer",
              "minimum": 50,
              "maximum": 1000000
            }
          },
          {
            "name": "months",
            "in": "query",
            "required": false,
            "description": "Required when `type=premium` — the subscription length in months (3, 6, or 12).",
            "schema": {
              "type": "integer",
              "enum": [
                3,
                6,
                12
              ]
            }
          },
          {
            "name": "payment_currency",
            "in": "query",
            "required": false,
            "schema": {
              "$ref": "#/components/schemas/PaymentCurrency"
            }
          }
        ],
        "x-codeSamples": [
          {
            "lang": "shell",
            "label": "cURL",
            "source": "curl \"https://api.mystars.tg/v1/pricing?type=stars&quantity=500&payment_currency=ton\" \\\n  -H \"X-Api-Key: $MYSTARS_API_KEY\"\n"
          },
          {
            "lang": "typescript",
            "label": "TypeScript SDK",
            "source": "import { MyStarsClient } from \"@mystars-tg/faas-sdk\";\n\nconst client = MyStarsClient.production(process.env.MYSTARS_API_KEY!);\n\nconst quote = await client.getPricing({\n  type: \"stars\",\n  quantity: 500,\n  payment_currency: \"ton\",\n});\nconsole.log(`pay ${quote.amount} ${quote.currency}`);\n"
          },
          {
            "lang": "python",
            "label": "Python SDK",
            "source": "import os\n\nfrom mystars_faas import MyStarsClient\n\nclient = MyStarsClient.production(os.environ[\"MYSTARS_API_KEY\"])\n\nquote = client.get_pricing(type=\"stars\", quantity=500, payment_currency=\"ton\")\nprint(quote.amount, quote.currency)\n"
          },
          {
            "lang": "python",
            "label": "Python (HTTP)",
            "source": "import requests\n\nresp = requests.get(\n    \"https://api.mystars.tg/v1/pricing\",\n    headers={\"X-Api-Key\": MYSTARS_API_KEY},\n    params={\"type\": \"stars\", \"quantity\": 500, \"payment_currency\": \"ton\"},\n)\nresp.raise_for_status()\nprint(resp.json())\n"
          }
        ],
        "responses": {
          "200": {
            "description": "A price quote.",
            "content": {
              "application/json": {
                "schema": {
                  "$ref": "#/components/schemas/Quote"
                }
              }
            }
          },
          "400": {
            "$ref": "#/components/responses/BadRequest"
          },
          "401": {
            "$ref": "#/components/responses/Unauthorized"
          },
          "429": {
            "$ref": "#/components/responses/RateLimited"
          },
          "503": {
            "$ref": "#/components/responses/Unavailable"
          }
        }
      }
    },
    "/v1/pricing/batch": {
      "get": {
        "tags": [
          "Pricing"
        ],
        "operationId": "getPricingBatch",
        "summary": "Quote many Stars quantities in one request",
        "description": "Quote a whole LIST of Stars quantities in a single request — built for\nstorefronts that refresh preview prices for an entire pack catalog. One\nbatch call consumes ONE unit of your request budget (and one probe\nunit), instead of one per pack.\n\nStars-only (`type=stars`). The quantity list is deduped and returned\nsorted ascending, up to **200 values** per call. Each entry carries the\nsame `amount` + `fee` itemisation as `GET /v1/pricing` for that\nquantity — the two endpoints agree cent-for-cent. Shared response\nfields (`usdt_per_ton`, `quoted_at`, `valid_until`) are hoisted to the\ntop level.\n\n**Rate limit:** same tighter 60 req/min per-tenant probe cap as\n`GET /v1/pricing` — but since a single call covers your whole catalog,\none call per refresh window is all you need.\n",
        "parameters": [
          {
            "name": "type",
            "in": "query",
            "required": true,
            "schema": {
              "type": "string",
              "enum": [
                "stars"
              ]
            }
          },
          {
            "name": "quantities",
            "in": "query",
            "required": true,
            "description": "Comma-separated Stars quantities (each 50–1000000, max 200 values).",
            "schema": {
              "type": "string"
            },
            "example": "50,100,500,1000"
          },
          {
            "name": "payment_currency",
            "in": "query",
            "required": false,
            "schema": {
              "$ref": "#/components/schemas/PaymentCurrency"
            }
          }
        ],
        "x-codeSamples": [
          {
            "lang": "shell",
            "label": "cURL",
            "source": "curl \"https://api.mystars.tg/v1/pricing/batch?type=stars&quantities=50,100,500&payment_currency=usdt_ton\" \\\n  -H \"X-Api-Key: $MYSTARS_API_KEY\"\n"
          },
          {
            "lang": "typescript",
            "label": "TypeScript SDK",
            "source": "import { MyStarsClient } from \"@mystars-tg/faas-sdk\";\n\nconst client = MyStarsClient.production(process.env.MYSTARS_API_KEY!);\n\nconst batch = await client.getPricingBatch({\n  quantities: [50, 100, 500],\n  payment_currency: \"usdt_ton\",\n});\nfor (const q of batch.quotes) console.log(q.quantity, q.amount);\n"
          },
          {
            "lang": "python",
            "label": "Python SDK",
            "source": "import os\n\nfrom mystars_faas import MyStarsClient\n\nclient = MyStarsClient.production(os.environ[\"MYSTARS_API_KEY\"])\n\nbatch = client.get_pricing_batch(quantities=[50, 100, 500], payment_currency=\"usdt_ton\")\nfor q in batch.quotes:\n    print(q.quantity, q.amount)\n"
          },
          {
            "lang": "python",
            "label": "Python (HTTP)",
            "source": "import requests\n\nresp = requests.get(\n    \"https://api.mystars.tg/v1/pricing/batch\",\n    headers={\"X-Api-Key\": MYSTARS_API_KEY},\n    params={\"type\": \"stars\", \"quantities\": \"50,100,500\", \"payment_currency\": \"usdt_ton\"},\n)\nresp.raise_for_status()\nfor q in resp.json()[\"quotes\"]:\n    print(q[\"quantity\"], q[\"amount\"])\n"
          }
        ],
        "responses": {
          "200": {
            "description": "One quote per requested quantity (deduped, ascending).",
            "content": {
              "application/json": {
                "schema": {
                  "$ref": "#/components/schemas/QuoteBatch"
                }
              }
            }
          },
          "400": {
            "$ref": "#/components/responses/BadRequest"
          },
          "401": {
            "$ref": "#/components/responses/Unauthorized"
          },
          "429": {
            "$ref": "#/components/responses/RateLimited"
          },
          "503": {
            "$ref": "#/components/responses/Unavailable"
          }
        }
      }
    },
    "/v1/currencies": {
      "get": {
        "tags": [
          "Pricing"
        ],
        "operationId": "listCurrencies",
        "summary": "List payment currencies",
        "description": "The two on-chain currencies you can pay in.",
        "x-codeSamples": [
          {
            "lang": "shell",
            "label": "cURL",
            "source": "curl https://api.mystars.tg/v1/currencies \\\n  -H \"X-Api-Key: $MYSTARS_API_KEY\"\n"
          },
          {
            "lang": "typescript",
            "label": "TypeScript SDK",
            "source": "import { MyStarsClient } from \"@mystars-tg/faas-sdk\";\n\nconst client = MyStarsClient.production(process.env.MYSTARS_API_KEY!);\n\nconst currencies = await client.listCurrencies();\nconsole.log(currencies);\n"
          },
          {
            "lang": "python",
            "label": "Python SDK",
            "source": "import os\n\nfrom mystars_faas import MyStarsClient\n\nclient = MyStarsClient.production(os.environ[\"MYSTARS_API_KEY\"])\n\ncurrencies = client.list_currencies()\nprint(currencies)\n"
          },
          {
            "lang": "python",
            "label": "Python (HTTP)",
            "source": "import requests\n\nresp = requests.get(\n    \"https://api.mystars.tg/v1/currencies\",\n    headers={\"X-Api-Key\": MYSTARS_API_KEY},\n)\nresp.raise_for_status()\nprint(resp.json())\n"
          }
        ],
        "responses": {
          "200": {
            "description": "Supported payment currencies.",
            "content": {
              "application/json": {
                "schema": {
                  "type": "object",
                  "required": [
                    "currencies"
                  ],
                  "properties": {
                    "currencies": {
                      "type": "array",
                      "items": {
                        "type": "object",
                        "required": [
                          "code",
                          "chain",
                          "name"
                        ],
                        "properties": {
                          "code": {
                            "$ref": "#/components/schemas/PaymentCurrency"
                          },
                          "chain": {
                            "type": "string",
                            "example": "ton"
                          },
                          "name": {
                            "type": "string",
                            "example": "GRAM"
                          }
                        }
                      }
                    }
                  }
                }
              }
            }
          },
          "401": {
            "$ref": "#/components/responses/Unauthorized"
          },
          "429": {
            "$ref": "#/components/responses/RateLimited"
          }
        }
      }
    },
    "/v1/products": {
      "get": {
        "tags": [
          "Pricing"
        ],
        "operationId": "listProducts",
        "summary": "List available products",
        "description": "The product catalog — the two product **types** you can sell and the\nbuyable shape of each. Static, price-free metadata (call `GET /v1/pricing`\nfor a price): use it to build your own catalog/UI and to learn the bounds\nthe order endpoints enforce.\n\nEach entry's `parameter` names the request field to send to `/v1/pricing`\nand `/v1/orders` (`quantity` for stars, `months` for premium). A `null`\n`values` means a continuous integer range `[min, max]` (stars — any\nquantity in range, no fixed denominations); a non-null `values` is the\nexact allowed set (premium — `[3, 6, 12]`).\n",
        "x-codeSamples": [
          {
            "lang": "shell",
            "label": "cURL",
            "source": "curl https://api.mystars.tg/v1/products \\\n  -H \"X-Api-Key: $MYSTARS_API_KEY\"\n"
          },
          {
            "lang": "typescript",
            "label": "TypeScript SDK",
            "source": "import { MyStarsClient } from \"@mystars-tg/faas-sdk\";\n\nconst client = MyStarsClient.production(process.env.MYSTARS_API_KEY!);\n\nconst products = await client.listProducts();\nconsole.log(products);\n"
          },
          {
            "lang": "python",
            "label": "Python SDK",
            "source": "import os\n\nfrom mystars_faas import MyStarsClient\n\nclient = MyStarsClient.production(os.environ[\"MYSTARS_API_KEY\"])\n\nproducts = client.list_products()\nprint(products)\n"
          },
          {
            "lang": "python",
            "label": "Python (HTTP)",
            "source": "import requests\n\nresp = requests.get(\n    \"https://api.mystars.tg/v1/products\",\n    headers={\"X-Api-Key\": MYSTARS_API_KEY},\n)\nresp.raise_for_status()\nprint(resp.json())\n"
          }
        ],
        "responses": {
          "200": {
            "description": "The product catalog.",
            "content": {
              "application/json": {
                "schema": {
                  "type": "object",
                  "required": [
                    "products"
                  ],
                  "properties": {
                    "products": {
                      "type": "array",
                      "items": {
                        "$ref": "#/components/schemas/Product"
                      }
                    }
                  }
                }
              }
            }
          },
          "401": {
            "$ref": "#/components/responses/Unauthorized"
          },
          "429": {
            "$ref": "#/components/responses/RateLimited"
          }
        }
      }
    }
  },
  "webhooks": {
    "orderStatus": {
      "post": {
        "operationId": "orderStatusWebhook",
        "summary": "Order status callback",
        "description": "When an order reaches a terminal status (`delivered`, `failed`,\n`reversed`, `expired`) we `POST` this event to your `callback_url`.\n\nVerify authenticity with the `X-Faas-Signature` header — it's the\nhex `HMAC-SHA256` of the **exact raw request body** under your webhook\nsecret (the standard Stripe/GitHub signing scheme). Respond `2xx` to\nacknowledge; non-2xx is retried with exponential backoff, then\ndead-lettered. The body (and signature) are stable across retries.\n\n**Delivery constraints.** Your endpoint must respond within **5 seconds**\n(connect + headers + body timeout each). HTTP redirects are not followed —\nthe `callback_url` must be the final destination. A timeout or non-2xx\nresponse triggers the retry/dead-letter path exactly as a connection error\nwould; it never blocks order processing.\n\n**Secret rotation (rollover).** During the grace window after you rotate\nyour webhook secret, this header may carry **multiple comma-separated\nsignatures** (the new and the previous secret). Treat the header as a\ncomma-separated list and accept the request if **any** entry matches your\nsecret — so you can switch from the old secret to the new one at any point\nin the window without dropping a webhook. Outside a rotation it is a single\nsignature, so naive single-value verification keeps working day to day.\n",
        "parameters": [
          {
            "name": "X-Faas-Signature",
            "in": "header",
            "required": true,
            "description": "Hex HMAC-SHA256 of the raw body under your webhook secret. May be a comma-separated list of signatures during a secret-rotation rollover — accept if ANY matches your secret.",
            "schema": {
              "type": "string"
            }
          }
        ],
        "requestBody": {
          "required": true,
          "content": {
            "application/json": {
              "schema": {
                "type": "object",
                "required": [
                  "order_id",
                  "status"
                ],
                "properties": {
                  "order_id": {
                    "type": "string",
                    "format": "uuid"
                  },
                  "status": {
                    "type": "string",
                    "enum": [
                      "delivered",
                      "failed",
                      "reversed",
                      "expired"
                    ]
                  },
                  "failure_reason": {
                    "type": [
                      "string",
                      "null"
                    ],
                    "description": "Why a non-`delivered` order ended where it did — `underpaid` / `overpaid` (amount mismatch → `failed`, funds reversed), `no_memo` / `wrong_memo` (an unmatched payment with a missing / unrecognised memo → `failed`, funds reversed to the sender), `undeliverable` (`reversed`, funds reversed), or `expired` (no payment within the window). Null on `delivered`. See **Reversals & delivery** above for the full meaning of each.",
                    "example": "undeliverable"
                  },
                  "purchase_tx": {
                    "type": [
                      "string",
                      "null"
                    ]
                  },
                  "reversal_tx": {
                    "type": [
                      "string",
                      "null"
                    ]
                  }
                }
              }
            }
          }
        },
        "responses": {
          "200": {
            "description": "Acknowledged. Any 2xx stops retries."
          }
        }
      }
    }
  },
  "components": {
    "securitySchemes": {
      "ApiKeyAuth": {
        "type": "apiKey",
        "in": "header",
        "name": "X-Api-Key",
        "description": "Your secret API key. Get one from [@my_stars_tg_bot](https://t.me/my_stars_tg_bot) → **API access**, then send it in the `X-Api-Key` header on every request. Treat it like a password — anyone with the key can create orders on your tenant, read your order history, and cancel unpaid orders. Each order is settled by its own on-chain payment, so the key by itself cannot move funds. Rotate it any time with `/api_rotate` in the bot.\n"
      }
    },
    "parameters": {
      "OrderId": {
        "name": "id",
        "in": "path",
        "required": true,
        "description": "The order id (UUID, also used as the on-chain payment memo).",
        "schema": {
          "type": "string",
          "format": "uuid"
        }
      },
      "IdempotencyKey": {
        "name": "Idempotency-Key",
        "in": "header",
        "required": true,
        "description": "A unique key for this create attempt. Retrying with the same key and an\nidentical body returns the original order; a different body is a 409.\n",
        "schema": {
          "type": "string"
        }
      }
    },
    "schemas": {
      "PaymentCurrency": {
        "type": "string",
        "enum": [
          "ton",
          "usdt_ton"
        ],
        "default": "ton",
        "description": "`ton` = GRAM (ex TON), `usdt_ton` = USDT (TON)."
      },
      "OrderType": {
        "type": "string",
        "enum": [
          "stars",
          "premium"
        ]
      },
      "OrderStatus": {
        "type": "string",
        "description": "Lifecycle status. `awaiting_payment` is the only cancellable state.",
        "enum": [
          "received",
          "awaiting_payment",
          "paid",
          "reserved",
          "swapping",
          "funding",
          "purchasing",
          "fulfilling",
          "completed",
          "delivered",
          "failed",
          "reversed",
          "expired",
          "held",
          "cancelled"
        ]
      },
      "Recipient": {
        "type": "object",
        "required": [
          "username"
        ],
        "properties": {
          "username": {
            "type": "string",
            "description": "Telegram @username (the leading `@` is optional, case-insensitive). After canonicalisation (strip `@`, lowercase) must match `[a-z0-9_]{1,32}` — invalid or oversized handles return 400.\n",
            "pattern": "^@?[a-zA-Z0-9_]{1,32}$",
            "example": "durov"
          }
        }
      },
      "CreateOrderRequest": {
        "type": "object",
        "required": [
          "type",
          "recipient"
        ],
        "properties": {
          "type": {
            "$ref": "#/components/schemas/OrderType"
          },
          "recipient": {
            "$ref": "#/components/schemas/Recipient"
          },
          "quantity": {
            "type": "integer",
            "minimum": 50,
            "maximum": 1000000,
            "description": "Number of Stars. Required when `type=stars`. Must be in [50, 1000000]."
          },
          "months": {
            "type": "integer",
            "enum": [
              3,
              6,
              12
            ],
            "description": "Premium subscription length in months. Required when `type=premium`. Must be 3, 6, or 12."
          },
          "payment_currency": {
            "$ref": "#/components/schemas/PaymentCurrency"
          },
          "callback_url": {
            "type": "string",
            "format": "uri",
            "description": "Optional HTTPS URL for the signed order-status webhook. Must be a publicly reachable `https://` URL — loopback addresses, private-network hosts, and non-HTTPS schemes are rejected with `400 bad_request`."
          }
        }
      },
      "PaymentInstruction": {
        "type": "object",
        "description": "How to pay for the order. Send EXACTLY `amount` with `memo`.",
        "required": [
          "currency",
          "chain",
          "pay_to_address",
          "memo",
          "amount",
          "amount_units",
          "fee"
        ],
        "properties": {
          "currency": {
            "$ref": "#/components/schemas/PaymentCurrency"
          },
          "chain": {
            "type": "string",
            "example": "ton"
          },
          "pay_to_address": {
            "type": "string",
            "description": "The treasury wallet to pay. The SAME address is returned for both `ton` and `usdt_ton` — a USDT jetton transfer routes by owner, so its destination is this (owner) address, not a derived jetton-wallet address."
          },
          "memo": {
            "type": "string",
            "description": "The required transfer memo (equals the order id)."
          },
          "amount": {
            "type": "string",
            "description": "Exact amount to send, as a decimal string.",
            "example": "5.757"
          },
          "amount_units": {
            "type": "string",
            "enum": [
              "ton",
              "usdt"
            ]
          },
          "fee": {
            "description": "For `usdt_ton` only: an itemisation of the processing fee already INCLUDED in `amount` (the 1% DEX swap fee + 0.5 GRAM swap gas we pass through). `null` for `ton` (no swap, no fee). It does NOT add to `amount` — pay exactly `amount`.",
            "oneOf": [
              {
                "$ref": "#/components/schemas/FeeBreakdown"
              },
              {
                "type": "null"
              }
            ]
          }
        }
      },
      "FeeBreakdown": {
        "type": "object",
        "description": "Itemisation of the `usdt_ton` processing fee that is ALREADY part of the all-in amount. `subtotal + processing_fee == total == amount`. Pass-through swap cost only — it does not reveal our cost basis or markup. Only `total` (= `amount`) is binding — the `subtotal`/`processing_fee` split is informational and may shift by a cent with the live FX rate.",
        "required": [
          "subtotal",
          "processing_fee",
          "total",
          "description",
          "currency"
        ],
        "properties": {
          "subtotal": {
            "type": "string",
            "description": "The item price before the processing fee, as a decimal string (USDT).",
            "example": "13.18"
          },
          "processing_fee": {
            "type": "string",
            "description": "The 1% DEX swap fee + 0.5 GRAM swap gas, combined and rounded up to the cent, as a decimal string (USDT).",
            "example": "0.92"
          },
          "total": {
            "type": "string",
            "description": "subtotal + processing_fee — equals `amount`. Decimal string (USDT).",
            "example": "14.1"
          },
          "description": {
            "type": "string",
            "description": "Human-readable label for the fee components.",
            "example": "1% swap + 0.5 GRAM gas"
          },
          "currency": {
            "type": "string",
            "enum": [
              "usdt"
            ],
            "description": "The unit of the fee amounts (always `usdt`)."
          }
        }
      },
      "CreatedOrder": {
        "type": "object",
        "required": [
          "order_id",
          "status",
          "type",
          "quantity",
          "months",
          "payment",
          "expires_at"
        ],
        "properties": {
          "order_id": {
            "type": "string",
            "format": "uuid"
          },
          "status": {
            "$ref": "#/components/schemas/OrderStatus"
          },
          "type": {
            "type": "string",
            "description": "The product this order is for — echoed back from your request.",
            "enum": [
              "stars",
              "premium"
            ]
          },
          "quantity": {
            "type": [
              "integer",
              "null"
            ],
            "description": "The number of Stars ordered (when `type=stars`); `null` for Premium.\n",
            "example": 500
          },
          "months": {
            "type": [
              "integer",
              "null"
            ],
            "description": "The Premium subscription length in months ordered (when `type=premium`); `null` for Stars.\n",
            "example": null
          },
          "payment": {
            "$ref": "#/components/schemas/PaymentInstruction"
          },
          "expires_at": {
            "type": "string",
            "format": "date-time",
            "description": "After this, an unpaid order expires and is cleaned up."
          }
        }
      },
      "Order": {
        "type": "object",
        "required": [
          "order_id",
          "status",
          "type",
          "recipient_username"
        ],
        "properties": {
          "order_id": {
            "type": "string",
            "format": "uuid"
          },
          "status": {
            "$ref": "#/components/schemas/OrderStatus"
          },
          "type": {
            "$ref": "#/components/schemas/OrderType"
          },
          "recipient_username": {
            "type": "string"
          },
          "quantity": {
            "type": [
              "integer",
              "null"
            ]
          },
          "months": {
            "type": [
              "integer",
              "null"
            ]
          },
          "amount_ton": {
            "type": [
              "string",
              "null"
            ],
            "description": "The GRAM fulfilment cost (decimal string), when known. The field name `amount_ton` is frozen for wire compatibility."
          },
          "payment_tx": {
            "type": [
              "string",
              "null"
            ]
          },
          "purchase_tx": {
            "type": [
              "string",
              "null"
            ]
          },
          "failure_reason": {
            "type": [
              "string",
              "null"
            ],
            "description": "Why the order ended where it did, when not `delivered`: `underpaid` / `overpaid` / `no_memo` / `wrong_memo` (→ `failed`, funds reversed), `undeliverable` (→ `reversed`, funds reversed), or `expired`. Null otherwise. See **Reversals & delivery**."
          },
          "reversal_tx": {
            "type": [
              "string",
              "null"
            ]
          },
          "telegram_message": {
            "type": [
              "string",
              "null"
            ],
            "description": "A verbatim user-facing message from Fragment, when present."
          },
          "created_at": {
            "type": "string",
            "format": "date-time"
          },
          "updated_at": {
            "type": "string",
            "format": "date-time"
          },
          "expires_at": {
            "type": [
              "string",
              "null"
            ],
            "format": "date-time",
            "description": "Non-null only while `awaiting_payment`."
          }
        }
      },
      "RecipientCheckRequest": {
        "type": "object",
        "required": [
          "type",
          "recipient"
        ],
        "properties": {
          "type": {
            "description": "The product to check — and the product you intend to order. `stars` resolves a Stars recipient; `premium` resolves a Premium-gift recipient and uses `months`. Must match the `type` you pass to `POST /v1/orders`; a Stars check does not prove a Premium gift will be accepted (Premium has extra eligibility rules — see the endpoint description).\n",
            "allOf": [
              {
                "$ref": "#/components/schemas/OrderType"
              }
            ]
          },
          "recipient": {
            "$ref": "#/components/schemas/Recipient"
          },
          "months": {
            "type": "integer",
            "enum": [
              3,
              6,
              12
            ],
            "description": "Only meaningful for `type=premium` (defaults to 3). Must be 3, 6, or 12."
          }
        }
      },
      "RecipientCheckResult": {
        "type": "object",
        "required": [
          "resolved",
          "eligible"
        ],
        "properties": {
          "resolved": {
            "type": "boolean",
            "description": "Whether the @username was found on Telegram."
          },
          "eligible": {
            "type": "boolean",
            "description": "Whether the recipient can receive the item."
          },
          "recipient_name": {
            "type": [
              "string",
              "null"
            ],
            "description": "The resolved recipient's display name from Fragment, when available (best-effort) — useful to confirm you're sending to the right person. `null` if Fragment returned no name.\n"
          },
          "reason": {
            "type": [
              "string",
              "null"
            ],
            "description": "Permanent-rejection class, set only when `eligible` is false: `already_subscribed`, `not_found`, or `ineligible`. `null` when eligible.\n",
            "enum": [
              "already_subscribed",
              "not_found",
              "ineligible",
              null
            ]
          },
          "telegram_message": {
            "type": [
              "string",
              "null"
            ],
            "description": "Fragment's raw rejection text, propagated verbatim — set only when `eligible` is false. `null` when eligible.\n"
          },
          "indeterminate": {
            "type": "boolean",
            "default": false,
            "description": "`true` when the eligibility probe could not reach a verdict and this endpoint FAILED OPEN — `eligible` is then a permissive default, not a measurement. `false` on every real verdict (eligible or not).\n\nTreat an indeterminate response as \"unknown\", never as \"yes\": the recipient has not been checked. It is safe to proceed to `POST /v1/orders` (which runs its own authoritative check), but do not present it to a buyer as a confirmed-deliverable recipient.\n\nAlways present since 1.11.0. Clients written against an earlier version saw the field absent; absent and `false` mean the same thing.\n"
          }
        }
      },
      "QuoteBatch": {
        "type": "object",
        "required": [
          "type",
          "currency",
          "quotes",
          "usdt_per_ton",
          "quoted_at",
          "valid_until"
        ],
        "properties": {
          "type": {
            "type": "string",
            "enum": [
              "stars"
            ],
            "description": "Batch pricing is Stars-only."
          },
          "currency": {
            "$ref": "#/components/schemas/PaymentCurrency"
          },
          "quotes": {
            "type": "array",
            "description": "One entry per requested quantity (deduped, ascending).",
            "items": {
              "type": "object",
              "required": [
                "quantity",
                "amount",
                "fee"
              ],
              "properties": {
                "quantity": {
                  "type": "integer",
                  "description": "The number of Stars this entry priced.",
                  "example": 500
                },
                "amount": {
                  "type": "string",
                  "description": "The full, all-in total to pay for this quantity, as a decimal string in the top-level `currency` — identical to what `GET /v1/pricing` returns for the same quantity.\n",
                  "example": "5.757"
                },
                "fee": {
                  "description": "For `usdt_ton` only: the same processing-fee itemisation as `GET /v1/pricing` (already included in `amount`). `null` for `ton`.",
                  "oneOf": [
                    {
                      "$ref": "#/components/schemas/FeeBreakdown"
                    },
                    {
                      "type": "null"
                    }
                  ]
                }
              }
            }
          },
          "usdt_per_ton": {
            "type": "string",
            "nullable": true,
            "description": "Indicative USDT per 1 GRAM (informational; the field name `usdt_per_ton` is frozen for wire compatibility; `null` if unavailable).",
            "example": "2.85"
          },
          "quoted_at": {
            "type": "string",
            "format": "date-time"
          },
          "valid_until": {
            "type": "string",
            "format": "date-time",
            "description": "Re-quote hint — prices are locked only at order creation."
          }
        }
      },
      "Quote": {
        "type": "object",
        "required": [
          "type",
          "quantity",
          "months",
          "amount",
          "currency",
          "fee",
          "usdt_per_ton",
          "quoted_at",
          "valid_until"
        ],
        "properties": {
          "type": {
            "type": "string",
            "description": "The product this quote priced — echoed back from your request.",
            "enum": [
              "stars",
              "premium"
            ]
          },
          "quantity": {
            "type": [
              "integer",
              "null"
            ],
            "description": "The number of Stars priced (when `type=stars`); `null` for Premium.\n",
            "example": 500
          },
          "months": {
            "type": [
              "integer",
              "null"
            ],
            "description": "The Premium subscription length in months priced (when `type=premium`); `null` for Stars.\n",
            "example": null
          },
          "amount": {
            "type": "string",
            "description": "The full, all-in total to pay, as a decimal string in `currency`. Nothing else to add — send exactly this amount on-chain.\n",
            "example": "5.757"
          },
          "currency": {
            "$ref": "#/components/schemas/PaymentCurrency"
          },
          "fee": {
            "description": "For `usdt_ton` only: an itemisation of the processing fee already INCLUDED in `amount` (1% DEX swap fee + 0.5 GRAM swap gas). `null` for `ton`. It does NOT add to `amount` — `fee.total` equals `amount`.",
            "oneOf": [
              {
                "$ref": "#/components/schemas/FeeBreakdown"
              },
              {
                "type": "null"
              }
            ]
          },
          "usdt_per_ton": {
            "type": "string",
            "nullable": true,
            "description": "The current indicative USDT per 1 GRAM (the field name `usdt_per_ton` is frozen for wire compatibility) — public market data you can use to convert GRAM↔USDT in your own interface. `null` if the rate is momentarily unavailable. This is NOT the amount you pay (that is `amount`); it is informational only.\n",
            "example": "2.85"
          },
          "quoted_at": {
            "type": "string",
            "format": "date-time",
            "description": "Server timestamp (ISO 8601) when this quote was computed.",
            "example": "2026-06-21T14:03:12.000Z"
          },
          "valid_until": {
            "type": "string",
            "format": "date-time",
            "description": "A RE-QUOTE HINT (ISO 8601): the price tracks the market and is recomputed about every minute, so re-fetch after this time. It is **not** a price lock — the price is locked only when you create an order (`POST /v1/orders`), which fixes the amount for the order's payment window. Read `expires_at` on the order for that deadline — it is authoritative; do not assume a fixed duration.\n",
            "example": "2026-06-21T14:04:12.000Z"
          }
        }
      },
      "Product": {
        "type": "object",
        "required": [
          "type",
          "name",
          "parameter",
          "min",
          "max",
          "values"
        ],
        "properties": {
          "type": {
            "type": "string",
            "description": "The product type — pass this as `type` to `/v1/pricing` and `/v1/orders`.",
            "enum": [
              "stars",
              "premium"
            ]
          },
          "name": {
            "type": "string",
            "description": "Human-readable product name.",
            "example": "Telegram Stars"
          },
          "parameter": {
            "type": "string",
            "description": "The request field that carries the amount for this product — `quantity` for stars, `months` for premium.\n",
            "enum": [
              "quantity",
              "months"
            ]
          },
          "min": {
            "type": "integer",
            "description": "Smallest buyable value (inclusive).",
            "example": 50
          },
          "max": {
            "type": "integer",
            "description": "Largest buyable value (inclusive).",
            "example": 1000000
          },
          "values": {
            "type": [
              "array",
              "null"
            ],
            "items": {
              "type": "integer"
            },
            "description": "The exact allowed values when the product is a fixed set (premium → `[3, 6, 12]`); `null` when any integer in `[min, max]` is valid (stars).\n",
            "example": null
          }
        }
      },
      "Error": {
        "type": "object",
        "required": [
          "error"
        ],
        "properties": {
          "error": {
            "type": "object",
            "required": [
              "code",
              "message"
            ],
            "properties": {
              "code": {
                "type": "string",
                "description": "Stable machine error code to branch on.",
                "enum": [
                  "bad_request",
                  "unauthorized",
                  "forbidden",
                  "not_found",
                  "conflict",
                  "recipient_ineligible",
                  "rate_limited",
                  "unavailable",
                  "internal"
                ]
              },
              "message": {
                "type": "string",
                "description": "Human-readable description."
              },
              "telegram_message": {
                "type": "string",
                "description": "A verbatim user-facing message from Fragment, when present."
              }
            }
          }
        }
      }
    },
    "responses": {
      "BadRequest": {
        "description": "Malformed request.",
        "content": {
          "application/json": {
            "schema": {
              "$ref": "#/components/schemas/Error"
            }
          }
        }
      },
      "Unauthorized": {
        "description": "Missing or invalid `X-Api-Key`.",
        "content": {
          "application/json": {
            "schema": {
              "$ref": "#/components/schemas/Error"
            }
          }
        }
      },
      "NotFound": {
        "description": "No such order for this tenant.",
        "content": {
          "application/json": {
            "schema": {
              "$ref": "#/components/schemas/Error"
            }
          }
        }
      },
      "RecipientIneligible": {
        "description": "The recipient cannot receive this item. No order is created.",
        "content": {
          "application/json": {
            "schema": {
              "$ref": "#/components/schemas/Error"
            }
          }
        }
      },
      "RateLimited": {
        "description": "A rate limit was reached — the per-minute request budget, the tighter\npricing/recipient-check probe cap (60 req/min), the daily order cap, or the\nper-recipient flood guard. See **Rate limits** in the overview. The\nper-minute-budget responses also carry `RateLimit-*` + `Retry-After` headers.\n",
        "content": {
          "application/json": {
            "schema": {
              "$ref": "#/components/schemas/Error"
            },
            "example": {
              "error": {
                "code": "rate_limited",
                "message": "rate limit exceeded"
              }
            }
          }
        }
      },
      "Unavailable": {
        "description": "A required source was temporarily unavailable — either the price source,\nor recipient eligibility could not be verified right now. **Retryable**:\nreuse the same `Idempotency-Key` and try again shortly. No order is created\nand you are not charged.\n",
        "content": {
          "application/json": {
            "schema": {
              "$ref": "#/components/schemas/Error"
            }
          }
        }
      }
    }
  }
}