{
  "components": {
    "schemas": {
      "BulkBatchResponse": {
        "properties": {
          "data": {
            "description": "Result of a batch order. PARTIAL-SUCCESS: failed lines do not block other lines; money is ONLY charged for `done` lines. Does not carry LPA/QR — retrieve eSIM via GET /v1/orders/{id} by each `order_id`.",
            "properties": {
              "created_at": {
                "format": "date-time",
                "type": "string"
              },
              "external_ref": {
                "example": "batch-2026-001",
                "nullable": true,
                "type": "string"
              },
              "failed_lines": {
                "type": "integer"
              },
              "id": {
                "format": "uuid",
                "type": "string"
              },
              "items": {
                "items": {
                  "$ref": "#/components/schemas/BulkLineResult"
                },
                "type": "array"
              },
              "mode": {
                "$ref": "#/components/schemas/ModeEcho"
              },
              "status": {
                "description": "`pending` = the batch still has lines that haven't run (interrupted mid-way) — call again with the SAME `Idempotency-Key` to finish the rest.",
                "enum": [
                  "pending",
                  "completed",
                  "partial",
                  "failed"
                ],
                "type": "string"
              },
              "succeeded_lines": {
                "type": "integer"
              },
              "total_charged": {
                "$ref": "#/components/schemas/Money"
              },
              "total_lines": {
                "type": "integer"
              }
            },
            "type": "object"
          },
          "ok": {
            "enum": [
              true
            ],
            "type": "boolean"
          }
        },
        "required": [
          "ok",
          "data"
        ],
        "type": "object"
      },
      "BulkLineResult": {
        "properties": {
          "charged": {
            "allOf": [
              {
                "$ref": "#/components/schemas/Money"
              }
            ],
            "nullable": true
          },
          "error": {
            "description": "A real `domain.reason` error code (e.g. `wallet.insufficient_balance`, `esim.out_of_stock`, `order.package_not_allowed`, `wallet.velocity_exceeded`).",
            "nullable": true,
            "properties": {
              "code": {
                "type": "string"
              },
              "message": {
                "type": "string"
              }
            },
            "type": "object"
          },
          "external_ref": {
            "example": "po-1001",
            "nullable": true,
            "type": "string"
          },
          "line_no": {
            "description": "1-based, in the exact order submitted.",
            "type": "integer"
          },
          "order_id": {
            "format": "uuid",
            "nullable": true,
            "type": "string"
          },
          "package_code": {
            "type": "string"
          },
          "quantity": {
            "type": "integer"
          },
          "status": {
            "enum": [
              "pending",
              "done",
              "failed"
            ],
            "type": "string"
          }
        },
        "type": "object"
      },
      "BulkRequest": {
        "properties": {
          "external_ref": {
            "description": "Reference code for the WHOLE batch — echoed in the batch response.",
            "example": "batch-2026-001",
            "nullable": true,
            "type": "string"
          },
          "items": {
            "description": "Each element creates ONE independent order. The line-count cap is configurable (default 100) — exceeding it → `internal.limit_exceeded` (422).",
            "items": {
              "properties": {
                "external_ref": {
                  "description": "Reference code for THIS line only — echoed verbatim in the line result.",
                  "example": "po-1001",
                  "nullable": true,
                  "type": "string"
                },
                "package_code": {
                  "description": "The package code from YOUR catalog, i.e. the `public_sku`. IMMUTABLE per package (it does not change when we switch the supplier behind it), so you can safely use it as a reconciliation key. The example value here has the right FORMAT but will NOT work: fetch a real code from `GET /v1/catalog/packages`.",
                  "example": "TKE-VN-30D-X3K9F",
                  "type": "string"
                },
                "quantity": {
                  "example": 5,
                  "minimum": 1,
                  "type": "integer"
                }
              },
              "required": [
                "package_code",
                "quantity"
              ],
              "type": "object"
            },
            "minItems": 1,
            "type": "array"
          }
        },
        "required": [
          "items"
        ],
        "type": "object"
      },
      "CancelResponse": {
        "properties": {
          "data": {
            "properties": {
              "cancelled_at": {
                "format": "date-time",
                "type": "string"
              },
              "esim_id": {
                "format": "uuid",
                "type": "string"
              },
              "mode": {
                "$ref": "#/components/schemas/ModeEcho"
              },
              "refund_suggested": {
                "description": "`true` = a refund request is queued for a human to approve; the money has NOT moved yet. Do not read `refunded.amount = 0` as \"no refund\".",
                "type": "boolean"
              },
              "refunded": {
                "$ref": "#/components/schemas/Money",
                "description": "Money that actually LANDED back in your wallet during this cancellation. On live it is usually `0` — read `refund_suggested` before concluding anything."
              },
              "status": {
                "example": "cancelled",
                "type": "string"
              }
            },
            "type": "object"
          },
          "ok": {
            "enum": [
              true
            ],
            "type": "boolean"
          }
        },
        "required": [
          "ok",
          "data"
        ],
        "type": "object"
      },
      "Cancellable": {
        "description": "Whether this eSIM can still be cancelled, and if not, why — read it BEFORE calling `POST /v1/esims/{order_item_id}/cancel`. This block is ALWAYS present (never `null`): it is an answer, not optional data.",
        "properties": {
          "allowed": {
            "type": "boolean"
          },
          "disclosed_at": {
            "description": "When the code left our systems (ISO-8601 UTC) — `null` while nobody has seen it. This is the trace you can follow when a customer claims \"I never received the code, why can't it be cancelled\".",
            "format": "date-time",
            "nullable": true,
            "type": "string"
          },
          "reason": {
            "description": "Why it cannot be cancelled. `code_disclosed` = the code has been sent to or shown to someone; `wrong_status` = the eSIM is not in a cancellable state; `expired` = the plan has expired; `not_supported` = the package does not declare a cancel capability. `null` when `allowed = true`. Same vocabulary as `meta.reason` on `esim.not_cancellable`.",
            "enum": [
              "code_disclosed",
              "wrong_status",
              "expired",
              "not_supported",
              null
            ],
            "nullable": true,
            "type": "string"
          }
        },
        "type": "object"
      },
      "ChangePlanRequest": {
        "properties": {
          "package_code": {
            "description": "The package code from YOUR catalog, i.e. the `public_sku`. IMMUTABLE per package (it does not change when we switch the supplier behind it), so you can safely use it as a reconciliation key. The example value here has the right FORMAT but will NOT work: fetch a real code from `GET /v1/catalog/packages`.",
            "example": "TKE-VN-7D-P4M2Q",
            "type": "string"
          }
        },
        "required": [
          "package_code"
        ],
        "type": "object"
      },
      "ChangePlanResponse": {
        "properties": {
          "data": {
            "description": "Result of changing the eSIM plan.",
            "properties": {
              "charged": {
                "$ref": "#/components/schemas/Money"
              },
              "current_plan": {
                "properties": {
                  "code": {
                    "type": "string"
                  },
                  "expires_at": {
                    "format": "date-time",
                    "nullable": true,
                    "type": "string"
                  },
                  "started_at": {
                    "format": "date-time",
                    "nullable": true,
                    "type": "string"
                  }
                },
                "type": "object"
              },
              "esim_id": {
                "format": "uuid",
                "type": "string"
              },
              "mode": {
                "$ref": "#/components/schemas/ModeEcho"
              },
              "order_id": {
                "format": "uuid",
                "type": "string"
              },
              "previous_plan": {
                "nullable": true,
                "properties": {
                  "cancelled_at": {
                    "format": "date-time",
                    "nullable": true,
                    "type": "string"
                  },
                  "code": {
                    "type": "string"
                  },
                  "remaining_bytes_forfeited": {
                    "format": "int64",
                    "nullable": true,
                    "type": "integer"
                  }
                },
                "type": "object"
              },
              "warning": {
                "type": "string"
              }
            },
            "type": "object"
          },
          "ok": {
            "enum": [
              true
            ],
            "type": "boolean"
          }
        },
        "required": [
          "ok",
          "data"
        ],
        "type": "object"
      },
      "CreateOrderRequest": {
        "properties": {
          "external_ref": {
            "description": "Your own reference code — echoed in the response and in webhooks.",
            "example": "po-1001",
            "nullable": true,
            "type": "string"
          },
          "include": {
            "description": "Ask for extra blocks in the response: `[\"lpa\"]` or `[\"lpa\",\"qr\"]`. Omitted ⇒ `items[].install` is `null` and EVERY eSIM in the order keeps its right to be cancelled.\n\n**RETURNING THE CODE IS DISCLOSING IT**: asking for `lpa` here means accepting that the cancellation window closes at the moment the order is placed.",
            "example": [
              "lpa"
            ],
            "items": {
              "enum": [
                "lpa",
                "qr"
              ],
              "type": "string"
            },
            "nullable": true,
            "type": "array"
          },
          "invoice_buyer": {
            "$ref": "#/components/schemas/MemberInvoiceBuyer"
          },
          "items": {
            "description": "List of packages and quantities to buy (maximum 50 eSIMs).",
            "items": {
              "properties": {
                "package_code": {
                  "description": "The package code from YOUR catalog, i.e. the `public_sku`. IMMUTABLE per package (it does not change when we switch the supplier behind it), so you can safely use it as a reconciliation key. The example value here has the right FORMAT but will NOT work: fetch a real code from `GET /v1/catalog/packages`.",
                  "example": "TKE-VN-30D-X3K9F",
                  "type": "string"
                },
                "quantity": {
                  "description": "Quantity of eSIMs to buy for this package (≥ 1).",
                  "example": 2,
                  "type": "integer"
                }
              },
              "required": [
                "package_code",
                "quantity"
              ],
              "type": "object"
            },
            "type": "array"
          }
        },
        "required": [
          "items"
        ],
        "type": "object"
      },
      "CreateWebhookResponse": {
        "properties": {
          "data": {
            "properties": {
              "active": {
                "description": "`false` = the endpoint has stopped accepting new events. That only happens when your organization pauses it from the dashboard, or when the API key is revoked (the cascade shuts the outbound channel down in the same transaction). A `PUT /v1/webhooks/{id}` that passes CHECK_HEALTH turns it back on.",
                "type": "boolean"
              },
              "created_at": {
                "format": "date-time",
                "type": "string"
              },
              "events": {
                "description": "Empty = receive everything.",
                "items": {
                  "enum": [
                    "order.completed",
                    "order.failed",
                    "esim.delivered",
                    "esim.activated",
                    "esim.usage_threshold",
                    "esim.expiring",
                    "wallet.low_balance",
                    "catalog.updated"
                  ],
                  "type": "string"
                },
                "type": "array"
              },
              "health_check": {
                "enum": [
                  "passed"
                ],
                "type": "string"
              },
              "id": {
                "description": "The endpoint's ID — what you put into `/v1/webhooks/{id}`. A NEW one is minted on every create: deleting and re-creating gives you a different endpoint, not the old one brought back to life.",
                "format": "uuid",
                "type": "string"
              },
              "last_delivery_at": {
                "description": "The last time we DELIVERED through this endpoint. `null` = never delivered — a different thing from \"delivery failed\", and the first thing to check when a partner reports receiving no webhooks.",
                "format": "date-time",
                "nullable": true,
                "type": "string"
              },
              "mode": {
                "$ref": "#/components/schemas/ModeEcho"
              },
              "secret_last4": {
                "description": "The last four characters of the webhook secret — enough for you to check \"is the secret I hold the one the system signs with?\" without any endpoint handing the secret back. The full secret is shown ONCE, at creation.",
                "type": "string"
              },
              "url": {
                "type": "string"
              },
              "webhook_secret": {
                "description": "Shown ONLY ONCE, at creation time (prefix `whsec_`).",
                "type": "string"
              }
            },
            "type": "object"
          },
          "ok": {
            "enum": [
              true
            ],
            "type": "boolean"
          }
        },
        "required": [
          "ok",
          "data"
        ],
        "type": "object"
      },
      "DeleteWebhookResponse": {
        "properties": {
          "data": {
            "properties": {
              "deleted_at": {
                "format": "date-time",
                "type": "string"
              },
              "id": {
                "format": "uuid",
                "type": "string"
              },
              "mode": {
                "$ref": "#/components/schemas/ModeEcho"
              }
            },
            "type": "object"
          },
          "ok": {
            "enum": [
              true
            ],
            "type": "boolean"
          }
        },
        "required": [
          "ok",
          "data"
        ],
        "type": "object"
      },
      "Delivery": {
        "properties": {
          "attempts": {
            "type": "integer"
          },
          "created_at": {
            "format": "date-time",
            "type": "string"
          },
          "event_id": {
            "description": "The EVENT identifier. A replay creates a NEW delivery carrying the SAME `event_id` — keep deduplicating on `event_id`, not on the delivery `id`.",
            "format": "uuid",
            "type": "string"
          },
          "event_type": {
            "type": "string"
          },
          "id": {
            "format": "uuid",
            "type": "string"
          },
          "last_attempt_at": {
            "format": "date-time",
            "nullable": true,
            "type": "string"
          },
          "next_retry_at": {
            "format": "date-time",
            "nullable": true,
            "type": "string"
          },
          "response_code": {
            "nullable": true,
            "type": "integer"
          },
          "status": {
            "enum": [
              "pending",
              "delivered",
              "failed"
            ],
            "type": "string"
          },
          "webhook_id": {
            "format": "uuid",
            "type": "string"
          }
        },
        "type": "object"
      },
      "DeliveryListResponse": {
        "properties": {
          "data": {
            "properties": {
              "items": {
                "items": {
                  "$ref": "#/components/schemas/Delivery"
                },
                "type": "array"
              },
              "mode": {
                "$ref": "#/components/schemas/ModeEcho"
              },
              "next_cursor": {
                "description": "Pass it back VERBATIM to fetch the next page. `null` = NO MORE data, and that is the ONLY stop signal: a page shorter than `page_size` may still have pages after it.",
                "nullable": true,
                "type": "string"
              },
              "page_size": {
                "description": "The MAXIMUM number of records the server used for this page (already clamped to the cap), not the actual number of elements in `items`.",
                "nullable": true,
                "type": "integer"
              },
              "total": {
                "description": "How many rows match your filter. `null` when counting is too expensive — do not use it to tell whether you have reached the end; `next_cursor` is that signal.",
                "format": "int64",
                "nullable": true,
                "type": "integer"
              },
              "warnings": {
                "description": "WHY the list looks the way it does. Empty = nothing unusual.\n\n`code` is a CLOSED set: `filtered_by_key_package_filters` (your key's catalog restrictions), `filtered_by_provider_scope` (your organization's provider scope), `no_price_for_org` (your organization has no price yet), `price_filter_removed_rows` (the price-range filter), `provider_disabled` (no supplier can deliver it any more), `capability_missing` (the package has not declared its post-sale capabilities). `count` is the number of rows involved, `null` when it cannot be expressed as a number.",
                "example": [],
                "items": {
                  "$ref": "#/components/schemas/ListWarning"
                },
                "type": "array"
              }
            },
            "type": "object"
          },
          "ok": {
            "enum": [
              true
            ],
            "type": "boolean"
          }
        },
        "required": [
          "ok",
          "data"
        ],
        "type": "object"
      },
      "DeliveryReplayResponse": {
        "properties": {
          "data": {
            "properties": {
              "attempts": {
                "type": "integer"
              },
              "created_at": {
                "format": "date-time",
                "type": "string"
              },
              "event_id": {
                "description": "The EVENT identifier. A replay creates a NEW delivery carrying the SAME `event_id` — keep deduplicating on `event_id`, not on the delivery `id`.",
                "format": "uuid",
                "type": "string"
              },
              "event_type": {
                "type": "string"
              },
              "id": {
                "format": "uuid",
                "type": "string"
              },
              "last_attempt_at": {
                "format": "date-time",
                "nullable": true,
                "type": "string"
              },
              "mode": {
                "$ref": "#/components/schemas/ModeEcho"
              },
              "next_retry_at": {
                "format": "date-time",
                "nullable": true,
                "type": "string"
              },
              "response_code": {
                "nullable": true,
                "type": "integer"
              },
              "status": {
                "enum": [
                  "pending",
                  "delivered",
                  "failed"
                ],
                "type": "string"
              },
              "webhook_id": {
                "format": "uuid",
                "type": "string"
              }
            },
            "type": "object"
          },
          "ok": {
            "enum": [
              true
            ],
            "type": "boolean"
          }
        },
        "required": [
          "ok",
          "data"
        ],
        "type": "object"
      },
      "ErrorEnvelope": {
        "properties": {
          "error": {
            "properties": {
              "code": {
                "description": "A `domain.reason` code (see the error code table).",
                "type": "string"
              },
              "field": {
                "nullable": true,
                "type": "string"
              },
              "message": {
                "type": "string"
              },
              "meta": {
                "nullable": true,
                "type": "object"
              },
              "request_id": {
                "type": "string"
              }
            },
            "required": [
              "code",
              "message",
              "request_id"
            ],
            "type": "object"
          },
          "ok": {
            "enum": [
              false
            ],
            "type": "boolean"
          }
        },
        "required": [
          "ok",
          "error"
        ],
        "type": "object"
      },
      "Esim": {
        "properties": {
          "data": {
            "properties": {
              "cancellable": {
                "$ref": "#/components/schemas/Cancellable"
              },
              "external_ref": {
                "description": "Your own order reference (the `external_ref` you sent when ordering) — matches the eSIM back to your order. `null` when the order was placed without one.",
                "nullable": true,
                "type": "string"
              },
              "iccid": {
                "example": "8984012345678901234",
                "nullable": true,
                "type": "string"
              },
              "id": {
                "description": "The eSIM id — used in every `/v1/esims/{id}/…` path (also the `order_item_id` in webhooks). If you only have the ICCID, call `GET /v1/esims?iccid=…` to get the `id`.",
                "format": "uuid",
                "type": "string"
              },
              "install": {
                "$ref": "#/components/schemas/Install"
              },
              "mode": {
                "$ref": "#/components/schemas/ModeEcho"
              },
              "msisdn": {
                "nullable": true,
                "type": "string"
              },
              "order_id": {
                "format": "uuid",
                "type": "string"
              },
              "package_code": {
                "description": "The package CODE from the catalog — the key to map this eSIM back to your catalog. `null` only when the source package has been removed from the catalog.",
                "nullable": true,
                "type": "string"
              },
              "package_name": {
                "description": "The display NAME — a human-readable label that can change. Do NOT use it as a reconciliation key.",
                "type": "string"
              },
              "plan": {
                "$ref": "#/components/schemas/PlanView"
              },
              "smdp_status": {
                "example": "released",
                "nullable": true,
                "type": "string"
              },
              "status": {
                "example": "delivered",
                "type": "string"
              },
              "updated_at": {
                "format": "date-time",
                "type": "string"
              },
              "usage": {
                "$ref": "#/components/schemas/Usage"
              }
            },
            "type": "object"
          },
          "ok": {
            "enum": [
              true
            ],
            "type": "boolean"
          }
        },
        "required": [
          "ok",
          "data"
        ],
        "type": "object"
      },
      "EsimItem": {
        "properties": {
          "cancellable": {
            "$ref": "#/components/schemas/Cancellable"
          },
          "external_ref": {
            "description": "Your own order reference (the `external_ref` you sent when ordering) — matches the eSIM back to your order. `null` when the order was placed without one.",
            "nullable": true,
            "type": "string"
          },
          "iccid": {
            "example": "8984012345678901234",
            "nullable": true,
            "type": "string"
          },
          "id": {
            "description": "The eSIM id — used in every `/v1/esims/{id}/…` path (also the `order_item_id` in webhooks). If you only have the ICCID, call `GET /v1/esims?iccid=…` to get the `id`.",
            "format": "uuid",
            "type": "string"
          },
          "install": {
            "$ref": "#/components/schemas/Install"
          },
          "msisdn": {
            "nullable": true,
            "type": "string"
          },
          "order_id": {
            "format": "uuid",
            "type": "string"
          },
          "package_code": {
            "description": "The package CODE from the catalog — the key to map this eSIM back to your catalog. `null` only when the source package has been removed from the catalog.",
            "nullable": true,
            "type": "string"
          },
          "package_name": {
            "description": "The display NAME — a human-readable label that can change. Do NOT use it as a reconciliation key.",
            "type": "string"
          },
          "plan": {
            "$ref": "#/components/schemas/PlanView"
          },
          "smdp_status": {
            "example": "released",
            "nullable": true,
            "type": "string"
          },
          "status": {
            "example": "delivered",
            "type": "string"
          },
          "updated_at": {
            "format": "date-time",
            "type": "string"
          },
          "usage": {
            "$ref": "#/components/schemas/Usage"
          }
        },
        "type": "object"
      },
      "EsimListResponse": {
        "properties": {
          "data": {
            "properties": {
              "items": {
                "items": {
                  "$ref": "#/components/schemas/EsimItem"
                },
                "type": "array"
              },
              "mode": {
                "$ref": "#/components/schemas/ModeEcho"
              },
              "next_cursor": {
                "description": "Pass it back VERBATIM to fetch the next page. `null` = NO MORE data, and that is the ONLY stop signal: a page shorter than `page_size` may still have pages after it.",
                "nullable": true,
                "type": "string"
              },
              "page_size": {
                "description": "The MAXIMUM number of records the server used for this page (already clamped to the cap), not the actual number of elements in `items`.",
                "nullable": true,
                "type": "integer"
              },
              "total": {
                "description": "How many rows match your filter. `null` when counting is too expensive — do not use it to tell whether you have reached the end; `next_cursor` is that signal.",
                "format": "int64",
                "nullable": true,
                "type": "integer"
              },
              "warnings": {
                "description": "WHY the list looks the way it does. Empty = nothing unusual.\n\n`code` is a CLOSED set: `filtered_by_key_package_filters` (your key's catalog restrictions), `filtered_by_provider_scope` (your organization's provider scope), `no_price_for_org` (your organization has no price yet), `price_filter_removed_rows` (the price-range filter), `provider_disabled` (no supplier can deliver it any more), `capability_missing` (the package has not declared its post-sale capabilities). `count` is the number of rows involved, `null` when it cannot be expressed as a number.",
                "example": [],
                "items": {
                  "$ref": "#/components/schemas/ListWarning"
                },
                "type": "array"
              }
            },
            "type": "object"
          },
          "ok": {
            "enum": [
              true
            ],
            "type": "boolean"
          }
        },
        "required": [
          "ok",
          "data"
        ],
        "type": "object"
      },
      "EsimUsage": {
        "properties": {
          "data": {
            "properties": {
              "esim_id": {
                "format": "uuid",
                "type": "string"
              },
              "mode": {
                "$ref": "#/components/schemas/ModeEcho"
              },
              "usage": {
                "$ref": "#/components/schemas/Usage"
              }
            },
            "type": "object"
          },
          "ok": {
            "enum": [
              true
            ],
            "type": "boolean"
          }
        },
        "required": [
          "ok",
          "data"
        ],
        "type": "object"
      },
      "Install": {
        "description": "Everything needed to INSTALL this eSIM. `null` = you did NOT ASK for it (no `include=lpa`) — which is not the same as \"this eSIM has no code yet\"; in that case the block is present with `lpa: null`.\n\nA response carrying this block always has `Cache-Control: no-store`.",
        "nullable": true,
        "properties": {
          "android_url": {
            "nullable": true,
            "type": "string"
          },
          "apn": {
            "description": "The access point name (APN) to enter when the device does not pick up the configuration automatically. ALWAYS `null` in the current release — `null` means \"unknown\", not \"not needed\".",
            "nullable": true,
            "type": "string"
          },
          "ios_url": {
            "nullable": true,
            "type": "string"
          },
          "lpa": {
            "description": "The eSIM activation code (a sellable secret). This string is all you need to render the QR code on your side.",
            "example": "LPA:1$smdp.example.com$MATCH-ABC123",
            "nullable": true,
            "type": "string"
          },
          "matching_id": {
            "example": "MATCH-ABC123",
            "nullable": true,
            "type": "string"
          },
          "qr_code_base64": {
            "description": "PNG data-URI of the QR code. Only present when you ask for `include=qr` AND call a DETAIL endpoint — lists NEVER carry it (≈11 KB per row, so a 100-row page is ≈1.1 MB); render the QR from `lpa` instead.",
            "nullable": true,
            "type": "string"
          },
          "smdp_address": {
            "example": "smdp.example.com",
            "nullable": true,
            "type": "string"
          }
        },
        "type": "object"
      },
      "ListWarning": {
        "description": "WHY the list looks the way it does. Empty = nothing unusual.\n\n`code` is a CLOSED set: `filtered_by_key_package_filters` (your key's catalog restrictions), `filtered_by_provider_scope` (your organization's provider scope), `no_price_for_org` (your organization has no price yet), `price_filter_removed_rows` (the price-range filter), `provider_disabled` (no supplier can deliver it any more), `capability_missing` (the package has not declared its post-sale capabilities). `count` is the number of rows involved, `null` when it cannot be expressed as a number.",
        "properties": {
          "code": {
            "enum": [
              "filtered_by_key_package_filters",
              "filtered_by_provider_scope",
              "no_price_for_org",
              "price_filter_removed_rows",
              "provider_disabled",
              "capability_missing"
            ],
            "example": "no_price_for_org",
            "type": "string"
          },
          "count": {
            "example": 12,
            "format": "int64",
            "nullable": true,
            "type": "integer"
          },
          "message": {
            "example": "12 gói bị lược vì tổ chức bạn chưa có giá.",
            "type": "string"
          }
        },
        "type": "object"
      },
      "MemberInvoiceBuyer": {
        "description": "Member keys only. Omitted data uses the consumer flow without an organization or Vietnamese legal entity. Supplied data follows the existing personal validator: full_name, a 12-digit personal tax_code, and address/email under checkout rules. Business invoices are requested after purchase. Test mode only validates the snapshot and never issues MISA invoices.",
        "nullable": true,
        "properties": {
          "address": {
            "nullable": true,
            "type": "string"
          },
          "email": {
            "nullable": true,
            "type": "string"
          },
          "full_name": {
            "nullable": true,
            "type": "string"
          },
          "legal_name": {
            "nullable": true,
            "type": "string"
          },
          "tax_code": {
            "nullable": true,
            "type": "string"
          }
        },
        "type": "object"
      },
      "MemberRefundRequest": {
        "properties": {
          "decided_at": {
            "format": "date-time",
            "nullable": true,
            "type": "string"
          },
          "decision_note": {
            "nullable": true,
            "type": "string"
          },
          "id": {
            "format": "uuid",
            "type": "string"
          },
          "order_id": {
            "format": "uuid",
            "type": "string"
          },
          "order_item_id": {
            "format": "uuid",
            "type": "string"
          },
          "reason": {
            "type": "string"
          },
          "refunded": {
            "allOf": [
              {
                "$ref": "#/components/schemas/Money"
              }
            ],
            "nullable": true
          },
          "requested_at": {
            "format": "date-time",
            "type": "string"
          },
          "status": {
            "enum": [
              "pending",
              "approved",
              "rejected"
            ],
            "type": "string"
          },
          "suggested": {
            "$ref": "#/components/schemas/Money"
          }
        },
        "type": "object"
      },
      "MemberRefundRequestInput": {
        "properties": {
          "reason": {
            "description": "Refund reason, 5 to 1000 characters after trimming leading and trailing whitespace.",
            "maxLength": 1000,
            "minLength": 5,
            "type": "string"
          }
        },
        "required": [
          "reason"
        ],
        "type": "object"
      },
      "MemberRefundRequestListResponse": {
        "properties": {
          "data": {
            "properties": {
              "has_open_request": {
                "type": "boolean"
              },
              "items": {
                "items": {
                  "$ref": "#/components/schemas/MemberRefundRequest"
                },
                "type": "array"
              },
              "mode": {
                "$ref": "#/components/schemas/ModeEcho"
              },
              "next_cursor": {
                "description": "Pass it back VERBATIM to fetch the next page. `null` = NO MORE data, and that is the ONLY stop signal: a page shorter than `page_size` may still have pages after it.",
                "nullable": true,
                "type": "string"
              },
              "page_size": {
                "description": "The MAXIMUM number of records the server used for this page (already clamped to the cap), not the actual number of elements in `items`.",
                "nullable": true,
                "type": "integer"
              },
              "total": {
                "description": "How many rows match your filter. `null` when counting is too expensive — do not use it to tell whether you have reached the end; `next_cursor` is that signal.",
                "format": "int64",
                "nullable": true,
                "type": "integer"
              },
              "warnings": {
                "description": "WHY the list looks the way it does. Empty = nothing unusual.\n\n`code` is a CLOSED set: `filtered_by_key_package_filters` (your key's catalog restrictions), `filtered_by_provider_scope` (your organization's provider scope), `no_price_for_org` (your organization has no price yet), `price_filter_removed_rows` (the price-range filter), `provider_disabled` (no supplier can deliver it any more), `capability_missing` (the package has not declared its post-sale capabilities). `count` is the number of rows involved, `null` when it cannot be expressed as a number.",
                "example": [],
                "items": {
                  "$ref": "#/components/schemas/ListWarning"
                },
                "type": "array"
              }
            },
            "type": "object"
          },
          "ok": {
            "enum": [
              true
            ],
            "type": "boolean"
          }
        },
        "required": [
          "ok",
          "data"
        ],
        "type": "object"
      },
      "MemberRefundRequestResponse": {
        "properties": {
          "data": {
            "properties": {
              "decided_at": {
                "format": "date-time",
                "nullable": true,
                "type": "string"
              },
              "decision_note": {
                "nullable": true,
                "type": "string"
              },
              "id": {
                "format": "uuid",
                "type": "string"
              },
              "mode": {
                "$ref": "#/components/schemas/ModeEcho"
              },
              "order_id": {
                "format": "uuid",
                "type": "string"
              },
              "order_item_id": {
                "format": "uuid",
                "type": "string"
              },
              "reason": {
                "type": "string"
              },
              "refunded": {
                "allOf": [
                  {
                    "$ref": "#/components/schemas/Money"
                  }
                ],
                "nullable": true
              },
              "requested_at": {
                "format": "date-time",
                "type": "string"
              },
              "status": {
                "enum": [
                  "pending",
                  "approved",
                  "rejected"
                ],
                "type": "string"
              },
              "suggested": {
                "$ref": "#/components/schemas/Money"
              }
            },
            "type": "object"
          },
          "ok": {
            "enum": [
              true
            ],
            "type": "boolean"
          }
        },
        "required": [
          "ok",
          "data"
        ],
        "type": "object"
      },
      "ModeEcho": {
        "description": "The calling key environment, matching the X-TKE-Mode header. Organization keys use tke_live_/tke_test_; member keys use tke_member_live_/tke_member_test_. Live and testing hosts enforce the key mode.",
        "enum": [
          "test",
          "live"
        ],
        "example": "live",
        "type": "string"
      },
      "Money": {
        "description": "An amount of money. ONE shape everywhere on `/v1` — no field ever switches between an object and a bare number.",
        "properties": {
          "amount": {
            "description": "Integer, in the smallest unit of `currency` (VND has no minor unit).",
            "example": 273000,
            "format": "int64",
            "type": "integer"
          },
          "currency": {
            "example": "VND",
            "type": "string"
          }
        },
        "type": "object"
      },
      "OkEnvelope": {
        "properties": {
          "data": {
            "description": "Payload varies by endpoint.",
            "type": "object"
          },
          "ok": {
            "enum": [
              true
            ],
            "type": "boolean"
          }
        },
        "required": [
          "ok",
          "data"
        ],
        "type": "object"
      },
      "OrderItem": {
        "properties": {
          "cancellable": {
            "$ref": "#/components/schemas/Cancellable"
          },
          "external_ref": {
            "description": "Your own order reference (the `external_ref` you sent when ordering) — matches the eSIM back to your order. `null` when the order was placed without one.",
            "nullable": true,
            "type": "string"
          },
          "iccid": {
            "example": "8984012345678901234",
            "nullable": true,
            "type": "string"
          },
          "id": {
            "description": "The eSIM id — used in every `/v1/esims/{id}/…` path (also the `order_item_id` in webhooks). If you only have the ICCID, call `GET /v1/esims?iccid=…` to get the `id`.",
            "format": "uuid",
            "type": "string"
          },
          "install": {
            "$ref": "#/components/schemas/Install"
          },
          "msisdn": {
            "nullable": true,
            "type": "string"
          },
          "order_id": {
            "format": "uuid",
            "type": "string"
          },
          "package_code": {
            "description": "The package CODE from the catalog — the key to map this eSIM back to your catalog. `null` only when the source package has been removed from the catalog.",
            "nullable": true,
            "type": "string"
          },
          "package_name": {
            "description": "The display NAME — a human-readable label that can change. Do NOT use it as a reconciliation key.",
            "type": "string"
          },
          "plan": {
            "$ref": "#/components/schemas/PlanView"
          },
          "smdp_status": {
            "example": "released",
            "nullable": true,
            "type": "string"
          },
          "status": {
            "example": "delivered",
            "type": "string"
          },
          "updated_at": {
            "format": "date-time",
            "type": "string"
          },
          "usage": {
            "$ref": "#/components/schemas/Usage"
          }
        },
        "type": "object"
      },
      "OrderListItem": {
        "properties": {
          "created_at": {
            "format": "date-time",
            "type": "string"
          },
          "external_ref": {
            "example": "po-1001",
            "nullable": true,
            "type": "string"
          },
          "fulfillment_status": {
            "enum": [
              "fulfilled",
              "partially_fulfilled",
              "unfulfilled"
            ],
            "type": "string"
          },
          "id": {
            "format": "uuid",
            "type": "string"
          },
          "item_count": {
            "format": "int64",
            "type": "integer"
          },
          "net": {
            "$ref": "#/components/schemas/Money"
          },
          "public_ref": {
            "example": "TKE-DH-2A4C7Q",
            "nullable": true,
            "type": "string"
          },
          "refunded": {
            "$ref": "#/components/schemas/Money"
          },
          "status": {
            "type": "string"
          },
          "total": {
            "$ref": "#/components/schemas/Money"
          },
          "updated_at": {
            "format": "date-time",
            "type": "string"
          }
        },
        "type": "object"
      },
      "OrderListResponse": {
        "properties": {
          "data": {
            "properties": {
              "items": {
                "items": {
                  "$ref": "#/components/schemas/OrderListItem"
                },
                "type": "array"
              },
              "mode": {
                "$ref": "#/components/schemas/ModeEcho"
              },
              "next_cursor": {
                "description": "Pass it back VERBATIM to fetch the next page. `null` = NO MORE data, and that is the ONLY stop signal: a page shorter than `page_size` may still have pages after it.",
                "nullable": true,
                "type": "string"
              },
              "page_size": {
                "description": "The MAXIMUM number of records the server used for this page (already clamped to the cap), not the actual number of elements in `items`.",
                "nullable": true,
                "type": "integer"
              },
              "total": {
                "description": "How many rows match your filter. `null` when counting is too expensive — do not use it to tell whether you have reached the end; `next_cursor` is that signal.",
                "format": "int64",
                "nullable": true,
                "type": "integer"
              },
              "warnings": {
                "description": "WHY the list looks the way it does. Empty = nothing unusual.\n\n`code` is a CLOSED set: `filtered_by_key_package_filters` (your key's catalog restrictions), `filtered_by_provider_scope` (your organization's provider scope), `no_price_for_org` (your organization has no price yet), `price_filter_removed_rows` (the price-range filter), `provider_disabled` (no supplier can deliver it any more), `capability_missing` (the package has not declared its post-sale capabilities). `count` is the number of rows involved, `null` when it cannot be expressed as a number.",
                "example": [],
                "items": {
                  "$ref": "#/components/schemas/ListWarning"
                },
                "type": "array"
              }
            },
            "type": "object"
          },
          "ok": {
            "enum": [
              true
            ],
            "type": "boolean"
          }
        },
        "required": [
          "ok",
          "data"
        ],
        "type": "object"
      },
      "OrderResponse": {
        "properties": {
          "data": {
            "description": "Partner order.",
            "properties": {
              "created_at": {
                "format": "date-time",
                "type": "string"
              },
              "external_ref": {
                "example": "po-1001",
                "nullable": true,
                "type": "string"
              },
              "fulfillment_status": {
                "description": "How much was actually delivered — a SEPARATE axis from `status`. An order of 3 eSIMs where 2 deliver and 1 fails still has `status=completed`, but `fulfillment_status=partially_fulfilled` and `refunded_amount > 0`.",
                "enum": [
                  "fulfilled",
                  "partially_fulfilled",
                  "unfulfilled"
                ],
                "type": "string"
              },
              "id": {
                "description": "The order ID (UUID).",
                "format": "uuid",
                "type": "string"
              },
              "idempotent_replay": {
                "description": "true if this request was an idempotent replay of an earlier success.",
                "nullable": true,
                "type": "boolean"
              },
              "invoice_no": {
                "description": "Invoice number once issued.",
                "nullable": true,
                "type": "string"
              },
              "invoice_status": {
                "description": "The order's VAT invoice status. `none` = not issued yet.",
                "enum": [
                  "none",
                  "pending",
                  "issuing",
                  "issued",
                  "failed",
                  "cancelled"
                ],
                "type": "string"
              },
              "items": {
                "items": {
                  "$ref": "#/components/schemas/OrderItem"
                },
                "type": "array"
              },
              "mode": {
                "$ref": "#/components/schemas/ModeEcho"
              },
              "money_settled": {
                "description": "Whether the order's money has settled (no refund still in progress). `false` ⇒ an invoice cannot be issued yet.",
                "type": "boolean"
              },
              "net": {
                "$ref": "#/components/schemas/Money"
              },
              "public_ref": {
                "description": "A short, human-readable order code — use it when opening a support ticket.",
                "example": "TKE-DH-2A4C7Q",
                "nullable": true,
                "type": "string"
              },
              "refunded": {
                "$ref": "#/components/schemas/Money"
              },
              "status": {
                "enum": [
                  "completed",
                  "pending_provider",
                  "provider_failed",
                  "cancelled"
                ],
                "type": "string"
              },
              "total": {
                "$ref": "#/components/schemas/Money"
              }
            },
            "type": "object"
          },
          "ok": {
            "enum": [
              true
            ],
            "type": "boolean"
          }
        },
        "required": [
          "ok",
          "data"
        ],
        "type": "object"
      },
      "Package": {
        "description": "**`null` means WE DO NOT HAVE THE INFORMATION — not \"no\" and not \"yes\".** Do not infer a commitment from an empty field. Only `true`/`false` values and entries PRESENT in `features.apps[]` are things the supplier has actually declared.",
        "properties": {
          "capabilities": {
            "$ref": "#/components/schemas/PackageCapabilities"
          },
          "code": {
            "description": "The package code from YOUR catalog, i.e. the `public_sku`. IMMUTABLE per package (it does not change when we switch the supplier behind it), so you can safely use it as a reconciliation key. The example value here has the right FORMAT but will NOT work: fetch a real code from `GET /v1/catalog/packages`.",
            "example": "TKE-VN-30D-X3K9F",
            "type": "string"
          },
          "coverage": {
            "description": "Where the package works.",
            "properties": {
              "countries": {
                "description": "ISO-3166 alpha-2 codes. Empty for global packages (we do not enumerate 200 countries) and for packages with no declared coverage.",
                "example": [
                  "CN"
                ],
                "items": {
                  "type": "string"
                },
                "type": "array"
              },
              "country_count": {
                "description": "How many countries the package covers — so you can sort or filter on \"multi-country packages\" without fetching everything and counting the array yourself.",
                "example": 1,
                "type": "integer"
              },
              "group": {
                "description": "Whether the package covers a list of countries (`country`), a region (`region`), or the whole world (`global`). OPEN ENUM: clients MUST ignore unknown values.",
                "example": "country",
                "type": "string"
              },
              "region": {
                "description": "ALWAYS `null` today: regions are attached to COUNTRIES, not to packages, so any value here would be an inference. Filtering by region still works through the `?region=` parameter.",
                "nullable": true,
                "type": "string"
              }
            },
            "type": "object"
          },
          "data": {
            "description": "The package's data allowance. EVERY figure is in **BYTES** (not MB/GB — it avoids rounding twice).",
            "properties": {
              "daily_bytes": {
                "description": "PER-DAY data allowance, in bytes. `null` for `fixed`/`unlimited` packages.",
                "example": 1073741824,
                "format": "int64",
                "nullable": true,
                "type": "integer"
              },
              "label": {
                "description": "A human-readable label, generated by the same function every one of our screens uses.",
                "example": "1GB/ngày",
                "type": "string"
              },
              "model": {
                "description": "How the allowance is counted: `daily` (per-day allowance) | `fixed` (total for the whole period) | `unlimited`. OPEN ENUM — clients MUST ignore unknown values.",
                "enum": [
                  "daily",
                  "fixed",
                  "unlimited"
                ],
                "example": "daily",
                "type": "string"
              },
              "reset": {
                "description": "When the daily allowance resets. A per-day package whose reset point is unknown costs a customer who buys at 22:00 almost the whole first day — this is sales information, not a technical detail. `null` = not declared.",
                "nullable": true,
                "properties": {
                  "at": {
                    "description": "Reset time in the local time of `timezone` (`00:00`, `00:00:01`).",
                    "example": "00:00",
                    "nullable": true,
                    "type": "string"
                  },
                  "note": {
                    "nullable": true,
                    "type": "string"
                  },
                  "timezone": {
                    "description": "Time zone of the reset point (`UTC+8`, `UTC+7`).",
                    "example": "UTC+8",
                    "nullable": true,
                    "type": "string"
                  }
                },
                "type": "object"
              },
              "total_bytes": {
                "description": "Total allowance for the whole period, in bytes. `null` for `daily`/`unlimited` packages: a per-day allowance times the number of days is NOT a figure the supplier commits to.",
                "example": null,
                "format": "int64",
                "nullable": true,
                "type": "integer"
              }
            },
            "type": "object"
          },
          "description": {
            "description": "The sales description of the package. `null` when nothing has been written yet.",
            "nullable": true,
            "type": "string"
          },
          "description_en": {
            "description": "English description. `null` when not provided yet.",
            "nullable": true,
            "type": "string"
          },
          "features": {
            "description": "What the package can and cannot do. A TYPED object rather than an array of codes: `[]` cannot tell \"no hotspot\" apart from \"nobody declared it\", whereas `false` and `null` can.",
            "properties": {
              "apps": {
                "description": "Per-OPERATING-SYSTEM app status.\n\n**An app NOT PRESENT in this array means WE HAVE NO INFORMATION, NOT \"it works\".** Only what is listed is something the supplier commits to; today only TikTok is declared.\n\nThere is no combined field, and that is deliberate: measured on the real catalog, 2,465 packages run TikTok on iPhone but NOT on Android. A single combined flag flattens them to \"no\", and you would then be wrong about nearly a third of the catalog.",
                "items": {
                  "properties": {
                    "android": {
                      "description": "`supported` (the supplier declares it works) | `blocked` (declares it does not) | `unknown` (the declaration does not cover this OS). OPEN ENUM.",
                      "enum": [
                        "supported",
                        "blocked",
                        "unknown"
                      ],
                      "example": "blocked",
                      "type": "string"
                    },
                    "app": {
                      "description": "App code, lowercase (e.g. `tiktok`).",
                      "example": "tiktok",
                      "type": "string"
                    },
                    "ios": {
                      "description": "`supported` (the supplier declares it works) | `blocked` (declares it does not) | `unknown` (the declaration does not cover this OS). OPEN ENUM.",
                      "enum": [
                        "supported",
                        "blocked",
                        "unknown"
                      ],
                      "example": "supported",
                      "type": "string"
                    },
                    "note": {
                      "nullable": true,
                      "type": "string"
                    }
                  },
                  "type": "object"
                },
                "type": "array"
              },
              "hotspot": {
                "description": "Wi-Fi tethering for other devices. `null` = not declared, DIFFERENT from `false`.",
                "example": true,
                "nullable": true,
                "type": "boolean"
              },
              "kyc_required": {
                "description": "Identity verification (KYC) is required before use. `null` = not declared.",
                "example": false,
                "nullable": true,
                "type": "boolean"
              },
              "sms": {
                "description": "SMS. `false` = SMS is NOT received, which means bank OTPs are NOT received. `null` = not declared.",
                "example": false,
                "nullable": true,
                "type": "boolean"
              },
              "voice": {
                "description": "Voice calls. `false` = the supplier declares there is NO voice — a data-only line cannot receive calls, and selling it to someone who needs voice is a guaranteed complaint. `null` = not declared.",
                "example": false,
                "nullable": true,
                "type": "boolean"
              }
            },
            "type": "object"
          },
          "fup": {
            "description": "What happens after the high-speed allowance runs out. `null` = the package declares nothing about throttling.",
            "nullable": true,
            "properties": {
              "behavior": {
                "description": "`throttle` (slowed down) | `cutoff` (disconnected) | `null` (not declared). OPEN ENUM.",
                "enum": [
                  "throttle",
                  "cutoff",
                  null
                ],
                "nullable": true,
                "type": "string"
              },
              "high_speed_bytes": {
                "description": "BYTES at full speed before throttling kicks in.",
                "example": 1048576,
                "format": "int64",
                "nullable": true,
                "type": "integer"
              },
              "note": {
                "nullable": true,
                "type": "string"
              },
              "speed_kbps": {
                "description": "Speed AFTER the high-speed allowance runs out (kbps). `null` when the line is cut off or nothing is declared. DIFFERENT from `network.speed_cap_kbps`, which is the cap for the whole package.",
                "example": 128,
                "nullable": true,
                "type": "integer"
              }
            },
            "type": "object"
          },
          "market": {
            "description": "The package's SUPPLY SYSTEM, an OPEN ENUM (clients MUST ignore unknown values). This is the axis that decides whether a package has stock behind it and which post-sale operations it has: `domestic` sells kits and supports top-up, cancel and usage lookup; `international` is ordered from the supplier on request so it has no stock.",
            "enum": [
              "domestic",
              "international"
            ],
            "example": "international",
            "type": "string"
          },
          "name": {
            "example": "eSIM Trung Quốc Mainland Multi - 1GB/ngày - 24 ngày",
            "type": "string"
          },
          "name_en": {
            "description": "English name. `null` when not provided yet.",
            "nullable": true,
            "type": "string"
          },
          "network": {
            "description": "Which networks the package runs on.",
            "properties": {
              "apn": {
                "description": "The APN that must be entered by hand on some Android devices that do not pick it up automatically. Without it you cannot build an installation guide, and every such case becomes a support call. `null` = not declared.",
                "example": "e-ideas",
                "nullable": true,
                "type": "string"
              },
              "carriers": {
                "description": "The carriers this package RUNS ON. This is an eSIM specification (whose radio it uses), not the supplier.",
                "items": {
                  "properties": {
                    "name": {
                      "example": "China Unicom",
                      "type": "string"
                    }
                  },
                  "type": "object"
                },
                "type": "array"
              },
              "generations": {
                "description": "Network generations the package can use (`[\"4g\", \"5g\"]`). `null` = not declared — NOT \"2G only\".",
                "example": [
                  "4g",
                  "5g"
                ],
                "items": {
                  "type": "string"
                },
                "nullable": true,
                "type": "array"
              },
              "speed_cap_kbps": {
                "description": "Speed cap for the WHOLE package (kbps) — different from `fup.speed_kbps`, which applies after the high-speed allowance is gone.",
                "nullable": true,
                "type": "integer"
              }
            },
            "type": "object"
          },
          "price": {
            "$ref": "#/components/schemas/Money",
            "description": "The price YOUR ORGANIZATION pays through the API, exactly the figure `POST /v1/orders` will charge. Always a `{amount, currency}` object, never a bare number. No cost price is exposed here."
          },
          "tags": {
            "description": "Normalized commercial tags (lowercase, hyphenated). They are also what your key's catalog restrictions match on. NO package carries a tag today, so `?tag=` returns an empty list until the seller starts tagging.",
            "example": [],
            "items": {
              "type": "string"
            },
            "type": "array"
          },
          "validity": {
            "description": "How long the package lasts, and when the clock starts.",
            "properties": {
              "days": {
                "example": 24,
                "type": "integer"
              },
              "install_by_days": {
                "description": "Number of days to scan the QR / install the eSIM after purchase. `null` = not declared — NOT \"install whenever you like\".",
                "example": 365,
                "nullable": true,
                "type": "integer"
              },
              "label": {
                "example": "24 ngày",
                "type": "string"
              },
              "reclaim_on_expiry": {
                "description": "The supplier RECLAIMS the subscription when the plan expires — meaning it can no longer be topped up or changed. If you sell renewals you must know that window. `null` = not declared.",
                "example": true,
                "nullable": true,
                "type": "boolean"
              },
              "starts_when": {
                "description": "WHEN the clock starts: `on_install` (when the customer installs the eSIM) | `on_purchase` (at purchase) | `null`.\n\nThe HEAVIEST field in this block. If the supplier counts from INSTALL and you do not know it, you will quote the wrong expiry date to EVERY customer. `null` ⇒ do not promise a specific expiry date.",
                "enum": [
                  "on_install",
                  "on_purchase",
                  null
                ],
                "example": "on_install",
                "nullable": true,
                "type": "string"
              },
              "unused_valid_days": {
                "description": "Number of days the package can sit unused on the shelf before it expires. `null` = not declared, NOT unlimited. Deliberately separate from `install_by_days`: one is the INSTALL deadline, the other is the SHELF life.",
                "nullable": true,
                "type": "integer"
              }
            },
            "type": "object"
          }
        },
        "type": "object"
      },
      "PackageCapabilities": {
        "description": "What this package can do AFTER the sale. Read from each package's REAL declaration, never inferred from the supplier type: a package with nothing declared reports every flag as `false`. We would rather omit a button you could have used than promise an endpoint that is certain to return `422 capability.not_supported`.",
        "properties": {
          "cancel": {
            "properties": {
              "restriction": {
                "description": "`code_not_disclosed` = cancellation is only possible while NOBODY has seen the activation code (not sent to the customer, not read through the dashboard or through `?include=lpa`). This is the REAL condition on the cancel path. It says nothing about whether the customer has installed the eSIM — the system cannot know that, and the old label (`unused_only`) promised exactly that wrong thing.",
                "enum": [
                  "code_not_disclosed",
                  null
                ],
                "nullable": true,
                "type": "string"
              },
              "supported": {
                "type": "boolean"
              }
            },
            "type": "object"
          },
          "plan_change": {
            "properties": {
              "policy": {
                "$ref": "#/components/schemas/PlanChangePolicy"
              },
              "supported": {
                "type": "boolean"
              }
            },
            "type": "object"
          },
          "usage_check": {
            "properties": {
              "supported": {
                "type": "boolean"
              }
            },
            "type": "object"
          }
        },
        "type": "object"
      },
      "PackageDetail": {
        "properties": {
          "data": {
            "allOf": [
              {
                "$ref": "#/components/schemas/Package"
              },
              {
                "properties": {
                  "mode": {
                    "$ref": "#/components/schemas/ModeEcho"
                  }
                },
                "type": "object"
              }
            ]
          },
          "ok": {
            "enum": [
              true
            ],
            "type": "boolean"
          }
        },
        "required": [
          "ok",
          "data"
        ],
        "type": "object"
      },
      "PackageListResponse": {
        "properties": {
          "data": {
            "properties": {
              "items": {
                "items": {
                  "$ref": "#/components/schemas/Package"
                },
                "type": "array"
              },
              "mode": {
                "$ref": "#/components/schemas/ModeEcho"
              },
              "next_cursor": {
                "description": "Pass it back VERBATIM to fetch the next page. `null` = NO MORE data, and that is the ONLY stop signal: a page shorter than `page_size` may still have pages after it.",
                "nullable": true,
                "type": "string"
              },
              "page_size": {
                "description": "The MAXIMUM number of records the server used for this page (already clamped to the cap), not the actual number of elements in `items`.",
                "nullable": true,
                "type": "integer"
              },
              "total": {
                "description": "Total packages matching the filters. `null` when the set is large enough that counting is not worth paying for (the international system, or a call with no `market` filter). This count runs BEFORE the two application-level gates (your key's catalog restrictions and the package having a price for your organization), so it answers \"how many packages match in the catalog\", not \"how many you can buy\".",
                "format": "int64",
                "nullable": true,
                "type": "integer"
              },
              "warnings": {
                "description": "WHY the list looks the way it does. Empty = nothing unusual.\n\n`code` is a CLOSED set: `filtered_by_key_package_filters` (your key's catalog restrictions), `filtered_by_provider_scope` (your organization's provider scope), `no_price_for_org` (your organization has no price yet), `price_filter_removed_rows` (the price-range filter), `provider_disabled` (no supplier can deliver it any more), `capability_missing` (the package has not declared its post-sale capabilities). `count` is the number of rows involved, `null` when it cannot be expressed as a number.",
                "example": [],
                "items": {
                  "$ref": "#/components/schemas/ListWarning"
                },
                "type": "array"
              }
            },
            "type": "object"
          },
          "ok": {
            "enum": [
              true
            ],
            "type": "boolean"
          }
        },
        "required": [
          "ok",
          "data"
        ],
        "type": "object"
      },
      "PlanChangePolicy": {
        "description": "How TOPPING UP A NEW PLAN works at this supplier. `null` = the package cannot be topped up, OR we have not measured that supplier's policy. `null` says \"do not assume\"; an object of all-`false` would say \"we guarantee nothing gets cancelled\", and those are two very different statements.",
        "nullable": true,
        "properties": {
          "note": {
            "nullable": true,
            "type": "string"
          },
          "requires_cancel_first": {
            "description": "The RUNNING plan is cancelled before the new one is placed (this supplier does not stack plans). Tell the customer BEFORE they hit the top-up-a-new-plan button.",
            "type": "boolean"
          },
          "stacking": {
            "description": "Whether the new plan's validity and allowance add on top of the running one.",
            "type": "boolean"
          }
        },
        "type": "object"
      },
      "PlanOptionsResponse": {
        "properties": {
          "data": {
            "description": "The plans THIS eSIM can be topped up with — already filtered by the supplier holding it. Every element is a FULL `Package`, the same shape as the catalog: there is no second schema forcing you to write two type sets for one entity.\n\n`policy` is the policy of the supplier CURRENTLY HOLDING this eSIM. `null` = we have not measured that supplier's policy — read it as \"do not assume\", NOT as \"nothing gets cancelled\".",
            "properties": {
              "items": {
                "items": {
                  "$ref": "#/components/schemas/Package"
                },
                "type": "array"
              },
              "mode": {
                "$ref": "#/components/schemas/ModeEcho"
              },
              "next_cursor": {
                "description": "Pass it back VERBATIM to fetch the next page. `null` = NO MORE data, and that is the ONLY stop signal: a page shorter than `page_size` may still have pages after it.",
                "nullable": true,
                "type": "string"
              },
              "page_size": {
                "description": "The MAXIMUM number of records the server used for this page (already clamped to the cap), not the actual number of elements in `items`.",
                "type": "integer"
              },
              "policy": {
                "$ref": "#/components/schemas/PlanChangePolicy"
              },
              "total": {
                "description": "Total packages matching the filters. `null` when the set is large enough that counting is not worth paying for (the international system, or a call with no `market` filter). This count runs BEFORE the two application-level gates (your key's catalog restrictions and the package having a price for your organization), so it answers \"how many packages match in the catalog\", not \"how many you can buy\".",
                "format": "int64",
                "nullable": true,
                "type": "integer"
              },
              "warnings": {
                "description": "WHY the list looks the way it does. Empty = nothing unusual.\n\n`code` is a CLOSED set: `filtered_by_key_package_filters` (your key's catalog restrictions), `filtered_by_provider_scope` (your organization's provider scope), `no_price_for_org` (your organization has no price yet), `price_filter_removed_rows` (the price-range filter), `provider_disabled` (no supplier can deliver it any more), `capability_missing` (the package has not declared its post-sale capabilities). `count` is the number of rows involved, `null` when it cannot be expressed as a number.",
                "example": [],
                "items": {
                  "$ref": "#/components/schemas/ListWarning"
                },
                "type": "array"
              }
            },
            "type": "object"
          },
          "ok": {
            "enum": [
              true
            ],
            "type": "boolean"
          }
        },
        "required": [
          "ok",
          "data"
        ],
        "type": "object"
      },
      "PlanView": {
        "description": "The plan currently applied to this eSIM.",
        "nullable": true,
        "properties": {
          "current_code": {
            "type": "string"
          },
          "expires_at": {
            "format": "date-time",
            "nullable": true,
            "type": "string"
          },
          "started_at": {
            "format": "date-time",
            "nullable": true,
            "type": "string"
          }
        },
        "type": "object"
      },
      "ResendResponse": {
        "properties": {
          "data": {
            "properties": {
              "id": {
                "description": "ID of the order that was just re-sent.",
                "format": "uuid",
                "type": "string"
              },
              "mode": {
                "$ref": "#/components/schemas/ModeEcho"
              },
              "resent_at": {
                "format": "date-time",
                "type": "string"
              },
              "resent_to": {
                "description": "The recipient address, PARTIALLY MASKED — the partner surface never replays the customer's full email.",
                "example": "ab***@x.com",
                "type": "string"
              }
            },
            "type": "object"
          },
          "ok": {
            "enum": [
              true
            ],
            "type": "boolean"
          }
        },
        "required": [
          "ok",
          "data"
        ],
        "type": "object"
      },
      "SimulateRequest": {
        "properties": {
          "data": {
            "description": "Partially override the `data` field of the sample payload.",
            "type": "object"
          }
        },
        "type": "object"
      },
      "SimulateResponse": {
        "properties": {
          "data": {
            "properties": {
              "delivery_id": {
                "format": "uuid",
                "type": "string"
              },
              "event_id": {
                "format": "uuid",
                "type": "string"
              },
              "mode": {
                "$ref": "#/components/schemas/ModeEcho"
              }
            },
            "type": "object"
          },
          "ok": {
            "enum": [
              true
            ],
            "type": "boolean"
          }
        },
        "required": [
          "ok",
          "data"
        ],
        "type": "object"
      },
      "TestModeEsimState": {
        "properties": {
          "data": {
            "properties": {
              "cancellable": {
                "$ref": "#/components/schemas/Cancellable"
              },
              "external_ref": {
                "description": "Your own order reference (the `external_ref` you sent when ordering) — matches the eSIM back to your order. `null` when the order was placed without one.",
                "nullable": true,
                "type": "string"
              },
              "iccid": {
                "example": "8984012345678901234",
                "nullable": true,
                "type": "string"
              },
              "id": {
                "description": "The eSIM id — used in every `/v1/esims/{id}/…` path (also the `order_item_id` in webhooks). If you only have the ICCID, call `GET /v1/esims?iccid=…` to get the `id`.",
                "format": "uuid",
                "type": "string"
              },
              "install": {
                "$ref": "#/components/schemas/Install"
              },
              "mode": {
                "$ref": "#/components/schemas/ModeEcho"
              },
              "msisdn": {
                "nullable": true,
                "type": "string"
              },
              "order_id": {
                "format": "uuid",
                "type": "string"
              },
              "package_code": {
                "description": "The package CODE from the catalog — the key to map this eSIM back to your catalog. `null` only when the source package has been removed from the catalog.",
                "nullable": true,
                "type": "string"
              },
              "package_name": {
                "description": "The display NAME — a human-readable label that can change. Do NOT use it as a reconciliation key.",
                "type": "string"
              },
              "plan": {
                "$ref": "#/components/schemas/PlanView"
              },
              "simulated": {
                "description": "The simulated state AFTER the scenario has been applied.",
                "properties": {
                  "esim_state": {
                    "description": "The MACHINE lifecycle of the test eSIM. On live this comes from `smdp_status`, but a test eSIM has no SM-DP+ profile to read — so it lives here, next to the other test-mode-only keys.",
                    "enum": [
                      "released",
                      "installed",
                      "enabled",
                      "disabled"
                    ],
                    "type": "string"
                  },
                  "last_via": {
                    "description": "The flow that TOUCHED it most recently.",
                    "enum": [
                      "open_api",
                      "simulate",
                      "seed"
                    ],
                    "type": "string"
                  },
                  "via": {
                    "description": "The flow that CREATED this test eSIM.",
                    "enum": [
                      "open_api",
                      "simulate",
                      "seed"
                    ],
                    "type": "string"
                  }
                },
                "type": "object"
              },
              "smdp_status": {
                "example": "released",
                "nullable": true,
                "type": "string"
              },
              "status": {
                "example": "delivered",
                "type": "string"
              },
              "updated_at": {
                "format": "date-time",
                "type": "string"
              },
              "usage": {
                "$ref": "#/components/schemas/Usage"
              }
            },
            "type": "object"
          },
          "ok": {
            "enum": [
              true
            ],
            "type": "boolean"
          }
        },
        "required": [
          "ok",
          "data"
        ],
        "type": "object"
      },
      "TestModeFailNextRequest": {
        "properties": {
          "count": {
            "default": 1,
            "maximum": 5,
            "minimum": 1,
            "type": "integer"
          }
        },
        "type": "object"
      },
      "TestModeFailNextResponse": {
        "properties": {
          "data": {
            "properties": {
              "endpoint_id": {
                "format": "uuid",
                "type": "string"
              },
              "mode": {
                "$ref": "#/components/schemas/ModeEcho"
              },
              "testmode_fail_next": {
                "type": "integer"
              }
            },
            "type": "object"
          },
          "ok": {
            "enum": [
              true
            ],
            "type": "boolean"
          }
        },
        "required": [
          "ok",
          "data"
        ],
        "type": "object"
      },
      "TestModeReset": {
        "properties": {
          "data": {
            "properties": {
              "esim_states_deleted": {
                "format": "int64",
                "type": "integer"
              },
              "mode": {
                "$ref": "#/components/schemas/ModeEcho"
              },
              "order_items_deleted": {
                "format": "int64",
                "type": "integer"
              },
              "orders_deleted": {
                "format": "int64",
                "type": "integer"
              },
              "wallet_balance": {
                "description": "Test wallet balance AFTER the reset — always 0. The ledger is NOT deleted: the wallet is zeroed       by an offsetting entry.",
                "format": "int64",
                "type": "integer"
              }
            },
            "type": "object"
          },
          "ok": {
            "enum": [
              true
            ],
            "type": "boolean"
          }
        },
        "required": [
          "ok",
          "data"
        ],
        "type": "object"
      },
      "TestModeResetInput": {
        "properties": {
          "reason": {
            "example": "Reset test data",
            "maxLength": 1000,
            "minLength": 1,
            "type": "string"
          }
        },
        "type": "object"
      },
      "TestModeSeed": {
        "properties": {
          "data": {
            "properties": {
              "already_seeded": {
                "type": "boolean"
              },
              "esims_created": {
                "type": "integer"
              },
              "mode": {
                "$ref": "#/components/schemas/ModeEcho"
              },
              "orders_created": {
                "type": "integer"
              },
              "seeded": {
                "type": "boolean"
              }
            },
            "type": "object"
          },
          "ok": {
            "enum": [
              true
            ],
            "type": "boolean"
          }
        },
        "required": [
          "ok",
          "data"
        ],
        "type": "object"
      },
      "TestModeSimulateRequest": {
        "description": "The scenario to apply. Every field is optional but at least one is required — an empty body returns `422 testmode.invalid_scenario` instead of silently reporting success.",
        "properties": {
          "data_used_mb": {
            "description": "Data ALREADY USED (MB, binary 1 MiB units). A value above the package allowance is clamped to the allowance — that is the ran-out-of-data scenario, not an error.",
            "example": 4096,
            "format": "int64",
            "minimum": 0,
            "type": "integer"
          },
          "expire_at": {
            "description": "Set a specific expiry timestamp (ISO-8601 UTC).",
            "format": "date-time",
            "type": "string"
          },
          "expire_now": {
            "description": "`true` = expire the plan right now (`esim_status` becomes `expired`).",
            "type": "boolean"
          },
          "provider_reject": {
            "description": "Simulate provider rejection on the next operation (top-up/cancel).",
            "type": "boolean"
          },
          "status": {
            "description": "The MACHINE lifecycle of the eSIM profile (the provider's vocabulary). DIFFERENT from the `esim_status` returned by the eSIM endpoints (the commercial vocabulary).",
            "enum": [
              "released",
              "installed",
              "enabled",
              "disabled"
            ],
            "example": "enabled",
            "type": "string"
          },
          "throttled": {
            "description": "`true` = the line is throttled (an unlimited plan past its high-speed threshold).",
            "type": "boolean"
          }
        },
        "type": "object"
      },
      "TestModeStatus": {
        "properties": {
          "data": {
            "properties": {
              "base_url_testing": {
                "description": "Test-mode host. Use tke_test_ or tke_member_test_ keys only; calling the live host returns 403 partner.wrong_host.",
                "type": "string"
              },
              "capabilities": {
                "properties": {
                  "bulk_orders": {
                    "type": "boolean"
                  },
                  "cancel_refund": {
                    "type": "boolean"
                  },
                  "refund_requests": {
                    "type": "boolean"
                  },
                  "webhooks": {
                    "type": "boolean"
                  }
                },
                "type": "object"
              },
              "checklist": {
                "description": "Integration milestones are derived from actual API traces for the same owner and test mode. The webhook step applies to organizations only.",
                "items": {
                  "properties": {
                    "done": {
                      "type": "boolean"
                    },
                    "label": {
                      "type": "string"
                    },
                    "last_at": {
                      "format": "date-time",
                      "nullable": true,
                      "type": "string"
                    },
                    "step": {
                      "type": "string"
                    }
                  },
                  "type": "object"
                },
                "type": "array"
              },
              "drift": {
                "description": "The simulated usage loop: enabled state, interval and last run. Threshold webhooks are emitted only for organizations with webhook support.",
                "properties": {
                  "enabled": {
                    "type": "boolean"
                  },
                  "interval_secs": {
                    "format": "int64",
                    "type": "integer"
                  },
                  "last_run_at": {
                    "format": "date-time",
                    "nullable": true,
                    "type": "string"
                  }
                },
                "type": "object"
              },
              "mode": {
                "enum": [
                  "test",
                  "live"
                ],
                "type": "string"
              },
              "ready": {
                "description": "Whether the key owner has completed every supported checklist step. Members do not need a webhook test.",
                "type": "boolean"
              },
              "sample_counts": {
                "properties": {
                  "test_esims": {
                    "format": "int64",
                    "type": "integer"
                  },
                  "test_orders": {
                    "format": "int64",
                    "type": "integer"
                  }
                },
                "type": "object"
              },
              "seeded": {
                "description": "Whether the key owner already has sample test orders and eSIMs.",
                "type": "boolean"
              },
              "test_key_count": {
                "format": "int64",
                "type": "integer"
              },
              "wallet_balance": {
                "description": "Test wallet balance in virtual VND for the key owner, isolated from the live wallet.",
                "format": "int64",
                "type": "integer"
              },
              "webhook": {
                "properties": {
                  "endpoint_url": {
                    "nullable": true,
                    "type": "string"
                  },
                  "fail_next": {
                    "type": "integer"
                  },
                  "status": {
                    "nullable": true,
                    "type": "string"
                  }
                },
                "type": "object"
              }
            },
            "type": "object"
          },
          "ok": {
            "enum": [
              true
            ],
            "type": "boolean"
          }
        },
        "required": [
          "ok",
          "data"
        ],
        "type": "object"
      },
      "TestModeWebhookFireRequest": {
        "properties": {
          "data": {
            "description": "Partially override the `data` field of the sample payload.",
            "type": "object"
          },
          "event_type": {
            "description": "Webhook event type to simulate firing.",
            "enum": [
              "order.completed",
              "order.failed",
              "esim.delivered",
              "esim.activated",
              "esim.usage_threshold",
              "esim.expiring",
              "wallet.low_balance",
              "catalog.updated"
            ],
            "type": "string"
          }
        },
        "required": [
          "event_type"
        ],
        "type": "object"
      },
      "Usage": {
        "description": "eSIM data usage statistics. `updated_at = null` when never polled.",
        "nullable": true,
        "properties": {
          "expires_at": {
            "format": "date-time",
            "nullable": true,
            "type": "string"
          },
          "remaining_bytes": {
            "format": "int64",
            "nullable": true,
            "type": "integer"
          },
          "source": {
            "enum": [
              "poll",
              "webhook"
            ],
            "nullable": true,
            "type": "string"
          },
          "total_bytes": {
            "format": "int64",
            "nullable": true,
            "type": "integer"
          },
          "updated_at": {
            "format": "date-time",
            "nullable": true,
            "type": "string"
          },
          "used_bytes": {
            "format": "int64",
            "nullable": true,
            "type": "integer"
          }
        },
        "type": "object"
      },
      "WalletBalance": {
        "properties": {
          "data": {
            "properties": {
              "available": {
                "$ref": "#/components/schemas/Money"
              },
              "balance": {
                "$ref": "#/components/schemas/Money"
              },
              "credit_limit": {
                "$ref": "#/components/schemas/Money"
              },
              "mode": {
                "$ref": "#/components/schemas/ModeEcho"
              }
            },
            "type": "object"
          },
          "ok": {
            "enum": [
              true
            ],
            "type": "boolean"
          }
        },
        "required": [
          "ok",
          "data"
        ],
        "type": "object"
      },
      "WebhookConfig": {
        "properties": {
          "data": {
            "properties": {
              "active": {
                "description": "`false` = the endpoint has stopped accepting new events. That only happens when your organization pauses it from the dashboard, or when the API key is revoked (the cascade shuts the outbound channel down in the same transaction). A `PUT /v1/webhooks/{id}` that passes CHECK_HEALTH turns it back on.",
                "type": "boolean"
              },
              "created_at": {
                "format": "date-time",
                "type": "string"
              },
              "events": {
                "description": "Empty = receive everything.",
                "items": {
                  "enum": [
                    "order.completed",
                    "order.failed",
                    "esim.delivered",
                    "esim.activated",
                    "esim.usage_threshold",
                    "esim.expiring",
                    "wallet.low_balance",
                    "catalog.updated"
                  ],
                  "type": "string"
                },
                "type": "array"
              },
              "id": {
                "description": "The endpoint's ID — what you put into `/v1/webhooks/{id}`. A NEW one is minted on every create: deleting and re-creating gives you a different endpoint, not the old one brought back to life.",
                "format": "uuid",
                "type": "string"
              },
              "last_delivery_at": {
                "description": "The last time we DELIVERED through this endpoint. `null` = never delivered — a different thing from \"delivery failed\", and the first thing to check when a partner reports receiving no webhooks.",
                "format": "date-time",
                "nullable": true,
                "type": "string"
              },
              "mode": {
                "$ref": "#/components/schemas/ModeEcho"
              },
              "secret_last4": {
                "description": "The last four characters of the webhook secret — enough for you to check \"is the secret I hold the one the system signs with?\" without any endpoint handing the secret back. The full secret is shown ONCE, at creation.",
                "type": "string"
              },
              "url": {
                "type": "string"
              }
            },
            "type": "object"
          },
          "ok": {
            "enum": [
              true
            ],
            "type": "boolean"
          }
        },
        "required": [
          "ok",
          "data"
        ],
        "type": "object"
      },
      "WebhookEndpoint": {
        "properties": {
          "active": {
            "description": "`false` = the endpoint has stopped accepting new events. That only happens when your organization pauses it from the dashboard, or when the API key is revoked (the cascade shuts the outbound channel down in the same transaction). A `PUT /v1/webhooks/{id}` that passes CHECK_HEALTH turns it back on.",
            "type": "boolean"
          },
          "created_at": {
            "format": "date-time",
            "type": "string"
          },
          "events": {
            "description": "Empty = receive everything.",
            "items": {
              "enum": [
                "order.completed",
                "order.failed",
                "esim.delivered",
                "esim.activated",
                "esim.usage_threshold",
                "esim.expiring",
                "wallet.low_balance",
                "catalog.updated"
              ],
              "type": "string"
            },
            "type": "array"
          },
          "id": {
            "description": "The endpoint's ID — what you put into `/v1/webhooks/{id}`. A NEW one is minted on every create: deleting and re-creating gives you a different endpoint, not the old one brought back to life.",
            "format": "uuid",
            "type": "string"
          },
          "last_delivery_at": {
            "description": "The last time we DELIVERED through this endpoint. `null` = never delivered — a different thing from \"delivery failed\", and the first thing to check when a partner reports receiving no webhooks.",
            "format": "date-time",
            "nullable": true,
            "type": "string"
          },
          "secret_last4": {
            "description": "The last four characters of the webhook secret — enough for you to check \"is the secret I hold the one the system signs with?\" without any endpoint handing the secret back. The full secret is shown ONCE, at creation.",
            "type": "string"
          },
          "url": {
            "type": "string"
          }
        },
        "type": "object"
      },
      "WebhookListResponse": {
        "properties": {
          "data": {
            "properties": {
              "items": {
                "items": {
                  "$ref": "#/components/schemas/WebhookEndpoint"
                },
                "type": "array"
              },
              "mode": {
                "$ref": "#/components/schemas/ModeEcho"
              },
              "next_cursor": {
                "description": "Pass it back VERBATIM to fetch the next page. `null` = NO MORE data, and that is the ONLY stop signal: a page shorter than `page_size` may still have pages after it.",
                "nullable": true,
                "type": "string"
              },
              "page_size": {
                "description": "The MAXIMUM number of records the server used for this page (already clamped to the cap), not the actual number of elements in `items`.",
                "nullable": true,
                "type": "integer"
              },
              "total": {
                "description": "How many rows match your filter. `null` when counting is too expensive — do not use it to tell whether you have reached the end; `next_cursor` is that signal.",
                "format": "int64",
                "nullable": true,
                "type": "integer"
              },
              "warnings": {
                "description": "WHY the list looks the way it does. Empty = nothing unusual.\n\n`code` is a CLOSED set: `filtered_by_key_package_filters` (your key's catalog restrictions), `filtered_by_provider_scope` (your organization's provider scope), `no_price_for_org` (your organization has no price yet), `price_filter_removed_rows` (the price-range filter), `provider_disabled` (no supplier can deliver it any more), `capability_missing` (the package has not declared its post-sale capabilities). `count` is the number of rows involved, `null` when it cannot be expressed as a number.",
                "example": [],
                "items": {
                  "$ref": "#/components/schemas/ListWarning"
                },
                "type": "array"
              }
            },
            "type": "object"
          },
          "ok": {
            "enum": [
              true
            ],
            "type": "boolean"
          }
        },
        "required": [
          "ok",
          "data"
        ],
        "type": "object"
      },
      "WebhookRequest": {
        "properties": {
          "events": {
            "description": "Empty = receive everything. An unknown value → 400 `internal.validation_failed` (field `events`).",
            "items": {
              "enum": [
                "order.completed",
                "order.failed",
                "esim.delivered",
                "esim.activated",
                "esim.usage_threshold",
                "esim.expiring",
                "wallet.low_balance",
                "catalog.updated"
              ],
              "type": "string"
            },
            "type": "array"
          },
          "url": {
            "example": "https://partner.example.com/hooks/tke",
            "format": "uri",
            "type": "string"
          }
        },
        "required": [
          "url"
        ],
        "type": "object"
      }
    },
    "securitySchemes": {
      "TkeHmac": {
        "description": "HMAC-SHA256 authentication (§4). Send 4 headers: `X-TKE-Key` (public key_id), `X-TKE-Timestamp` (epoch ms), `X-TKE-Nonce` (a UUID per request), `X-TKE-Signature` (hex lowercase). signing_string = method\\npath\\ntimestamp\\nnonce\\nbody_hash; body_hash = SHA256(raw_body) hex. Compared with constant-time comparison. path INCLUDES the query string.",
        "in": "header",
        "name": "X-TKE-Signature",
        "type": "apiKey"
      }
    }
  },
  "info": {
    "description": "Open API for partners (tier-1 agents) integrating eSIM sales. Every `/v1/*` endpoint authenticates with **HMAC-SHA256** on 4 `X-TKE-*` headers (no unsigned mode). POST calls that touch money/orders require `Idempotency-Key`. A `tke_test_` key runs in test mode (virtual wallet, virtual stock, fake provisioning) on testing host. REST is the source of truth (pull-based reconciliation via `updated_since` + cursor); webhooks are only a convenience. Primary keys of resources are always `id` (foreign keys carry prefixes such as `order_id`).",
    "title": "Tổng Kho eSIM — Open API đối tác",
    "version": "2.4.0"
  },
  "openapi": "3.0.3",
  "paths": {
    "/v1/catalog/packages": {
      "get": {
        "description": "Required scope: `catalog:read`.\n\nYour organization's package catalog: ONE surface for both supply systems, filtered with `?market=`.\n\nIt only returns packages you can ACTUALLY BUY: published, still distributed by the supplier, within your key's catalog restrictions, AND already priced for your organization. An unpriced package does not appear, not to hide it but because you could not order it.\n\n**Cursor pagination**: call it first without `cursor`, then pass `next_cursor` back verbatim until it is `null`. A page SHORTER than `page_size` does NOT mean the end (see the `cursor` parameter).\n\n`capabilities` comes from each package's REAL declaration, never from a guess: a package with nothing declared reports every flag as `false`. We would rather omit a button you could have used than promise an endpoint that is certain to return `422`.\n\nThe catalog carries NO stock concept: no figure, no in-stock/sold-out level. Packages simply appear here; ordering one the supplier cannot fulfill returns a clear error right at order time from `POST /v1/orders` (`esim.out_of_stock`…), instead of making you infer it from a field shown beforehand.",
        "operationId": "get_catalog_packages",
        "parameters": [
          {
            "description": "Records per page. Default 50, capped at 100 — sending a larger value is clamped to 100 (NOT an error).",
            "in": "query",
            "name": "page_size",
            "required": false,
            "schema": {
              "default": 50,
              "maximum": 100,
              "minimum": 1,
              "type": "integer"
            }
          },
          {
            "description": "Supply system: `domestic` | `international`. A value outside the list returns `internal.invalid_filter` (400) rather than the whole catalog: one typo must not read as \"this filter excludes nothing\".",
            "in": "query",
            "name": "market",
            "required": false,
            "schema": {
              "type": "string"
            }
          },
          {
            "description": "Country code (ISO-3166 alpha-2, e.g. `VN`). Only packages covering this country.",
            "in": "query",
            "name": "country",
            "required": false,
            "schema": {
              "type": "string"
            }
          },
          {
            "description": "Region slug (e.g. `chau-a`). Packages covering at least one country of that region, plus every global package.",
            "in": "query",
            "name": "region",
            "required": false,
            "schema": {
              "type": "string"
            }
          },
          {
            "description": "Free-text search over the public code and the package name (partial, case-insensitive).",
            "in": "query",
            "name": "q",
            "required": false,
            "schema": {
              "type": "string"
            }
          },
          {
            "description": "Filter by ONE commercial tag. Tags are normalized exactly as they were stored (extra spaces removed, lowercased, hyphenated), so `Ban Le` and `ban-le` are the same tag.",
            "in": "query",
            "name": "tag",
            "required": false,
            "schema": {
              "type": "string"
            }
          },
          {
            "description": "Data model: `daily` | `fixed` | `unlimited`. REPLACES the old `supports_topup`. An unknown value returns `internal.invalid_filter` (400).",
            "in": "query",
            "name": "data_model",
            "required": false,
            "schema": {
              "type": "string"
            }
          },
          {
            "description": "Minimum data allowance in **BYTES**. Per-day packages are compared on their PER-DAY allowance; unlimited packages ALWAYS pass a lower bound.",
            "in": "query",
            "name": "data_min_bytes",
            "required": false,
            "schema": {
              "format": "int64",
              "type": "integer"
            }
          },
          {
            "description": "Maximum data allowance in **BYTES**. Unlimited packages are EXCLUDED: a data ceiling cannot apply to an unlimited package, and letting it through would hand an unlimited plan to someone looking for the smallest one.",
            "in": "query",
            "name": "data_max_bytes",
            "required": false,
            "schema": {
              "format": "int64",
              "type": "integer"
            }
          },
          {
            "description": "Minimum validity in days.",
            "in": "query",
            "name": "days_min",
            "required": false,
            "schema": {
              "format": "int64",
              "type": "integer"
            }
          },
          {
            "description": "Maximum validity in days.",
            "in": "query",
            "name": "days_max",
            "required": false,
            "schema": {
              "format": "int64",
              "type": "integer"
            }
          },
          {
            "description": "Minimum price (VND), compared against YOUR ORGANIZATION'S price. This filter runs AFTER the server has cut the page, so it can shorten a page without meaning the data ran out.",
            "in": "query",
            "name": "price_min",
            "required": false,
            "schema": {
              "format": "int64",
              "type": "integer"
            }
          },
          {
            "description": "Maximum price (VND), same rules as `price_min`.",
            "in": "query",
            "name": "price_max",
            "required": false,
            "schema": {
              "format": "int64",
              "type": "integer"
            }
          },
          {
            "description": "Only packages that run on this network generation (`2g` | `3g` | `4g` | `5g`). An unknown value returns `internal.invalid_filter` (400).",
            "in": "query",
            "name": "network_gen",
            "required": false,
            "schema": {
              "type": "string"
            }
          },
          {
            "description": "`true` = only packages DECLARED to have Wi-Fi tethering; `false` = only those DECLARED not to. A package with nothing declared (`features.hotspot = null`) matches NEITHER side — the `null` rule applies to filters exactly as it does to responses. Accepts only the two strings `true`/`false`.",
            "in": "query",
            "name": "hotspot",
            "required": false,
            "schema": {
              "enum": [
                "true",
                "false"
              ],
              "type": "string"
            }
          },
          {
            "description": "Only packages DECLARED to run this app on at least one operating system (`app=tiktok`). A package that declares nothing about that app does NOT match — silence is not a promise. Only `tiktok` is accepted today; any other code returns `internal.invalid_filter` (400).",
            "in": "query",
            "name": "app",
            "required": false,
            "schema": {
              "type": "string"
            }
          },
          {
            "description": "`true` = only packages DECLARED to have voice; `false` = only those DECLARED not to. A package with nothing declared matches neither side. Accepts only the two strings `true`/`false`.",
            "in": "query",
            "name": "voice",
            "required": false,
            "schema": {
              "enum": [
                "true",
                "false"
              ],
              "type": "string"
            }
          },
          {
            "description": "Cursor for the next page: pass the previous page's `next_cursor` back VERBATIM.\n\n**Stop when `next_cursor` is `null`; do NOT stop on a short page.** Three gates run AFTER the server has cut the page (your key's catalog restrictions, the package must be priced for your organization, and the price-range filter), so a page returning 3 items may still have thousands of packages behind it.\n\nThe cursor carries a FINGERPRINT of the filters that produced it. Sending it alongside different filters (or sending a corrupted string) returns `catalog.cursor_mismatch` (400) instead of quietly resuming a different query: stitching two pages from two filter sets together produces a catalog that never existed, and no layer can detect that afterwards.",
            "in": "query",
            "name": "cursor",
            "required": false,
            "schema": {
              "type": "string"
            }
          }
        ],
        "responses": {
          "200": {
            "content": {
              "application/json": {
                "schema": {
                  "$ref": "#/components/schemas/PackageListResponse"
                }
              }
            },
            "description": "Success"
          },
          "400": {
            "content": {
              "application/json": {
                "schema": {
                  "$ref": "#/components/schemas/ErrorEnvelope"
                }
              }
            },
            "description": "Error: `internal.validation_failed`, `internal.invalid_filter`, `catalog.cursor_mismatch`"
          },
          "401": {
            "content": {
              "application/json": {
                "schema": {
                  "$ref": "#/components/schemas/ErrorEnvelope"
                }
              }
            },
            "description": "Error: `partner.invalid_key`, `partner.invalid_signature`, `partner.timestamp_expired`, `partner.nonce_reused`"
          },
          "403": {
            "content": {
              "application/json": {
                "schema": {
                  "$ref": "#/components/schemas/ErrorEnvelope"
                }
              }
            },
            "description": "Error: `partner.ip_not_allowed`, `partner.scope_denied`, `partner.key_suspended`, `partner.key_revoked`, `org.suspended`"
          },
          "429": {
            "content": {
              "application/json": {
                "schema": {
                  "$ref": "#/components/schemas/ErrorEnvelope"
                }
              }
            },
            "description": "Error: `partner.rate_limited`"
          },
          "503": {
            "content": {
              "application/json": {
                "schema": {
                  "$ref": "#/components/schemas/ErrorEnvelope"
                }
              }
            },
            "description": "Error: `partner.endpoint_disabled`, `internal.service_unavailable`"
          }
        },
        "summary": "List packages",
        "tags": [
          "catalog"
        ],
        "x-tke-modes": [
          "live",
          "test"
        ],
        "x-tke-owner-types": [
          "organization",
          "member"
        ]
      }
    },
    "/v1/catalog/packages/{code}": {
      "get": {
        "description": "Required scope: `catalog:read`.\n\nDetails of EXACTLY ONE package by its public code. `data` carries the SAME FIELD SET as an element of `GET /v1/catalog/packages`, plus `mode` — no field exists only on the detail endpoint, so you write exactly one type for `Package`.\n\nA code you cannot see → `partner.package_not_visible` (404). ONE code for every cause (the code does not exist · it is outside your key's catalog restrictions · your organization has no price for it · the package lost its supply link): being able to tell them apart would hand a stranger a way to enumerate the whole catalog.",
        "operationId": "get_catalog_packages__code_",
        "parameters": [
          {
            "description": "The package code, i.e. the `code` you see in the catalog (`public_sku`). IMMUTABLE per package: it does not change when we switch the supplier behind it, so you can store it as a reconciliation key. A code outside your key's catalog restrictions, or a code your organization has no price for, returns `404 partner.package_not_visible` exactly like a code that does not exist.",
            "in": "path",
            "name": "code",
            "required": true,
            "schema": {
              "type": "string"
            }
          }
        ],
        "responses": {
          "200": {
            "content": {
              "application/json": {
                "schema": {
                  "$ref": "#/components/schemas/PackageDetail"
                }
              }
            },
            "description": "Success"
          },
          "400": {
            "content": {
              "application/json": {
                "schema": {
                  "$ref": "#/components/schemas/ErrorEnvelope"
                }
              }
            },
            "description": "Error: `internal.validation_failed`"
          },
          "401": {
            "content": {
              "application/json": {
                "schema": {
                  "$ref": "#/components/schemas/ErrorEnvelope"
                }
              }
            },
            "description": "Error: `partner.invalid_key`, `partner.invalid_signature`, `partner.timestamp_expired`, `partner.nonce_reused`"
          },
          "403": {
            "content": {
              "application/json": {
                "schema": {
                  "$ref": "#/components/schemas/ErrorEnvelope"
                }
              }
            },
            "description": "Error: `partner.ip_not_allowed`, `partner.scope_denied`, `partner.key_suspended`, `partner.key_revoked`, `org.suspended`"
          },
          "404": {
            "content": {
              "application/json": {
                "schema": {
                  "$ref": "#/components/schemas/ErrorEnvelope"
                }
              }
            },
            "description": "Error: `partner.package_not_visible`"
          },
          "429": {
            "content": {
              "application/json": {
                "schema": {
                  "$ref": "#/components/schemas/ErrorEnvelope"
                }
              }
            },
            "description": "Error: `partner.rate_limited`"
          },
          "503": {
            "content": {
              "application/json": {
                "schema": {
                  "$ref": "#/components/schemas/ErrorEnvelope"
                }
              }
            },
            "description": "Error: `partner.endpoint_disabled`, `internal.service_unavailable`"
          }
        },
        "summary": "Get one package",
        "tags": [
          "catalog"
        ],
        "x-tke-modes": [
          "live",
          "test"
        ],
        "x-tke-owner-types": [
          "organization",
          "member"
        ]
      }
    },
    "/v1/esims": {
      "get": {
        "description": "Required scope: `esims:read`.",
        "operationId": "get_esims",
        "parameters": [
          {
            "description": "Records per page. Default 50, capped at 200 — sending a larger value is clamped to 200 (NOT an error).",
            "in": "query",
            "name": "page_size",
            "required": false,
            "schema": {
              "default": 50,
              "maximum": 200,
              "minimum": 1,
              "type": "integer"
            }
          },
          {
            "description": "Filter by `esim_status`. A value outside the list → `internal.invalid_filter` (400) — it does NOT return an empty list, so a typo (e.g. `canceled` missing an `l`) isn't misread as \"this customer has no eSIM in that state\".",
            "in": "query",
            "name": "status",
            "required": false,
            "schema": {
              "enum": [
                "pending",
                "available",
                "reserved",
                "sold",
                "delivered",
                "in_use",
                "used_up",
                "expired",
                "suspended",
                "cancelled",
                "provider_failed"
              ],
              "type": "string"
            }
          },
          {
            "description": "Only eSIMs belonging to one order.",
            "in": "query",
            "name": "order_id",
            "required": false,
            "schema": {
              "format": "uuid",
              "type": "string"
            }
          },
          {
            "description": "Filter by ICCID (string, exact match).",
            "in": "query",
            "name": "iccid",
            "required": false,
            "schema": {
              "type": "string"
            }
          },
          {
            "description": "Every eSIM of the order(s) carrying this order reference of yours (the `external_ref` sent when ordering, exact match).",
            "in": "query",
            "name": "external_ref",
            "required": false,
            "schema": {
              "type": "string"
            }
          },
          {
            "description": "ISO-8601 UTC. Returns every record with `updated_at >= updated_since`, sorted ascending — this is the standard PULL-BASED RECONCILIATION path (§8.5): even a missed webhook can still be reconstructed from state. Wrong format → `internal.invalid_filter` (400).",
            "in": "query",
            "name": "updated_since",
            "required": false,
            "schema": {
              "format": "date-time",
              "type": "string"
            }
          },
          {
            "description": "Cursor for the next page — pass back the `next_cursor` value from the previous page VERBATIM. WARNING: a malformed cursor does NOT raise an error; the server just reads from the START of the list again (a deliberate choice so that changing the cursor encoding scheme never breaks a partner's running reconciliation loop). Consequence to know: an automated reconciliation loop that keeps sending a garbage cursor will loop forever on the first page instead of stopping — check that `next_cursor` differs from the cursor you just sent before looping again.",
            "in": "query",
            "name": "cursor",
            "required": false,
            "schema": {
              "type": "string"
            }
          },
          {
            "description": "Ask for extra blocks, comma-separated. `lpa` = return the `install` block (the eSIM activation code); `qr` = also return `install.qr_code_base64` (detail endpoints only).\n\n**RETURNING THE CODE IS DISCLOSING IT.** Asking for `lpa` PERMANENTLY closes the cancellation window of every eSIM in the response (`cancellable.allowed` becomes `false`, `reason = code_disclosed`) — once the code has left our systems it cannot be recalled. Ask for it when you deliver to the customer right away; skip it when you order ahead and deliver later, and you keep the right to cancel. An unknown value → `internal.invalid_filter` (400, `field: \"include\"`).",
            "in": "query",
            "name": "include",
            "required": false,
            "schema": {
              "example": "lpa",
              "type": "string"
            }
          }
        ],
        "responses": {
          "200": {
            "content": {
              "application/json": {
                "schema": {
                  "$ref": "#/components/schemas/EsimListResponse"
                }
              }
            },
            "description": "Success"
          },
          "400": {
            "content": {
              "application/json": {
                "schema": {
                  "$ref": "#/components/schemas/ErrorEnvelope"
                }
              }
            },
            "description": "Error: `internal.validation_failed`, `internal.invalid_filter`"
          },
          "401": {
            "content": {
              "application/json": {
                "schema": {
                  "$ref": "#/components/schemas/ErrorEnvelope"
                }
              }
            },
            "description": "Error: `partner.invalid_key`, `partner.invalid_signature`, `partner.timestamp_expired`, `partner.nonce_reused`"
          },
          "403": {
            "content": {
              "application/json": {
                "schema": {
                  "$ref": "#/components/schemas/ErrorEnvelope"
                }
              }
            },
            "description": "Error: `partner.ip_not_allowed`, `partner.scope_denied`, `partner.key_suspended`, `partner.key_revoked`, `org.suspended`"
          },
          "429": {
            "content": {
              "application/json": {
                "schema": {
                  "$ref": "#/components/schemas/ErrorEnvelope"
                }
              }
            },
            "description": "Error: `partner.rate_limited`"
          },
          "503": {
            "content": {
              "application/json": {
                "schema": {
                  "$ref": "#/components/schemas/ErrorEnvelope"
                }
              }
            },
            "description": "Error: `partner.endpoint_disabled`, `internal.service_unavailable`"
          }
        },
        "summary": "List eSIMs (pull-based reconciliation)",
        "tags": [
          "esims"
        ],
        "x-tke-modes": [
          "live",
          "test"
        ],
        "x-tke-owner-types": [
          "organization",
          "member"
        ]
      }
    },
    "/v1/esims/{id}": {
      "get": {
        "description": "Required scope: `esims:read`.",
        "operationId": "get_esims__id_",
        "parameters": [
          {
            "description": "The stable ID of a sold eSIM (NOT the ICCID — an ICCID can be reused).",
            "in": "path",
            "name": "id",
            "required": true,
            "schema": {
              "type": "string"
            }
          },
          {
            "description": "Ask for extra blocks, comma-separated. `lpa` = return the `install` block (the eSIM activation code); `qr` = also return `install.qr_code_base64` (detail endpoints only).\n\n**RETURNING THE CODE IS DISCLOSING IT.** Asking for `lpa` PERMANENTLY closes the cancellation window of every eSIM in the response (`cancellable.allowed` becomes `false`, `reason = code_disclosed`) — once the code has left our systems it cannot be recalled. Ask for it when you deliver to the customer right away; skip it when you order ahead and deliver later, and you keep the right to cancel. An unknown value → `internal.invalid_filter` (400, `field: \"include\"`).",
            "in": "query",
            "name": "include",
            "required": false,
            "schema": {
              "example": "lpa",
              "type": "string"
            }
          }
        ],
        "responses": {
          "200": {
            "content": {
              "application/json": {
                "schema": {
                  "$ref": "#/components/schemas/Esim"
                }
              }
            },
            "description": "Success"
          },
          "400": {
            "content": {
              "application/json": {
                "schema": {
                  "$ref": "#/components/schemas/ErrorEnvelope"
                }
              }
            },
            "description": "Error: `internal.validation_failed`, `internal.invalid_filter`"
          },
          "401": {
            "content": {
              "application/json": {
                "schema": {
                  "$ref": "#/components/schemas/ErrorEnvelope"
                }
              }
            },
            "description": "Error: `partner.invalid_key`, `partner.invalid_signature`, `partner.timestamp_expired`, `partner.nonce_reused`"
          },
          "403": {
            "content": {
              "application/json": {
                "schema": {
                  "$ref": "#/components/schemas/ErrorEnvelope"
                }
              }
            },
            "description": "Error: `partner.ip_not_allowed`, `partner.scope_denied`, `partner.key_suspended`, `partner.key_revoked`, `org.suspended`"
          },
          "404": {
            "content": {
              "application/json": {
                "schema": {
                  "$ref": "#/components/schemas/ErrorEnvelope"
                }
              }
            },
            "description": "Error: `internal.not_found`"
          },
          "429": {
            "content": {
              "application/json": {
                "schema": {
                  "$ref": "#/components/schemas/ErrorEnvelope"
                }
              }
            },
            "description": "Error: `partner.rate_limited`"
          },
          "503": {
            "content": {
              "application/json": {
                "schema": {
                  "$ref": "#/components/schemas/ErrorEnvelope"
                }
              }
            },
            "description": "Error: `partner.endpoint_disabled`, `internal.service_unavailable`"
          }
        },
        "summary": "Get one eSIM",
        "tags": [
          "esims"
        ],
        "x-tke-modes": [
          "live",
          "test"
        ],
        "x-tke-owner-types": [
          "organization",
          "member"
        ]
      }
    },
    "/v1/esims/{id}/cancel": {
      "post": {
        "description": "Required scope: `esims:write`.",
        "operationId": "post_esims__id__cancel",
        "parameters": [
          {
            "description": "The stable ID of a sold eSIM (NOT the ICCID — an ICCID can be reused).",
            "in": "path",
            "name": "id",
            "required": true,
            "schema": {
              "type": "string"
            }
          },
          {
            "description": "A client-generated string — the same key + body executes exactly once (a retry returns the original result). Scoped by `api_key_id` (§11): two different keys = two separate commands.",
            "in": "header",
            "name": "Idempotency-Key",
            "required": true,
            "schema": {
              "type": "string"
            }
          }
        ],
        "responses": {
          "200": {
            "content": {
              "application/json": {
                "schema": {
                  "$ref": "#/components/schemas/CancelResponse"
                }
              }
            },
            "description": "Success"
          },
          "400": {
            "content": {
              "application/json": {
                "schema": {
                  "$ref": "#/components/schemas/ErrorEnvelope"
                }
              }
            },
            "description": "Error: `internal.validation_failed`"
          },
          "401": {
            "content": {
              "application/json": {
                "schema": {
                  "$ref": "#/components/schemas/ErrorEnvelope"
                }
              }
            },
            "description": "Error: `partner.invalid_key`, `partner.invalid_signature`, `partner.timestamp_expired`, `partner.nonce_reused`"
          },
          "403": {
            "content": {
              "application/json": {
                "schema": {
                  "$ref": "#/components/schemas/ErrorEnvelope"
                }
              }
            },
            "description": "Error: `partner.ip_not_allowed`, `partner.scope_denied`, `partner.key_suspended`, `partner.key_revoked`, `org.suspended`"
          },
          "409": {
            "content": {
              "application/json": {
                "schema": {
                  "$ref": "#/components/schemas/ErrorEnvelope"
                }
              }
            },
            "description": "Error: `esim.not_cancellable`, `idempotency.conflict`"
          },
          "422": {
            "content": {
              "application/json": {
                "schema": {
                  "$ref": "#/components/schemas/ErrorEnvelope"
                }
              }
            },
            "description": "Error: `capability.not_supported`"
          },
          "429": {
            "content": {
              "application/json": {
                "schema": {
                  "$ref": "#/components/schemas/ErrorEnvelope"
                }
              }
            },
            "description": "Error: `partner.rate_limited`"
          },
          "503": {
            "content": {
              "application/json": {
                "schema": {
                  "$ref": "#/components/schemas/ErrorEnvelope"
                }
              }
            },
            "description": "Error: `partner.endpoint_disabled`, `internal.service_unavailable`"
          }
        },
        "summary": "Cancel an unused eSIM",
        "tags": [
          "esims"
        ],
        "x-tke-modes": [
          "live",
          "test"
        ],
        "x-tke-owner-types": [
          "organization",
          "member"
        ]
      }
    },
    "/v1/esims/{id}/change-plan": {
      "post": {
        "description": "Required scope: `esims:write`.",
        "operationId": "post_esims__id__change-plan",
        "parameters": [
          {
            "description": "The stable ID of a sold eSIM (NOT the ICCID — an ICCID can be reused).",
            "in": "path",
            "name": "id",
            "required": true,
            "schema": {
              "type": "string"
            }
          },
          {
            "description": "A client-generated string — the same key + body executes exactly once (a retry returns the original result). Scoped by `api_key_id` (§11): two different keys = two separate commands.",
            "in": "header",
            "name": "Idempotency-Key",
            "required": true,
            "schema": {
              "type": "string"
            }
          }
        ],
        "requestBody": {
          "content": {
            "application/json": {
              "schema": {
                "$ref": "#/components/schemas/ChangePlanRequest"
              }
            }
          },
          "required": true
        },
        "responses": {
          "200": {
            "content": {
              "application/json": {
                "schema": {
                  "$ref": "#/components/schemas/ChangePlanResponse"
                }
              }
            },
            "description": "Success"
          },
          "400": {
            "content": {
              "application/json": {
                "schema": {
                  "$ref": "#/components/schemas/ErrorEnvelope"
                }
              }
            },
            "description": "Error: `internal.validation_failed`"
          },
          "401": {
            "content": {
              "application/json": {
                "schema": {
                  "$ref": "#/components/schemas/ErrorEnvelope"
                }
              }
            },
            "description": "Error: `partner.invalid_key`, `partner.invalid_signature`, `partner.timestamp_expired`, `partner.nonce_reused`"
          },
          "402": {
            "content": {
              "application/json": {
                "schema": {
                  "$ref": "#/components/schemas/ErrorEnvelope"
                }
              }
            },
            "description": "Error: `wallet.insufficient_balance`"
          },
          "403": {
            "content": {
              "application/json": {
                "schema": {
                  "$ref": "#/components/schemas/ErrorEnvelope"
                }
              }
            },
            "description": "Error: `partner.ip_not_allowed`, `partner.scope_denied`, `partner.key_suspended`, `partner.key_revoked`, `org.suspended`"
          },
          "404": {
            "content": {
              "application/json": {
                "schema": {
                  "$ref": "#/components/schemas/ErrorEnvelope"
                }
              }
            },
            "description": "Error: `partner.package_not_visible`"
          },
          "409": {
            "content": {
              "application/json": {
                "schema": {
                  "$ref": "#/components/schemas/ErrorEnvelope"
                }
              }
            },
            "description": "Error: `esim.topup_not_supported`, `esim.topup_provider_mismatch`, `esim.not_eligible`, `idempotency.conflict`"
          },
          "422": {
            "content": {
              "application/json": {
                "schema": {
                  "$ref": "#/components/schemas/ErrorEnvelope"
                }
              }
            },
            "description": "Error: `capability.not_supported`"
          },
          "429": {
            "content": {
              "application/json": {
                "schema": {
                  "$ref": "#/components/schemas/ErrorEnvelope"
                }
              }
            },
            "description": "Error: `partner.rate_limited`"
          },
          "503": {
            "content": {
              "application/json": {
                "schema": {
                  "$ref": "#/components/schemas/ErrorEnvelope"
                }
              }
            },
            "description": "Error: `partner.endpoint_disabled`, `internal.service_unavailable`"
          }
        },
        "summary": "Top up an eSIM",
        "tags": [
          "esims"
        ],
        "x-tke-modes": [
          "live",
          "test"
        ],
        "x-tke-owner-types": [
          "organization",
          "member"
        ]
      }
    },
    "/v1/esims/{id}/plan-options": {
      "get": {
        "description": "Required scope: `esims:read`.\n\nReturn valid top-up packages **FOR THIS EXACT eSIM**. The server uses `id` to find the supplier holding it, then returns only that supplier's packages. `/v1` never exposes `provider_id`, so this endpoint performs the filtering for you and makes selecting another supplier's package structurally impossible. Calling `POST /v1/esims/{id}/change-plan` directly with a package outside this list returns `409 esim.topup_provider_mismatch`.\n\nAn eSIM with no linked supplier (a plain imported record or missing source data) returns an EMPTY list (`total: 0`). This is a valid answer, not an error.",
        "operationId": "get_esims__id__plan-options",
        "parameters": [
          {
            "description": "The stable ID of a sold eSIM (NOT the ICCID — an ICCID can be reused).",
            "in": "path",
            "name": "id",
            "required": true,
            "schema": {
              "type": "string"
            }
          },
          {
            "description": "Records per page. Default 50, capped at 200 — sending a larger value is clamped to 200 (NOT an error).",
            "in": "query",
            "name": "page_size",
            "required": false,
            "schema": {
              "default": 50,
              "maximum": 200,
              "minimum": 1,
              "type": "integer"
            }
          },
          {
            "description": "Cursor for the next page — pass back the `next_cursor` value from the previous page VERBATIM. WARNING: a malformed cursor does NOT raise an error; the server just reads from the START of the list again (a deliberate choice so that changing the cursor encoding scheme never breaks a partner's running reconciliation loop). Consequence to know: an automated reconciliation loop that keeps sending a garbage cursor will loop forever on the first page instead of stopping — check that `next_cursor` differs from the cursor you just sent before looping again.",
            "in": "query",
            "name": "cursor",
            "required": false,
            "schema": {
              "type": "string"
            }
          }
        ],
        "responses": {
          "200": {
            "content": {
              "application/json": {
                "schema": {
                  "$ref": "#/components/schemas/PlanOptionsResponse"
                }
              }
            },
            "description": "Success"
          },
          "400": {
            "content": {
              "application/json": {
                "schema": {
                  "$ref": "#/components/schemas/ErrorEnvelope"
                }
              }
            },
            "description": "Error: `internal.validation_failed`"
          },
          "401": {
            "content": {
              "application/json": {
                "schema": {
                  "$ref": "#/components/schemas/ErrorEnvelope"
                }
              }
            },
            "description": "Error: `partner.invalid_key`, `partner.invalid_signature`, `partner.timestamp_expired`, `partner.nonce_reused`"
          },
          "403": {
            "content": {
              "application/json": {
                "schema": {
                  "$ref": "#/components/schemas/ErrorEnvelope"
                }
              }
            },
            "description": "Error: `partner.ip_not_allowed`, `partner.scope_denied`, `partner.key_suspended`, `partner.key_revoked`, `org.suspended`"
          },
          "404": {
            "content": {
              "application/json": {
                "schema": {
                  "$ref": "#/components/schemas/ErrorEnvelope"
                }
              }
            },
            "description": "Error: `internal.not_found`"
          },
          "429": {
            "content": {
              "application/json": {
                "schema": {
                  "$ref": "#/components/schemas/ErrorEnvelope"
                }
              }
            },
            "description": "Error: `partner.rate_limited`"
          },
          "503": {
            "content": {
              "application/json": {
                "schema": {
                  "$ref": "#/components/schemas/ErrorEnvelope"
                }
              }
            },
            "description": "Error: `partner.endpoint_disabled`, `internal.service_unavailable`"
          }
        },
        "summary": "Valid top-up packages for this eSIM",
        "tags": [
          "esims"
        ],
        "x-tke-modes": [
          "live",
          "test"
        ],
        "x-tke-owner-types": [
          "organization",
          "member"
        ]
      }
    },
    "/v1/esims/{id}/refund-requests": {
      "get": {
        "description": "Required scope: `esims:read`.\n\nAuthorized live member keys only. Return up to 50 recent refund requests for the member-owned eSIM, newest first; next_cursor and total are null. has_open_request indicates pending review. Organization and test keys receive capability.not_supported.",
        "operationId": "get_esims__id__refund-requests",
        "parameters": [
          {
            "description": "The stable ID of a sold eSIM (NOT the ICCID — an ICCID can be reused).",
            "in": "path",
            "name": "id",
            "required": true,
            "schema": {
              "type": "string"
            }
          }
        ],
        "responses": {
          "200": {
            "content": {
              "application/json": {
                "schema": {
                  "$ref": "#/components/schemas/MemberRefundRequestListResponse"
                }
              }
            },
            "description": "Success"
          },
          "400": {
            "content": {
              "application/json": {
                "schema": {
                  "$ref": "#/components/schemas/ErrorEnvelope"
                }
              }
            },
            "description": "Error: `internal.validation_failed`"
          },
          "401": {
            "content": {
              "application/json": {
                "schema": {
                  "$ref": "#/components/schemas/ErrorEnvelope"
                }
              }
            },
            "description": "Error: `partner.invalid_key`, `partner.invalid_signature`, `partner.timestamp_expired`, `partner.nonce_reused`"
          },
          "403": {
            "content": {
              "application/json": {
                "schema": {
                  "$ref": "#/components/schemas/ErrorEnvelope"
                }
              }
            },
            "description": "Error: `partner.ip_not_allowed`, `partner.scope_denied`, `partner.key_suspended`, `partner.key_revoked`, `org.suspended`"
          },
          "404": {
            "content": {
              "application/json": {
                "schema": {
                  "$ref": "#/components/schemas/ErrorEnvelope"
                }
              }
            },
            "description": "Error: `internal.not_found`"
          },
          "422": {
            "content": {
              "application/json": {
                "schema": {
                  "$ref": "#/components/schemas/ErrorEnvelope"
                }
              }
            },
            "description": "Error: `capability.not_supported`"
          },
          "429": {
            "content": {
              "application/json": {
                "schema": {
                  "$ref": "#/components/schemas/ErrorEnvelope"
                }
              }
            },
            "description": "Error: `partner.rate_limited`"
          },
          "503": {
            "content": {
              "application/json": {
                "schema": {
                  "$ref": "#/components/schemas/ErrorEnvelope"
                }
              }
            },
            "description": "Error: `partner.endpoint_disabled`, `internal.service_unavailable`"
          }
        },
        "summary": "eSIM refund request history (members only)",
        "tags": [
          "esims"
        ],
        "x-tke-modes": [
          "live"
        ],
        "x-tke-owner-types": [
          "member"
        ]
      },
      "post": {
        "description": "Required scope: `esims:write`.\n\nAuthorized live member keys only. Submit a refund request for that member’s eSIM with Idempotency-Key; no money moves until admin approval. Organization and test keys receive capability.not_supported. In test mode, cancel an eSIM to exercise a virtual-wallet refund.",
        "operationId": "post_esims__id__refund-requests",
        "parameters": [
          {
            "description": "The stable ID of a sold eSIM (NOT the ICCID — an ICCID can be reused).",
            "in": "path",
            "name": "id",
            "required": true,
            "schema": {
              "type": "string"
            }
          },
          {
            "description": "A client-generated string — the same key + body executes exactly once (a retry returns the original result). Scoped by `api_key_id` (§11): two different keys = two separate commands.",
            "in": "header",
            "name": "Idempotency-Key",
            "required": true,
            "schema": {
              "type": "string"
            }
          }
        ],
        "requestBody": {
          "content": {
            "application/json": {
              "schema": {
                "$ref": "#/components/schemas/MemberRefundRequestInput"
              }
            }
          },
          "required": true
        },
        "responses": {
          "200": {
            "content": {
              "application/json": {
                "schema": {
                  "$ref": "#/components/schemas/MemberRefundRequestResponse"
                }
              }
            },
            "description": "Success"
          },
          "400": {
            "content": {
              "application/json": {
                "schema": {
                  "$ref": "#/components/schemas/ErrorEnvelope"
                }
              }
            },
            "description": "Error: `internal.validation_failed`, `internal.validation_failed`"
          },
          "401": {
            "content": {
              "application/json": {
                "schema": {
                  "$ref": "#/components/schemas/ErrorEnvelope"
                }
              }
            },
            "description": "Error: `partner.invalid_key`, `partner.invalid_signature`, `partner.timestamp_expired`, `partner.nonce_reused`"
          },
          "403": {
            "content": {
              "application/json": {
                "schema": {
                  "$ref": "#/components/schemas/ErrorEnvelope"
                }
              }
            },
            "description": "Error: `partner.ip_not_allowed`, `partner.scope_denied`, `partner.key_suspended`, `partner.key_revoked`, `org.suspended`"
          },
          "404": {
            "content": {
              "application/json": {
                "schema": {
                  "$ref": "#/components/schemas/ErrorEnvelope"
                }
              }
            },
            "description": "Error: `internal.not_found`"
          },
          "409": {
            "content": {
              "application/json": {
                "schema": {
                  "$ref": "#/components/schemas/ErrorEnvelope"
                }
              }
            },
            "description": "Error: `esim.refund_not_eligible`, `esim.refund_request_pending`, `idempotency.conflict`"
          },
          "422": {
            "content": {
              "application/json": {
                "schema": {
                  "$ref": "#/components/schemas/ErrorEnvelope"
                }
              }
            },
            "description": "Error: `capability.not_supported`"
          },
          "429": {
            "content": {
              "application/json": {
                "schema": {
                  "$ref": "#/components/schemas/ErrorEnvelope"
                }
              }
            },
            "description": "Error: `partner.rate_limited`"
          },
          "503": {
            "content": {
              "application/json": {
                "schema": {
                  "$ref": "#/components/schemas/ErrorEnvelope"
                }
              }
            },
            "description": "Error: `partner.endpoint_disabled`, `internal.service_unavailable`"
          }
        },
        "summary": "Create an eSIM refund request (members only)",
        "tags": [
          "esims"
        ],
        "x-tke-modes": [
          "live"
        ],
        "x-tke-owner-types": [
          "member"
        ]
      }
    },
    "/v1/esims/{id}/usage": {
      "get": {
        "description": "Required scope: `esims:read`.\n\nRead an eSIM's used and remaining data, with the `updated_at` timestamp of the figures (the real freshness). For an eSIM from a supplier that is read on demand only (currently SkyFi), this call queues a scan and WAITS for the result for up to about 15 seconds, so the response can take up to ~15 seconds; the figures returned are those of the scan that just finished. If the scan fails, is within the 15-second spacing between scans of the same eSIM, or the wait times out, NO error is returned: the most recently stored figures are returned, so check `updated_at`. Other suppliers return stored figures that are refreshed periodically.",
        "operationId": "get_esims__id__usage",
        "parameters": [
          {
            "description": "The stable ID of a sold eSIM (NOT the ICCID — an ICCID can be reused).",
            "in": "path",
            "name": "id",
            "required": true,
            "schema": {
              "type": "string"
            }
          }
        ],
        "responses": {
          "200": {
            "content": {
              "application/json": {
                "schema": {
                  "$ref": "#/components/schemas/EsimUsage"
                }
              }
            },
            "description": "Success"
          },
          "400": {
            "content": {
              "application/json": {
                "schema": {
                  "$ref": "#/components/schemas/ErrorEnvelope"
                }
              }
            },
            "description": "Error: `internal.validation_failed`"
          },
          "401": {
            "content": {
              "application/json": {
                "schema": {
                  "$ref": "#/components/schemas/ErrorEnvelope"
                }
              }
            },
            "description": "Error: `partner.invalid_key`, `partner.invalid_signature`, `partner.timestamp_expired`, `partner.nonce_reused`"
          },
          "403": {
            "content": {
              "application/json": {
                "schema": {
                  "$ref": "#/components/schemas/ErrorEnvelope"
                }
              }
            },
            "description": "Error: `partner.ip_not_allowed`, `partner.scope_denied`, `partner.key_suspended`, `partner.key_revoked`, `org.suspended`"
          },
          "422": {
            "content": {
              "application/json": {
                "schema": {
                  "$ref": "#/components/schemas/ErrorEnvelope"
                }
              }
            },
            "description": "Error: `capability.not_supported`"
          },
          "429": {
            "content": {
              "application/json": {
                "schema": {
                  "$ref": "#/components/schemas/ErrorEnvelope"
                }
              }
            },
            "description": "Error: `partner.rate_limited`"
          },
          "503": {
            "content": {
              "application/json": {
                "schema": {
                  "$ref": "#/components/schemas/ErrorEnvelope"
                }
              }
            },
            "description": "Error: `partner.endpoint_disabled`, `internal.service_unavailable`"
          }
        },
        "summary": "Data used",
        "tags": [
          "esims"
        ],
        "x-tke-modes": [
          "live",
          "test"
        ],
        "x-tke-owner-types": [
          "organization",
          "member"
        ]
      }
    },
    "/v1/orders": {
      "get": {
        "description": "Required scope: `orders:read`.",
        "operationId": "get_orders",
        "parameters": [
          {
            "description": "Records per page. Default 50, capped at 200 — sending a larger value is clamped to 200 (NOT an error).",
            "in": "query",
            "name": "page_size",
            "required": false,
            "schema": {
              "default": 50,
              "maximum": 200,
              "minimum": 1,
              "type": "integer"
            }
          },
          {
            "description": "Filter by order status. A value outside the list → `internal.invalid_filter` (400).",
            "in": "query",
            "name": "status",
            "required": false,
            "schema": {
              "enum": [
                "completed",
                "pending_provider",
                "provider_failed",
                "cancelled"
              ],
              "type": "string"
            }
          },
          {
            "description": "Filter by the reference code you sent at commit time (EXACT match).",
            "in": "query",
            "name": "external_ref",
            "required": false,
            "schema": {
              "type": "string"
            }
          },
          {
            "description": "ISO-8601 UTC. Returns every record with `updated_at >= updated_since`, sorted ascending — this is the standard PULL-BASED RECONCILIATION path (§8.5): even a missed webhook can still be reconstructed from state. Wrong format → `internal.invalid_filter` (400).",
            "in": "query",
            "name": "updated_since",
            "required": false,
            "schema": {
              "format": "date-time",
              "type": "string"
            }
          },
          {
            "description": "Cursor for the next page — pass back the `next_cursor` value from the previous page VERBATIM. WARNING: a malformed cursor does NOT raise an error; the server just reads from the START of the list again (a deliberate choice so that changing the cursor encoding scheme never breaks a partner's running reconciliation loop). Consequence to know: an automated reconciliation loop that keeps sending a garbage cursor will loop forever on the first page instead of stopping — check that `next_cursor` differs from the cursor you just sent before looping again.",
            "in": "query",
            "name": "cursor",
            "required": false,
            "schema": {
              "type": "string"
            }
          }
        ],
        "responses": {
          "200": {
            "content": {
              "application/json": {
                "schema": {
                  "$ref": "#/components/schemas/OrderListResponse"
                }
              }
            },
            "description": "Success"
          },
          "400": {
            "content": {
              "application/json": {
                "schema": {
                  "$ref": "#/components/schemas/ErrorEnvelope"
                }
              }
            },
            "description": "Error: `internal.validation_failed`, `internal.invalid_filter`"
          },
          "401": {
            "content": {
              "application/json": {
                "schema": {
                  "$ref": "#/components/schemas/ErrorEnvelope"
                }
              }
            },
            "description": "Error: `partner.invalid_key`, `partner.invalid_signature`, `partner.timestamp_expired`, `partner.nonce_reused`"
          },
          "403": {
            "content": {
              "application/json": {
                "schema": {
                  "$ref": "#/components/schemas/ErrorEnvelope"
                }
              }
            },
            "description": "Error: `partner.ip_not_allowed`, `partner.scope_denied`, `partner.key_suspended`, `partner.key_revoked`, `org.suspended`"
          },
          "429": {
            "content": {
              "application/json": {
                "schema": {
                  "$ref": "#/components/schemas/ErrorEnvelope"
                }
              }
            },
            "description": "Error: `partner.rate_limited`"
          },
          "503": {
            "content": {
              "application/json": {
                "schema": {
                  "$ref": "#/components/schemas/ErrorEnvelope"
                }
              }
            },
            "description": "Error: `partner.endpoint_disabled`, `internal.service_unavailable`"
          }
        },
        "summary": "List orders (pull-based reconciliation)",
        "tags": [
          "orders"
        ],
        "x-tke-modes": [
          "live",
          "test"
        ],
        "x-tke-owner-types": [
          "organization",
          "member"
        ]
      },
      "post": {
        "description": "Required scope: `orders:write`.",
        "operationId": "post_orders",
        "parameters": [
          {
            "description": "A client-generated string — the same key + body executes exactly once (a retry returns the original result). Scoped by `api_key_id` (§11): two different keys = two separate commands.",
            "in": "header",
            "name": "Idempotency-Key",
            "required": true,
            "schema": {
              "type": "string"
            }
          }
        ],
        "requestBody": {
          "content": {
            "application/json": {
              "schema": {
                "$ref": "#/components/schemas/CreateOrderRequest"
              }
            }
          },
          "required": true
        },
        "responses": {
          "200": {
            "content": {
              "application/json": {
                "schema": {
                  "$ref": "#/components/schemas/OrderResponse"
                }
              }
            },
            "description": "A replay of a previously committed command: same `Idempotency-Key` + same body within 24h. The body is IDENTICAL to the first call, money is NOT charged a second time, and the header `Idempotent-Replayed: true` is added. A NEW commit returns `201` — don't branch on success by status code."
          },
          "201": {
            "content": {
              "application/json": {
                "schema": {
                  "$ref": "#/components/schemas/OrderResponse"
                }
              }
            },
            "description": "Success"
          },
          "400": {
            "content": {
              "application/json": {
                "schema": {
                  "$ref": "#/components/schemas/ErrorEnvelope"
                }
              }
            },
            "description": "Error: `internal.validation_failed`, `internal.invalid_filter`"
          },
          "401": {
            "content": {
              "application/json": {
                "schema": {
                  "$ref": "#/components/schemas/ErrorEnvelope"
                }
              }
            },
            "description": "Error: `partner.invalid_key`, `partner.invalid_signature`, `partner.timestamp_expired`, `partner.nonce_reused`"
          },
          "402": {
            "content": {
              "application/json": {
                "schema": {
                  "$ref": "#/components/schemas/ErrorEnvelope"
                }
              }
            },
            "description": "Error: `wallet.insufficient_balance`"
          },
          "403": {
            "content": {
              "application/json": {
                "schema": {
                  "$ref": "#/components/schemas/ErrorEnvelope"
                }
              }
            },
            "description": "Error: `partner.ip_not_allowed`, `partner.scope_denied`, `partner.key_suspended`, `partner.key_revoked`, `org.suspended`"
          },
          "404": {
            "content": {
              "application/json": {
                "schema": {
                  "$ref": "#/components/schemas/ErrorEnvelope"
                }
              }
            },
            "description": "Error: `partner.package_not_visible`"
          },
          "409": {
            "content": {
              "application/json": {
                "schema": {
                  "$ref": "#/components/schemas/ErrorEnvelope"
                }
              }
            },
            "description": "Error: `esim.out_of_stock`, `idempotency.conflict`"
          },
          "422": {
            "content": {
              "application/json": {
                "schema": {
                  "$ref": "#/components/schemas/ErrorEnvelope"
                }
              }
            },
            "description": "Error: `invoice.buyer_invalid`"
          },
          "429": {
            "content": {
              "application/json": {
                "schema": {
                  "$ref": "#/components/schemas/ErrorEnvelope"
                }
              }
            },
            "description": "Error: `partner.rate_limited`"
          },
          "503": {
            "content": {
              "application/json": {
                "schema": {
                  "$ref": "#/components/schemas/ErrorEnvelope"
                }
              }
            },
            "description": "Error: `partner.endpoint_disabled`, `internal.service_unavailable`"
          }
        },
        "summary": "Create order (direct purchase)",
        "tags": [
          "orders"
        ],
        "x-tke-modes": [
          "live",
          "test"
        ],
        "x-tke-owner-types": [
          "organization",
          "member"
        ]
      }
    },
    "/v1/orders/bulk": {
      "post": {
        "description": "Required scope: `orders:write`.\n\nPlace a **multi-line batch**: each line creates ONE independent order through the same quote→commit path as `POST /v1/orders`.\n\n**PARTIAL SUCCESS**: failed lines do not block others, and **only successful lines are charged**. Business errors (stock, balance, unavailable package, abuse limits) appear in `lines[].error` without changing the batch HTTP status. If the balance runs out, later lines are still attempted because a cheaper line may succeed.\n\n**Status** is always `200`: the response mixes per-line results rather than creating one resource, and the batch finishes within the request, so neither `201` nor `202` plus polling applies.\n\n**Retry or interruption**: the SAME `Idempotency-Key` and SAME body reload the old batch, preserve completed line results, and run only remaining `pending` lines, without another charge. If no line ran, the response adds `Idempotent-Replayed: true`. Same key with another body returns `idempotency.conflict`.\n\n**Repeating the same cart within 10 minutes**: identical batch content from one organization is treated as an accidental repeat, even with different `Idempotency-Key` values. The old batch is returned with `Idempotent-Replayed: true`, without another charge. To intentionally order an identical cart for a second customer, **change `external_ref`**: it belongs to the signed body, makes the commands distinct, and identifies each order during reconciliation. A completely failed batch (no orders created) is never blocked, and after 10 minutes a new order is allowed.\n\nPer-line `external_ref` is echoed in `lines[].external_ref`; the batch reference is echoed at the response root.\n\nThe response contains NO LPA/QR. Fetch eSIMs through `GET /v1/orders/{order_id}` for each `order_id`.",
        "operationId": "post_orders_bulk",
        "parameters": [
          {
            "description": "A client-generated string — the same key + body executes exactly once (a retry returns the original result). Scoped by `api_key_id` (§11): two different keys = two separate commands.",
            "in": "header",
            "name": "Idempotency-Key",
            "required": true,
            "schema": {
              "type": "string"
            }
          }
        ],
        "requestBody": {
          "content": {
            "application/json": {
              "schema": {
                "$ref": "#/components/schemas/BulkRequest"
              }
            }
          },
          "required": true
        },
        "responses": {
          "200": {
            "content": {
              "application/json": {
                "schema": {
                  "$ref": "#/components/schemas/BulkBatchResponse"
                }
              }
            },
            "description": "Success"
          },
          "400": {
            "content": {
              "application/json": {
                "schema": {
                  "$ref": "#/components/schemas/ErrorEnvelope"
                }
              }
            },
            "description": "Error: `internal.validation_failed`"
          },
          "401": {
            "content": {
              "application/json": {
                "schema": {
                  "$ref": "#/components/schemas/ErrorEnvelope"
                }
              }
            },
            "description": "Error: `partner.invalid_key`, `partner.invalid_signature`, `partner.timestamp_expired`, `partner.nonce_reused`"
          },
          "403": {
            "content": {
              "application/json": {
                "schema": {
                  "$ref": "#/components/schemas/ErrorEnvelope"
                }
              }
            },
            "description": "Error: `partner.ip_not_allowed`, `partner.scope_denied`, `partner.key_suspended`, `partner.key_revoked`, `org.suspended`"
          },
          "404": {
            "content": {
              "application/json": {
                "schema": {
                  "$ref": "#/components/schemas/ErrorEnvelope"
                }
              }
            },
            "description": "Error: `partner.package_not_visible`"
          },
          "409": {
            "content": {
              "application/json": {
                "schema": {
                  "$ref": "#/components/schemas/ErrorEnvelope"
                }
              }
            },
            "description": "Error: `idempotency.conflict`"
          },
          "422": {
            "content": {
              "application/json": {
                "schema": {
                  "$ref": "#/components/schemas/ErrorEnvelope"
                }
              }
            },
            "description": "Error: `internal.limit_exceeded`"
          },
          "429": {
            "content": {
              "application/json": {
                "schema": {
                  "$ref": "#/components/schemas/ErrorEnvelope"
                }
              }
            },
            "description": "Error: `partner.rate_limited`"
          },
          "503": {
            "content": {
              "application/json": {
                "schema": {
                  "$ref": "#/components/schemas/ErrorEnvelope"
                }
              }
            },
            "description": "Error: `partner.endpoint_disabled`, `internal.service_unavailable`"
          }
        },
        "summary": "Place a multi-line batch (one order per line)",
        "tags": [
          "orders"
        ],
        "x-tke-modes": [
          "live",
          "test"
        ],
        "x-tke-owner-types": [
          "organization"
        ]
      }
    },
    "/v1/orders/bulk/{id}": {
      "get": {
        "description": "Required scope: `orders:read`.\n\nRe-read per-line results of a placed bulk batch — recovery path when original response is lost without resending body. Batch belonging to other org or other environment (live ⇄ testmode) → `404`.",
        "operationId": "get_orders_bulk__id_",
        "parameters": [
          {
            "description": "The batch ID (UUID) returned by POST /v1/orders/bulk.",
            "in": "path",
            "name": "id",
            "required": true,
            "schema": {
              "type": "string"
            }
          }
        ],
        "responses": {
          "200": {
            "content": {
              "application/json": {
                "schema": {
                  "$ref": "#/components/schemas/BulkBatchResponse"
                }
              }
            },
            "description": "Success"
          },
          "400": {
            "content": {
              "application/json": {
                "schema": {
                  "$ref": "#/components/schemas/ErrorEnvelope"
                }
              }
            },
            "description": "Error: `internal.validation_failed`"
          },
          "401": {
            "content": {
              "application/json": {
                "schema": {
                  "$ref": "#/components/schemas/ErrorEnvelope"
                }
              }
            },
            "description": "Error: `partner.invalid_key`, `partner.invalid_signature`, `partner.timestamp_expired`, `partner.nonce_reused`"
          },
          "403": {
            "content": {
              "application/json": {
                "schema": {
                  "$ref": "#/components/schemas/ErrorEnvelope"
                }
              }
            },
            "description": "Error: `partner.ip_not_allowed`, `partner.scope_denied`, `partner.key_suspended`, `partner.key_revoked`, `org.suspended`"
          },
          "404": {
            "content": {
              "application/json": {
                "schema": {
                  "$ref": "#/components/schemas/ErrorEnvelope"
                }
              }
            },
            "description": "Error: `internal.not_found`"
          },
          "429": {
            "content": {
              "application/json": {
                "schema": {
                  "$ref": "#/components/schemas/ErrorEnvelope"
                }
              }
            },
            "description": "Error: `partner.rate_limited`"
          },
          "503": {
            "content": {
              "application/json": {
                "schema": {
                  "$ref": "#/components/schemas/ErrorEnvelope"
                }
              }
            },
            "description": "Error: `partner.endpoint_disabled`, `internal.service_unavailable`"
          }
        },
        "summary": "Result of a batch order",
        "tags": [
          "orders"
        ],
        "x-tke-modes": [
          "live",
          "test"
        ],
        "x-tke-owner-types": [
          "organization"
        ]
      }
    },
    "/v1/orders/{id}": {
      "get": {
        "description": "Required scope: `orders:read`.",
        "operationId": "get_orders__id_",
        "parameters": [
          {
            "description": "The order ID (UUID).",
            "in": "path",
            "name": "id",
            "required": true,
            "schema": {
              "type": "string"
            }
          },
          {
            "description": "Ask for extra blocks, comma-separated. `lpa` = return the `install` block (the eSIM activation code); `qr` = also return `install.qr_code_base64` (detail endpoints only).\n\n**RETURNING THE CODE IS DISCLOSING IT.** Asking for `lpa` PERMANENTLY closes the cancellation window of every eSIM in the response (`cancellable.allowed` becomes `false`, `reason = code_disclosed`) — once the code has left our systems it cannot be recalled. Ask for it when you deliver to the customer right away; skip it when you order ahead and deliver later, and you keep the right to cancel. An unknown value → `internal.invalid_filter` (400, `field: \"include\"`).",
            "in": "query",
            "name": "include",
            "required": false,
            "schema": {
              "example": "lpa",
              "type": "string"
            }
          }
        ],
        "responses": {
          "200": {
            "content": {
              "application/json": {
                "schema": {
                  "$ref": "#/components/schemas/OrderResponse"
                }
              }
            },
            "description": "Success"
          },
          "400": {
            "content": {
              "application/json": {
                "schema": {
                  "$ref": "#/components/schemas/ErrorEnvelope"
                }
              }
            },
            "description": "Error: `internal.validation_failed`, `internal.invalid_filter`"
          },
          "401": {
            "content": {
              "application/json": {
                "schema": {
                  "$ref": "#/components/schemas/ErrorEnvelope"
                }
              }
            },
            "description": "Error: `partner.invalid_key`, `partner.invalid_signature`, `partner.timestamp_expired`, `partner.nonce_reused`"
          },
          "403": {
            "content": {
              "application/json": {
                "schema": {
                  "$ref": "#/components/schemas/ErrorEnvelope"
                }
              }
            },
            "description": "Error: `partner.ip_not_allowed`, `partner.scope_denied`, `partner.key_suspended`, `partner.key_revoked`, `org.suspended`"
          },
          "404": {
            "content": {
              "application/json": {
                "schema": {
                  "$ref": "#/components/schemas/ErrorEnvelope"
                }
              }
            },
            "description": "Error: `internal.not_found`"
          },
          "429": {
            "content": {
              "application/json": {
                "schema": {
                  "$ref": "#/components/schemas/ErrorEnvelope"
                }
              }
            },
            "description": "Error: `partner.rate_limited`"
          },
          "503": {
            "content": {
              "application/json": {
                "schema": {
                  "$ref": "#/components/schemas/ErrorEnvelope"
                }
              }
            },
            "description": "Error: `partner.endpoint_disabled`, `internal.service_unavailable`"
          }
        },
        "summary": "Order details + eSIMs",
        "tags": [
          "orders"
        ],
        "x-tke-modes": [
          "live",
          "test"
        ],
        "x-tke-owner-types": [
          "organization",
          "member"
        ]
      }
    },
    "/v1/orders/{id}/resend": {
      "post": {
        "description": "Required scope: `orders:write`.",
        "operationId": "post_orders__id__resend",
        "parameters": [
          {
            "description": "The order ID (UUID).",
            "in": "path",
            "name": "id",
            "required": true,
            "schema": {
              "type": "string"
            }
          }
        ],
        "responses": {
          "200": {
            "content": {
              "application/json": {
                "schema": {
                  "$ref": "#/components/schemas/ResendResponse"
                }
              }
            },
            "description": "Success"
          },
          "400": {
            "content": {
              "application/json": {
                "schema": {
                  "$ref": "#/components/schemas/ErrorEnvelope"
                }
              }
            },
            "description": "Error: `internal.validation_failed`"
          },
          "401": {
            "content": {
              "application/json": {
                "schema": {
                  "$ref": "#/components/schemas/ErrorEnvelope"
                }
              }
            },
            "description": "Error: `partner.invalid_key`, `partner.invalid_signature`, `partner.timestamp_expired`, `partner.nonce_reused`"
          },
          "403": {
            "content": {
              "application/json": {
                "schema": {
                  "$ref": "#/components/schemas/ErrorEnvelope"
                }
              }
            },
            "description": "Error: `partner.ip_not_allowed`, `partner.scope_denied`, `partner.key_suspended`, `partner.key_revoked`, `org.suspended`"
          },
          "409": {
            "content": {
              "application/json": {
                "schema": {
                  "$ref": "#/components/schemas/ErrorEnvelope"
                }
              }
            },
            "description": "Error: `order.not_deliverable`"
          },
          "429": {
            "content": {
              "application/json": {
                "schema": {
                  "$ref": "#/components/schemas/ErrorEnvelope"
                }
              }
            },
            "description": "Error: `partner.rate_limited`"
          },
          "503": {
            "content": {
              "application/json": {
                "schema": {
                  "$ref": "#/components/schemas/ErrorEnvelope"
                }
              }
            },
            "description": "Error: `partner.endpoint_disabled`, `internal.service_unavailable`"
          }
        },
        "summary": "Resend the order's email/QR",
        "tags": [
          "orders"
        ],
        "x-tke-modes": [
          "live",
          "test"
        ],
        "x-tke-owner-types": [
          "organization",
          "member"
        ]
      }
    },
    "/v1/testmode/esims/{id}/simulate": {
      "post": {
        "description": "Required scope: `esims:write`.\n\nTest keys tke_test_ or tke_member_test_ only. Apply a scenario to the owner’s test eSIM: used data, installation state, expiry, throttling or simulated provider rejection of the next operation. The body must contain at least one field; an empty body returns 422 testmode.invalid_scenario. Read the result through GET /v1/esims/{id} and /usage as in live mode. Plan-change and cancellation policies are shared with live mode; no live supplier is called.",
        "operationId": "post_testmode_esims__id__simulate",
        "parameters": [
          {
            "description": "The stable ID of a sold eSIM (NOT the ICCID — an ICCID can be reused).",
            "in": "path",
            "name": "id",
            "required": true,
            "schema": {
              "type": "string"
            }
          }
        ],
        "requestBody": {
          "content": {
            "application/json": {
              "schema": {
                "$ref": "#/components/schemas/TestModeSimulateRequest"
              }
            }
          },
          "required": true
        },
        "responses": {
          "200": {
            "content": {
              "application/json": {
                "schema": {
                  "$ref": "#/components/schemas/TestModeEsimState"
                }
              }
            },
            "description": "Success"
          },
          "400": {
            "content": {
              "application/json": {
                "schema": {
                  "$ref": "#/components/schemas/ErrorEnvelope"
                }
              }
            },
            "description": "Error: `internal.validation_failed`"
          },
          "401": {
            "content": {
              "application/json": {
                "schema": {
                  "$ref": "#/components/schemas/ErrorEnvelope"
                }
              }
            },
            "description": "Error: `partner.invalid_key`, `partner.invalid_signature`, `partner.timestamp_expired`, `partner.nonce_reused`"
          },
          "403": {
            "content": {
              "application/json": {
                "schema": {
                  "$ref": "#/components/schemas/ErrorEnvelope"
                }
              }
            },
            "description": "Error: `partner.ip_not_allowed`, `partner.scope_denied`, `partner.key_suspended`, `partner.key_revoked`, `org.suspended`, `testmode.live_key_forbidden`"
          },
          "404": {
            "content": {
              "application/json": {
                "schema": {
                  "$ref": "#/components/schemas/ErrorEnvelope"
                }
              }
            },
            "description": "Error: `internal.not_found`"
          },
          "422": {
            "content": {
              "application/json": {
                "schema": {
                  "$ref": "#/components/schemas/ErrorEnvelope"
                }
              }
            },
            "description": "Error: `testmode.invalid_scenario`"
          },
          "429": {
            "content": {
              "application/json": {
                "schema": {
                  "$ref": "#/components/schemas/ErrorEnvelope"
                }
              }
            },
            "description": "Error: `partner.rate_limited`"
          },
          "503": {
            "content": {
              "application/json": {
                "schema": {
                  "$ref": "#/components/schemas/ErrorEnvelope"
                }
              }
            },
            "description": "Error: `partner.endpoint_disabled`, `internal.service_unavailable`"
          }
        },
        "summary": "Simulate an eSIM test mode lifecycle (test keys only)",
        "tags": [
          "testmode"
        ],
        "x-tke-modes": [
          "test"
        ],
        "x-tke-owner-types": [
          "organization",
          "member"
        ]
      }
    },
    "/v1/testmode/reset": {
      "post": {
        "description": "Required scope: `orders:write`.",
        "operationId": "post_testmode_reset",
        "parameters": [
          {
            "in": "header",
            "name": "Idempotency-Key",
            "required": false,
            "schema": {
              "type": "string"
            }
          }
        ],
        "requestBody": {
          "content": {
            "application/json": {
              "schema": {
                "$ref": "#/components/schemas/TestModeResetInput"
              }
            }
          },
          "required": false
        },
        "responses": {
          "200": {
            "content": {
              "application/json": {
                "schema": {
                  "$ref": "#/components/schemas/TestModeReset"
                }
              }
            },
            "description": "Success"
          },
          "400": {
            "content": {
              "application/json": {
                "schema": {
                  "$ref": "#/components/schemas/ErrorEnvelope"
                }
              }
            },
            "description": "Error: `internal.validation_failed`"
          },
          "401": {
            "content": {
              "application/json": {
                "schema": {
                  "$ref": "#/components/schemas/ErrorEnvelope"
                }
              }
            },
            "description": "Error: `partner.invalid_key`, `partner.invalid_signature`, `partner.timestamp_expired`, `partner.nonce_reused`"
          },
          "403": {
            "content": {
              "application/json": {
                "schema": {
                  "$ref": "#/components/schemas/ErrorEnvelope"
                }
              }
            },
            "description": "Error: `partner.ip_not_allowed`, `partner.scope_denied`, `partner.key_suspended`, `partner.key_revoked`, `org.suspended`, `testmode.live_key_forbidden`"
          },
          "429": {
            "content": {
              "application/json": {
                "schema": {
                  "$ref": "#/components/schemas/ErrorEnvelope"
                }
              }
            },
            "description": "Error: `partner.rate_limited`"
          },
          "503": {
            "content": {
              "application/json": {
                "schema": {
                  "$ref": "#/components/schemas/ErrorEnvelope"
                }
              }
            },
            "description": "Error: `partner.endpoint_disabled`, `internal.service_unavailable`"
          }
        },
        "summary": "Reset test mode data (test keys only)",
        "tags": [
          "testmode"
        ],
        "x-tke-idempotency-owner-types": [
          "member"
        ],
        "x-tke-modes": [
          "test"
        ],
        "x-tke-owner-types": [
          "organization",
          "member"
        ],
        "x-tke-required-body-owner-types": [
          "member"
        ]
      }
    },
    "/v1/testmode/seed": {
      "post": {
        "description": "Required scope: `orders:write`.",
        "operationId": "post_testmode_seed",
        "parameters": [
          {
            "in": "header",
            "name": "Idempotency-Key",
            "required": false,
            "schema": {
              "type": "string"
            }
          }
        ],
        "responses": {
          "200": {
            "content": {
              "application/json": {
                "schema": {
                  "$ref": "#/components/schemas/TestModeSeed"
                }
              }
            },
            "description": "Success"
          },
          "400": {
            "content": {
              "application/json": {
                "schema": {
                  "$ref": "#/components/schemas/ErrorEnvelope"
                }
              }
            },
            "description": "Error: `internal.validation_failed`"
          },
          "401": {
            "content": {
              "application/json": {
                "schema": {
                  "$ref": "#/components/schemas/ErrorEnvelope"
                }
              }
            },
            "description": "Error: `partner.invalid_key`, `partner.invalid_signature`, `partner.timestamp_expired`, `partner.nonce_reused`"
          },
          "403": {
            "content": {
              "application/json": {
                "schema": {
                  "$ref": "#/components/schemas/ErrorEnvelope"
                }
              }
            },
            "description": "Error: `partner.ip_not_allowed`, `partner.scope_denied`, `partner.key_suspended`, `partner.key_revoked`, `org.suspended`, `testmode.live_key_forbidden`"
          },
          "422": {
            "content": {
              "application/json": {
                "schema": {
                  "$ref": "#/components/schemas/ErrorEnvelope"
                }
              }
            },
            "description": "Error: `testmode.no_sellable_package`"
          },
          "429": {
            "content": {
              "application/json": {
                "schema": {
                  "$ref": "#/components/schemas/ErrorEnvelope"
                }
              }
            },
            "description": "Error: `partner.rate_limited`"
          },
          "503": {
            "content": {
              "application/json": {
                "schema": {
                  "$ref": "#/components/schemas/ErrorEnvelope"
                }
              }
            },
            "description": "Error: `partner.endpoint_disabled`, `internal.service_unavailable`"
          }
        },
        "summary": "Seed test mode sample data (test keys only)",
        "tags": [
          "testmode"
        ],
        "x-tke-idempotency-owner-types": [
          "member"
        ],
        "x-tke-modes": [
          "test"
        ],
        "x-tke-owner-types": [
          "organization",
          "member"
        ]
      }
    },
    "/v1/testmode/status": {
      "get": {
        "description": "Required scope: `esims:read`.",
        "operationId": "get_testmode_status",
        "responses": {
          "200": {
            "content": {
              "application/json": {
                "schema": {
                  "$ref": "#/components/schemas/TestModeStatus"
                }
              }
            },
            "description": "Success"
          },
          "400": {
            "content": {
              "application/json": {
                "schema": {
                  "$ref": "#/components/schemas/ErrorEnvelope"
                }
              }
            },
            "description": "Error: `internal.validation_failed`"
          },
          "401": {
            "content": {
              "application/json": {
                "schema": {
                  "$ref": "#/components/schemas/ErrorEnvelope"
                }
              }
            },
            "description": "Error: `partner.invalid_key`, `partner.invalid_signature`, `partner.timestamp_expired`, `partner.nonce_reused`"
          },
          "403": {
            "content": {
              "application/json": {
                "schema": {
                  "$ref": "#/components/schemas/ErrorEnvelope"
                }
              }
            },
            "description": "Error: `partner.ip_not_allowed`, `partner.scope_denied`, `partner.key_suspended`, `partner.key_revoked`, `org.suspended`, `testmode.live_key_forbidden`"
          },
          "429": {
            "content": {
              "application/json": {
                "schema": {
                  "$ref": "#/components/schemas/ErrorEnvelope"
                }
              }
            },
            "description": "Error: `partner.rate_limited`"
          },
          "503": {
            "content": {
              "application/json": {
                "schema": {
                  "$ref": "#/components/schemas/ErrorEnvelope"
                }
              }
            },
            "description": "Error: `partner.endpoint_disabled`, `internal.service_unavailable`"
          }
        },
        "summary": "Test mode overview (test keys only)",
        "tags": [
          "testmode"
        ],
        "x-tke-modes": [
          "test"
        ],
        "x-tke-owner-types": [
          "organization",
          "member"
        ]
      }
    },
    "/v1/testmode/webhooks/fail-next": {
      "post": {
        "description": "Required scope: `webhook:manage`.",
        "operationId": "post_testmode_webhooks_fail-next",
        "requestBody": {
          "content": {
            "application/json": {
              "schema": {
                "$ref": "#/components/schemas/TestModeFailNextRequest"
              }
            }
          },
          "required": true
        },
        "responses": {
          "200": {
            "content": {
              "application/json": {
                "schema": {
                  "$ref": "#/components/schemas/TestModeFailNextResponse"
                }
              }
            },
            "description": "Success"
          },
          "400": {
            "content": {
              "application/json": {
                "schema": {
                  "$ref": "#/components/schemas/ErrorEnvelope"
                }
              }
            },
            "description": "Error: `internal.validation_failed`, `internal.validation_failed`"
          },
          "401": {
            "content": {
              "application/json": {
                "schema": {
                  "$ref": "#/components/schemas/ErrorEnvelope"
                }
              }
            },
            "description": "Error: `partner.invalid_key`, `partner.invalid_signature`, `partner.timestamp_expired`, `partner.nonce_reused`"
          },
          "403": {
            "content": {
              "application/json": {
                "schema": {
                  "$ref": "#/components/schemas/ErrorEnvelope"
                }
              }
            },
            "description": "Error: `partner.ip_not_allowed`, `partner.scope_denied`, `partner.key_suspended`, `partner.key_revoked`, `org.suspended`, `testmode.live_key_forbidden`"
          },
          "404": {
            "content": {
              "application/json": {
                "schema": {
                  "$ref": "#/components/schemas/ErrorEnvelope"
                }
              }
            },
            "description": "Error: `internal.not_found`"
          },
          "429": {
            "content": {
              "application/json": {
                "schema": {
                  "$ref": "#/components/schemas/ErrorEnvelope"
                }
              }
            },
            "description": "Error: `partner.rate_limited`"
          },
          "503": {
            "content": {
              "application/json": {
                "schema": {
                  "$ref": "#/components/schemas/ErrorEnvelope"
                }
              }
            },
            "description": "Error: `partner.endpoint_disabled`, `internal.service_unavailable`"
          }
        },
        "summary": "Simulate next webhook failure (test keys only)",
        "tags": [
          "webhook"
        ],
        "x-tke-modes": [
          "test"
        ],
        "x-tke-owner-types": [
          "organization"
        ]
      }
    },
    "/v1/testmode/webhooks/fire": {
      "post": {
        "description": "Required scope: `webhook:manage`.",
        "operationId": "post_testmode_webhooks_fire",
        "requestBody": {
          "content": {
            "application/json": {
              "schema": {
                "$ref": "#/components/schemas/TestModeWebhookFireRequest"
              }
            }
          },
          "required": true
        },
        "responses": {
          "200": {
            "content": {
              "application/json": {
                "schema": {
                  "$ref": "#/components/schemas/SimulateResponse"
                }
              }
            },
            "description": "Success"
          },
          "400": {
            "content": {
              "application/json": {
                "schema": {
                  "$ref": "#/components/schemas/ErrorEnvelope"
                }
              }
            },
            "description": "Error: `internal.validation_failed`"
          },
          "401": {
            "content": {
              "application/json": {
                "schema": {
                  "$ref": "#/components/schemas/ErrorEnvelope"
                }
              }
            },
            "description": "Error: `partner.invalid_key`, `partner.invalid_signature`, `partner.timestamp_expired`, `partner.nonce_reused`"
          },
          "403": {
            "content": {
              "application/json": {
                "schema": {
                  "$ref": "#/components/schemas/ErrorEnvelope"
                }
              }
            },
            "description": "Error: `partner.ip_not_allowed`, `partner.scope_denied`, `partner.key_suspended`, `partner.key_revoked`, `org.suspended`, `testmode.live_key_forbidden`"
          },
          "404": {
            "content": {
              "application/json": {
                "schema": {
                  "$ref": "#/components/schemas/ErrorEnvelope"
                }
              }
            },
            "description": "Error: `webhook.not_configured`, `internal.not_found`"
          },
          "429": {
            "content": {
              "application/json": {
                "schema": {
                  "$ref": "#/components/schemas/ErrorEnvelope"
                }
              }
            },
            "description": "Error: `partner.rate_limited`"
          },
          "503": {
            "content": {
              "application/json": {
                "schema": {
                  "$ref": "#/components/schemas/ErrorEnvelope"
                }
              }
            },
            "description": "Error: `partner.endpoint_disabled`, `internal.service_unavailable`"
          }
        },
        "summary": "Fire a test webhook event (test keys only)",
        "tags": [
          "webhook"
        ],
        "x-tke-modes": [
          "test"
        ],
        "x-tke-owner-types": [
          "organization"
        ]
      }
    },
    "/v1/wallet/balance": {
      "get": {
        "description": "Required scope: `wallet:read`.",
        "operationId": "get_wallet_balance",
        "responses": {
          "200": {
            "content": {
              "application/json": {
                "schema": {
                  "$ref": "#/components/schemas/WalletBalance"
                }
              }
            },
            "description": "Success"
          },
          "400": {
            "content": {
              "application/json": {
                "schema": {
                  "$ref": "#/components/schemas/ErrorEnvelope"
                }
              }
            },
            "description": "Error: `internal.validation_failed`"
          },
          "401": {
            "content": {
              "application/json": {
                "schema": {
                  "$ref": "#/components/schemas/ErrorEnvelope"
                }
              }
            },
            "description": "Error: `partner.invalid_key`, `partner.invalid_signature`, `partner.timestamp_expired`, `partner.nonce_reused`"
          },
          "403": {
            "content": {
              "application/json": {
                "schema": {
                  "$ref": "#/components/schemas/ErrorEnvelope"
                }
              }
            },
            "description": "Error: `partner.ip_not_allowed`, `partner.scope_denied`, `partner.key_suspended`, `partner.key_revoked`, `org.suspended`"
          },
          "429": {
            "content": {
              "application/json": {
                "schema": {
                  "$ref": "#/components/schemas/ErrorEnvelope"
                }
              }
            },
            "description": "Error: `partner.rate_limited`"
          },
          "503": {
            "content": {
              "application/json": {
                "schema": {
                  "$ref": "#/components/schemas/ErrorEnvelope"
                }
              }
            },
            "description": "Error: `partner.endpoint_disabled`, `internal.service_unavailable`"
          }
        },
        "summary": "Wallet balance",
        "tags": [
          "wallet"
        ],
        "x-tke-modes": [
          "live",
          "test"
        ],
        "x-tke-owner-types": [
          "organization",
          "member"
        ]
      }
    },
    "/v1/webhooks": {
      "get": {
        "description": "Required scope: `webhook:manage`.\n\nThe webhook endpoints of the API key you are calling with. A key NEVER sees another key's endpoint — not even two keys of the same organization (§5.11: an endpoint is bound to an `api_key_id`; test and live never share one).\n\nNot configured yet → `200` with an EMPTY array, not `404`: the collection always exists, it just has no elements right now.",
        "operationId": "get_webhooks",
        "responses": {
          "200": {
            "content": {
              "application/json": {
                "schema": {
                  "$ref": "#/components/schemas/WebhookListResponse"
                }
              }
            },
            "description": "Success"
          },
          "400": {
            "content": {
              "application/json": {
                "schema": {
                  "$ref": "#/components/schemas/ErrorEnvelope"
                }
              }
            },
            "description": "Error: `internal.validation_failed`"
          },
          "401": {
            "content": {
              "application/json": {
                "schema": {
                  "$ref": "#/components/schemas/ErrorEnvelope"
                }
              }
            },
            "description": "Error: `partner.invalid_key`, `partner.invalid_signature`, `partner.timestamp_expired`, `partner.nonce_reused`"
          },
          "403": {
            "content": {
              "application/json": {
                "schema": {
                  "$ref": "#/components/schemas/ErrorEnvelope"
                }
              }
            },
            "description": "Error: `partner.ip_not_allowed`, `partner.scope_denied`, `partner.key_suspended`, `partner.key_revoked`, `org.suspended`"
          },
          "429": {
            "content": {
              "application/json": {
                "schema": {
                  "$ref": "#/components/schemas/ErrorEnvelope"
                }
              }
            },
            "description": "Error: `partner.rate_limited`"
          },
          "503": {
            "content": {
              "application/json": {
                "schema": {
                  "$ref": "#/components/schemas/ErrorEnvelope"
                }
              }
            },
            "description": "Error: `partner.endpoint_disabled`, `internal.service_unavailable`"
          }
        },
        "summary": "List webhook endpoints",
        "tags": [
          "webhook"
        ],
        "x-tke-modes": [
          "live",
          "test"
        ],
        "x-tke-owner-types": [
          "organization"
        ]
      },
      "post": {
        "description": "Required scope: `webhook:manage`.\n\nCreate a webhook endpoint for the API key you are calling with. Before saving, the server **POSTs a signed `ping` event** to the URL you sent (CHECK_HEALTH): no 2xx means NOTHING is saved and you get `422 webhook.health_check_failed`. The URL must be public `https` — pointing at an internal / metadata IP → `422 webhook.url_not_allowed`.\n\nThe `webhook_secret` in the response is shown **exactly once**: no endpoint can read it back. Lose it and you must rotate from the dashboard (the old secret stays valid in parallel for 24h, §8.4).\n\nThe key already has an endpoint → `409 webhook.already_configured`, **even when that endpoint is paused** (`is_active: false`). A paused endpoint is still the key's endpoint — its url, secret and log are all intact. To start over, `DELETE /v1/webhooks/{id}` and then create a new one.",
        "operationId": "post_webhooks",
        "requestBody": {
          "content": {
            "application/json": {
              "schema": {
                "$ref": "#/components/schemas/WebhookRequest"
              }
            }
          },
          "required": true
        },
        "responses": {
          "201": {
            "content": {
              "application/json": {
                "schema": {
                  "$ref": "#/components/schemas/CreateWebhookResponse"
                }
              }
            },
            "description": "Success"
          },
          "400": {
            "content": {
              "application/json": {
                "schema": {
                  "$ref": "#/components/schemas/ErrorEnvelope"
                }
              }
            },
            "description": "Error: `internal.validation_failed`"
          },
          "401": {
            "content": {
              "application/json": {
                "schema": {
                  "$ref": "#/components/schemas/ErrorEnvelope"
                }
              }
            },
            "description": "Error: `partner.invalid_key`, `partner.invalid_signature`, `partner.timestamp_expired`, `partner.nonce_reused`"
          },
          "403": {
            "content": {
              "application/json": {
                "schema": {
                  "$ref": "#/components/schemas/ErrorEnvelope"
                }
              }
            },
            "description": "Error: `partner.ip_not_allowed`, `partner.scope_denied`, `partner.key_suspended`, `partner.key_revoked`, `org.suspended`"
          },
          "409": {
            "content": {
              "application/json": {
                "schema": {
                  "$ref": "#/components/schemas/ErrorEnvelope"
                }
              }
            },
            "description": "Error: `webhook.already_configured`"
          },
          "422": {
            "content": {
              "application/json": {
                "schema": {
                  "$ref": "#/components/schemas/ErrorEnvelope"
                }
              }
            },
            "description": "Error: `webhook.health_check_failed`, `webhook.url_not_allowed`"
          },
          "429": {
            "content": {
              "application/json": {
                "schema": {
                  "$ref": "#/components/schemas/ErrorEnvelope"
                }
              }
            },
            "description": "Error: `partner.rate_limited`"
          },
          "503": {
            "content": {
              "application/json": {
                "schema": {
                  "$ref": "#/components/schemas/ErrorEnvelope"
                }
              }
            },
            "description": "Error: `partner.endpoint_disabled`, `internal.service_unavailable`"
          }
        },
        "summary": "Create a webhook endpoint",
        "tags": [
          "webhook"
        ],
        "x-tke-modes": [
          "live",
          "test"
        ],
        "x-tke-owner-types": [
          "organization"
        ]
      }
    },
    "/v1/webhooks/deliveries": {
      "get": {
        "description": "Required scope: `webhook:manage`.",
        "operationId": "get_webhooks_deliveries",
        "parameters": [
          {
            "description": "1-based page number. A page past the end of the data returns `items: []` + the real `total` (200, not 404).",
            "in": "query",
            "name": "page",
            "required": false,
            "schema": {
              "default": 1,
              "minimum": 1,
              "type": "integer"
            }
          },
          {
            "description": "Records per page. Default 50, capped at 200 — sending a larger value is clamped to 200 (NOT an error).",
            "in": "query",
            "name": "page_size",
            "required": false,
            "schema": {
              "default": 50,
              "maximum": 200,
              "minimum": 1,
              "type": "integer"
            }
          },
          {
            "description": "Filter by event type (see the webhook events catalog).",
            "in": "query",
            "name": "event_type",
            "required": false,
            "schema": {
              "type": "string"
            }
          },
          {
            "description": "Filter by delivery status. `?status=failed` is the filter used for pull-based webhook reconciliation (§13). An unknown value does NOT raise an error — the filter is simply IGNORED and the full log is returned.",
            "in": "query",
            "name": "status",
            "required": false,
            "schema": {
              "enum": [
                "pending",
                "delivered",
                "failed"
              ],
              "type": "string"
            }
          },
          {
            "description": "Every delivery attempt (including replays) of ONE event.",
            "in": "query",
            "name": "event_id",
            "required": false,
            "schema": {
              "format": "uuid",
              "type": "string"
            }
          }
        ],
        "responses": {
          "200": {
            "content": {
              "application/json": {
                "schema": {
                  "$ref": "#/components/schemas/DeliveryListResponse"
                }
              }
            },
            "description": "Success"
          },
          "400": {
            "content": {
              "application/json": {
                "schema": {
                  "$ref": "#/components/schemas/ErrorEnvelope"
                }
              }
            },
            "description": "Error: `internal.validation_failed`"
          },
          "401": {
            "content": {
              "application/json": {
                "schema": {
                  "$ref": "#/components/schemas/ErrorEnvelope"
                }
              }
            },
            "description": "Error: `partner.invalid_key`, `partner.invalid_signature`, `partner.timestamp_expired`, `partner.nonce_reused`"
          },
          "403": {
            "content": {
              "application/json": {
                "schema": {
                  "$ref": "#/components/schemas/ErrorEnvelope"
                }
              }
            },
            "description": "Error: `partner.ip_not_allowed`, `partner.scope_denied`, `partner.key_suspended`, `partner.key_revoked`, `org.suspended`"
          },
          "404": {
            "content": {
              "application/json": {
                "schema": {
                  "$ref": "#/components/schemas/ErrorEnvelope"
                }
              }
            },
            "description": "Error: `webhook.not_configured`"
          },
          "429": {
            "content": {
              "application/json": {
                "schema": {
                  "$ref": "#/components/schemas/ErrorEnvelope"
                }
              }
            },
            "description": "Error: `partner.rate_limited`"
          },
          "503": {
            "content": {
              "application/json": {
                "schema": {
                  "$ref": "#/components/schemas/ErrorEnvelope"
                }
              }
            },
            "description": "Error: `partner.endpoint_disabled`, `internal.service_unavailable`"
          }
        },
        "summary": "Webhook delivery log",
        "tags": [
          "webhook"
        ],
        "x-tke-modes": [
          "live",
          "test"
        ],
        "x-tke-owner-types": [
          "organization"
        ]
      }
    },
    "/v1/webhooks/deliveries/{id}/replay": {
      "post": {
        "description": "Required scope: `webhook:manage`.",
        "operationId": "post_webhooks_deliveries__id__replay",
        "parameters": [
          {
            "description": "The ID of one webhook delivery (`items[].id` from `GET /v1/webhooks/deliveries`).",
            "in": "path",
            "name": "id",
            "required": true,
            "schema": {
              "type": "string"
            }
          }
        ],
        "responses": {
          "200": {
            "content": {
              "application/json": {
                "schema": {
                  "$ref": "#/components/schemas/DeliveryReplayResponse"
                }
              }
            },
            "description": "Success"
          },
          "400": {
            "content": {
              "application/json": {
                "schema": {
                  "$ref": "#/components/schemas/ErrorEnvelope"
                }
              }
            },
            "description": "Error: `internal.validation_failed`"
          },
          "401": {
            "content": {
              "application/json": {
                "schema": {
                  "$ref": "#/components/schemas/ErrorEnvelope"
                }
              }
            },
            "description": "Error: `partner.invalid_key`, `partner.invalid_signature`, `partner.timestamp_expired`, `partner.nonce_reused`"
          },
          "403": {
            "content": {
              "application/json": {
                "schema": {
                  "$ref": "#/components/schemas/ErrorEnvelope"
                }
              }
            },
            "description": "Error: `partner.ip_not_allowed`, `partner.scope_denied`, `partner.key_suspended`, `partner.key_revoked`, `org.suspended`"
          },
          "404": {
            "content": {
              "application/json": {
                "schema": {
                  "$ref": "#/components/schemas/ErrorEnvelope"
                }
              }
            },
            "description": "Error: `webhook.delivery_not_found`, `webhook.not_configured`"
          },
          "429": {
            "content": {
              "application/json": {
                "schema": {
                  "$ref": "#/components/schemas/ErrorEnvelope"
                }
              }
            },
            "description": "Error: `partner.rate_limited`"
          },
          "503": {
            "content": {
              "application/json": {
                "schema": {
                  "$ref": "#/components/schemas/ErrorEnvelope"
                }
              }
            },
            "description": "Error: `partner.endpoint_disabled`, `internal.service_unavailable`"
          }
        },
        "summary": "Replay a webhook",
        "tags": [
          "webhook"
        ],
        "x-tke-modes": [
          "live",
          "test"
        ],
        "x-tke-owner-types": [
          "organization"
        ]
      }
    },
    "/v1/webhooks/{id}": {
      "delete": {
        "description": "Required scope: `webhook:manage`.\n\nRemove the endpoint: stop receiving any new events on the API key you are calling with.\n\n**What you lose**: the `webhook_secret` goes with the deleted endpoint. Re-creating via `POST /v1/webhooks` mints a NEW `id` and a NEW secret — your verify function must be updated. If you need a temporary stop that KEEPS the secret, use the \"Pause\" button in the dashboard, not this endpoint.\n\n**What you keep**: the full delivery log stays for reconciliation. But those deliveries belong to a removed endpoint, so they can no longer be replayed.\n\n**Events waiting on retry**: per §8.5, once an endpoint is removed its `pending` deliveries stop with reason `endpoint_disabled` and remain in the log.\n\nAn `id` that does not belong to the calling key, or one already removed → `404 webhook.not_found` (never a silent 200).",
        "operationId": "delete_webhooks__id_",
        "parameters": [
          {
            "description": "The webhook endpoint ID (the `id` returned by `POST /v1/webhooks` or `GET /v1/webhooks`).",
            "in": "path",
            "name": "id",
            "required": true,
            "schema": {
              "type": "string"
            }
          }
        ],
        "responses": {
          "200": {
            "content": {
              "application/json": {
                "schema": {
                  "$ref": "#/components/schemas/DeleteWebhookResponse"
                }
              }
            },
            "description": "Success"
          },
          "400": {
            "content": {
              "application/json": {
                "schema": {
                  "$ref": "#/components/schemas/ErrorEnvelope"
                }
              }
            },
            "description": "Error: `internal.validation_failed`"
          },
          "401": {
            "content": {
              "application/json": {
                "schema": {
                  "$ref": "#/components/schemas/ErrorEnvelope"
                }
              }
            },
            "description": "Error: `partner.invalid_key`, `partner.invalid_signature`, `partner.timestamp_expired`, `partner.nonce_reused`"
          },
          "403": {
            "content": {
              "application/json": {
                "schema": {
                  "$ref": "#/components/schemas/ErrorEnvelope"
                }
              }
            },
            "description": "Error: `partner.ip_not_allowed`, `partner.scope_denied`, `partner.key_suspended`, `partner.key_revoked`, `org.suspended`"
          },
          "404": {
            "content": {
              "application/json": {
                "schema": {
                  "$ref": "#/components/schemas/ErrorEnvelope"
                }
              }
            },
            "description": "Error: `webhook.not_found`"
          },
          "429": {
            "content": {
              "application/json": {
                "schema": {
                  "$ref": "#/components/schemas/ErrorEnvelope"
                }
              }
            },
            "description": "Error: `partner.rate_limited`"
          },
          "503": {
            "content": {
              "application/json": {
                "schema": {
                  "$ref": "#/components/schemas/ErrorEnvelope"
                }
              }
            },
            "description": "Error: `partner.endpoint_disabled`, `internal.service_unavailable`"
          }
        },
        "summary": "Remove a webhook endpoint (stop receiving new events)",
        "tags": [
          "webhook"
        ],
        "x-tke-modes": [
          "live",
          "test"
        ],
        "x-tke-owner-types": [
          "organization"
        ]
      },
      "get": {
        "description": "Required scope: `webhook:manage`.",
        "operationId": "get_webhooks__id_",
        "parameters": [
          {
            "description": "The webhook endpoint ID (the `id` returned by `POST /v1/webhooks` or `GET /v1/webhooks`).",
            "in": "path",
            "name": "id",
            "required": true,
            "schema": {
              "type": "string"
            }
          }
        ],
        "responses": {
          "200": {
            "content": {
              "application/json": {
                "schema": {
                  "$ref": "#/components/schemas/WebhookConfig"
                }
              }
            },
            "description": "Success"
          },
          "400": {
            "content": {
              "application/json": {
                "schema": {
                  "$ref": "#/components/schemas/ErrorEnvelope"
                }
              }
            },
            "description": "Error: `internal.validation_failed`"
          },
          "401": {
            "content": {
              "application/json": {
                "schema": {
                  "$ref": "#/components/schemas/ErrorEnvelope"
                }
              }
            },
            "description": "Error: `partner.invalid_key`, `partner.invalid_signature`, `partner.timestamp_expired`, `partner.nonce_reused`"
          },
          "403": {
            "content": {
              "application/json": {
                "schema": {
                  "$ref": "#/components/schemas/ErrorEnvelope"
                }
              }
            },
            "description": "Error: `partner.ip_not_allowed`, `partner.scope_denied`, `partner.key_suspended`, `partner.key_revoked`, `org.suspended`"
          },
          "404": {
            "content": {
              "application/json": {
                "schema": {
                  "$ref": "#/components/schemas/ErrorEnvelope"
                }
              }
            },
            "description": "Error: `webhook.not_found`"
          },
          "429": {
            "content": {
              "application/json": {
                "schema": {
                  "$ref": "#/components/schemas/ErrorEnvelope"
                }
              }
            },
            "description": "Error: `partner.rate_limited`"
          },
          "503": {
            "content": {
              "application/json": {
                "schema": {
                  "$ref": "#/components/schemas/ErrorEnvelope"
                }
              }
            },
            "description": "Error: `partner.endpoint_disabled`, `internal.service_unavailable`"
          }
        },
        "summary": "Get one webhook endpoint",
        "tags": [
          "webhook"
        ],
        "x-tke-modes": [
          "live",
          "test"
        ],
        "x-tke-owner-types": [
          "organization"
        ]
      },
      "put": {
        "description": "Required scope: `webhook:manage`.",
        "operationId": "put_webhooks__id_",
        "parameters": [
          {
            "description": "The webhook endpoint ID (the `id` returned by `POST /v1/webhooks` or `GET /v1/webhooks`).",
            "in": "path",
            "name": "id",
            "required": true,
            "schema": {
              "type": "string"
            }
          }
        ],
        "requestBody": {
          "content": {
            "application/json": {
              "schema": {
                "$ref": "#/components/schemas/WebhookRequest"
              }
            }
          },
          "required": true
        },
        "responses": {
          "200": {
            "content": {
              "application/json": {
                "schema": {
                  "$ref": "#/components/schemas/WebhookConfig"
                }
              }
            },
            "description": "Success"
          },
          "400": {
            "content": {
              "application/json": {
                "schema": {
                  "$ref": "#/components/schemas/ErrorEnvelope"
                }
              }
            },
            "description": "Error: `internal.validation_failed`"
          },
          "401": {
            "content": {
              "application/json": {
                "schema": {
                  "$ref": "#/components/schemas/ErrorEnvelope"
                }
              }
            },
            "description": "Error: `partner.invalid_key`, `partner.invalid_signature`, `partner.timestamp_expired`, `partner.nonce_reused`"
          },
          "403": {
            "content": {
              "application/json": {
                "schema": {
                  "$ref": "#/components/schemas/ErrorEnvelope"
                }
              }
            },
            "description": "Error: `partner.ip_not_allowed`, `partner.scope_denied`, `partner.key_suspended`, `partner.key_revoked`, `org.suspended`"
          },
          "404": {
            "content": {
              "application/json": {
                "schema": {
                  "$ref": "#/components/schemas/ErrorEnvelope"
                }
              }
            },
            "description": "Error: `webhook.not_found`"
          },
          "422": {
            "content": {
              "application/json": {
                "schema": {
                  "$ref": "#/components/schemas/ErrorEnvelope"
                }
              }
            },
            "description": "Error: `webhook.health_check_failed`, `webhook.url_not_allowed`"
          },
          "429": {
            "content": {
              "application/json": {
                "schema": {
                  "$ref": "#/components/schemas/ErrorEnvelope"
                }
              }
            },
            "description": "Error: `partner.rate_limited`"
          },
          "503": {
            "content": {
              "application/json": {
                "schema": {
                  "$ref": "#/components/schemas/ErrorEnvelope"
                }
              }
            },
            "description": "Error: `partner.endpoint_disabled`, `internal.service_unavailable`"
          }
        },
        "summary": "Update a webhook endpoint",
        "tags": [
          "webhook"
        ],
        "x-tke-modes": [
          "live",
          "test"
        ],
        "x-tke-owner-types": [
          "organization"
        ]
      }
    }
  },
  "security": [
    {
      "TkeHmac": []
    }
  ],
  "servers": [
    {
      "description": "Live",
      "url": "https://api.tongkhoesim.com"
    },
    {
      "description": "Test mode",
      "url": "https://api-testing.tongkhoesim.com"
    }
  ],
  "tags": [
    {
      "description": "The package catalog: ONE surface for both supply systems. The supply system is a FIELD (`market`: `domestic` | `international`) and a filter (`?market=`), no longer two separate paths.\n\nThe catalog carries NO stock concept (no figure, no `available`/`low`/`out_of_stock` level): packages simply appear here, and ordering one the supplier cannot fulfill returns a clear error from `POST /v1/orders` (`esim.out_of_stock`…) instead of the catalog announcing it upfront.\n\nOne cursor pagination scheme. `total` is present only when counting is cheap (filtering to the domestic system); otherwise it is `null`.",
      "name": "catalog"
    },
    {
      "description": "Place orders + reconcile",
      "name": "orders"
    },
    {
      "description": "eSIM records, usage, top-up, cancellation",
      "name": "esims"
    },
    {
      "description": "Wallet balance",
      "name": "wallet"
    },
    {
      "description": "Webhook configuration + delivery log + simulator",
      "name": "webhook"
    },
    {
      "description": "Use test keys tke_test_ or tke_member_test_ on the api-testing host only. Simulate the owner’s test eSIMs and data without live funds or suppliers. Member test mode does not support webhooks, bulk orders or refund-review requests.",
      "name": "testmode"
    }
  ]
}
