{
  "components": {
    "schemas": {
      "BulkBatchResponse": {
        "properties": {
          "data": {
            "description": "Résultat d’un lot. SUCCÈS PARTIEL : une ligne en échec ne bloque pas les autres ; seules les lignes `done` sont débitées. Aucun LPA/QR : lire GET /v1/orders/{id} pour chaque `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` : des lignes restent non exécutées après interruption. Rappelez avec le même `Idempotency-Key` pour les terminer.",
                "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": "Code d'erreur réel `domain.reason`, par exemple `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": "À partir de 1, dans l'ordre exact de l'envoi.",
            "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": "Référence du lot ENTIER, renvoyée dans la réponse du lot.",
            "example": "batch-2026-001",
            "nullable": true,
            "type": "string"
          },
          "items": {
            "description": "Chaque élément crée une commande indépendante. Limite configurable (100 par défaut) ; dépassement : `internal.limit_exceeded` (422).",
            "items": {
              "properties": {
                "external_ref": {
                  "description": "Référence de CETTE ligne, renvoyée à l’identique dans son résultat.",
                  "example": "po-1001",
                  "nullable": true,
                  "type": "string"
                },
                "package_code": {
                  "description": "Code de forfait de VOTRE catalogue, soit `public_sku`. IMMUABLE par forfait, même si le fournisseur change ; utilisable comme clé de rapprochement. L’exemple a le bon format mais ne fonctionne PAS : lire un vrai code via `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` : remboursement en attente d'approbation humaine, argent non transféré. `refunded.amount = 0` ne signifie pas absence de remboursement.",
                "type": "boolean"
              },
              "refunded": {
                "$ref": "#/components/schemas/Money",
                "description": "Montant réellement REVENU dans votre portefeuille lors de cette annulation. En production il vaut généralement `0` : consultez `refund_suggested` avant de conclure."
              },
              "status": {
                "example": "cancelled",
                "type": "string"
              }
            },
            "type": "object"
          },
          "ok": {
            "enum": [
              true
            ],
            "type": "boolean"
          }
        },
        "required": [
          "ok",
          "data"
        ],
        "type": "object"
      },
      "Cancellable": {
        "description": "Possibilité d'annuler l'eSIM et motif du refus ; à lire avant `POST /v1/esims/{order_item_id}/cancel`. Ce bloc est toujours présent, jamais `null` : c'est une réponse.",
        "properties": {
          "allowed": {
            "type": "boolean"
          },
          "disclosed_at": {
            "description": "Moment où le code a quitté nos systèmes (ISO-8601 UTC), `null` tant qu'il n'a été vu par personne. Trace utile en cas de contestation de la divulgation empêchant l'annulation.",
            "format": "date-time",
            "nullable": true,
            "type": "string"
          },
          "reason": {
            "description": "Motif du refus d'annulation : `code_disclosed` (code envoyé ou affiché), `wrong_status` (état incompatible), `expired` (forfait expiré), `not_supported` (capacité non déclarée). `null` si `allowed = true`. Même vocabulaire que `meta.reason` pour `esim.not_cancellable`.",
            "enum": [
              "code_disclosed",
              "wrong_status",
              "expired",
              "not_supported",
              null
            ],
            "nullable": true,
            "type": "string"
          }
        },
        "type": "object"
      },
      "ChangePlanRequest": {
        "properties": {
          "package_code": {
            "description": "Code de forfait de VOTRE catalogue, soit `public_sku`. IMMUABLE par forfait, même si le fournisseur change ; utilisable comme clé de rapprochement. L’exemple a le bon format mais ne fonctionne PAS : lire un vrai code via `GET /v1/catalog/packages`.",
            "example": "TKE-VN-7D-P4M2Q",
            "type": "string"
          }
        },
        "required": [
          "package_code"
        ],
        "type": "object"
      },
      "ChangePlanResponse": {
        "properties": {
          "data": {
            "description": "Résultat du changement de forfait eSIM.",
            "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": "Votre propre référence, restituée dans la réponse et les webhooks.",
            "example": "po-1001",
            "nullable": true,
            "type": "string"
          },
          "include": {
            "description": "Blocs supplémentaires : `[\"lpa\"]` ou `[\"lpa\",\"qr\"]`. Sans ce champ, `items[].install` reste `null` et chaque eSIM conserve son droit d'annulation. **RETOURNER LE CODE LE DIVULGUE** : demander `lpa` ferme la possibilité d'annulation dès la commande.",
            "example": [
              "lpa"
            ],
            "items": {
              "enum": [
                "lpa",
                "qr"
              ],
              "type": "string"
            },
            "nullable": true,
            "type": "array"
          },
          "invoice_buyer": {
            "$ref": "#/components/schemas/MemberInvoiceBuyer"
          },
          "items": {
            "description": "Forfaits et quantités à acheter (50 eSIM maximum).",
            "items": {
              "properties": {
                "package_code": {
                  "description": "Code de forfait de VOTRE catalogue, soit `public_sku`. IMMUABLE par forfait, même si le fournisseur change ; utilisable comme clé de rapprochement. L’exemple a le bon format mais ne fonctionne PAS : lire un vrai code via `GET /v1/catalog/packages`.",
                  "example": "TKE-VN-30D-X3K9F",
                  "type": "string"
                },
                "quantity": {
                  "description": "Quantité d’eSIM à acheter pour ce forfait (≥ 1).",
                  "example": 2,
                  "type": "integer"
                }
              },
              "required": [
                "package_code",
                "quantity"
              ],
              "type": "object"
            },
            "type": "array"
          }
        },
        "required": [
          "items"
        ],
        "type": "object"
      },
      "CreateWebhookResponse": {
        "properties": {
          "data": {
            "properties": {
              "active": {
                "description": "`false` : aucun nouvel événement accepté, après suspension dans le tableau de bord ou révocation de clé (arrêt dans la même transaction). Un `PUT /v1/webhooks/{id}` validé par CHECK_HEALTH le réactive.",
                "type": "boolean"
              },
              "created_at": {
                "format": "date-time",
                "type": "string"
              },
              "events": {
                "description": "Vide = tout recevoir.",
                "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": "ID de l’endpoint pour `/v1/webhooks/{id}`. Un NOUVEL ID est généré à chaque création : supprimer puis recréer produit un autre endpoint, pas la restauration de l’ancien.",
                "format": "uuid",
                "type": "string"
              },
              "last_delivery_at": {
                "description": "Dernier envoi via cet endpoint. `null` = jamais envoyé, distinct d’un échec d’envoi ; à vérifier d’abord lorsqu’un partenaire ne reçoit aucun webhook.",
                "format": "date-time",
                "nullable": true,
                "type": "string"
              },
              "mode": {
                "$ref": "#/components/schemas/ModeEcho"
              },
              "secret_last4": {
                "description": "Quatre derniers caractères du secret webhook, pour vérifier que votre secret correspond à celui utilisé pour signer sans le révéler. Le secret complet n’est affiché qu’UNE FOIS, à la création.",
                "type": "string"
              },
              "url": {
                "type": "string"
              },
              "webhook_secret": {
                "description": "Affiché UNE SEULE FOIS à la création (préfixe `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": "Identifiant de l’ÉVÉNEMENT. Un rejeu crée un NOUVEL envoi avec le MÊME `event_id` : dédupliquez sur `event_id`, pas sur l’`id` d’envoi.",
            "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": "Renvoyez le curseur SANS MODIFICATION pour charger la page suivante. `null` est le SEUL signal de fin : une page plus courte que `page_size` peut être suivie d’autres pages.",
                "nullable": true,
                "type": "string"
              },
              "page_size": {
                "description": "Nombre MAXIMUM d’enregistrements utilisé par le serveur pour cette page, déjà plafonné ; ce n’est pas le nombre réel d’éléments dans `items`.",
                "nullable": true,
                "type": "integer"
              },
              "total": {
                "description": "Nombre de lignes correspondant au filtre. `null` si le comptage coûte trop cher. Ne l’utilisez pas pour détecter la fin : seul `next_cursor` l’indique.",
                "format": "int64",
                "nullable": true,
                "type": "integer"
              },
              "warnings": {
                "description": "Explications de la liste ; vide si rien d'inhabituel. Codes fermés : `filtered_by_key_package_filters`, `filtered_by_provider_scope`, `no_price_for_org`, `price_filter_removed_rows`, `provider_disabled`, `capability_missing`. Ils indiquent restrictions de clé, périmètre fournisseur, absence de prix, filtre de prix, fournisseur désactivé ou capacité absente. `count` donne le nombre de lignes concernées, sinon `null`.",
                "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": "Identifiant de l’ÉVÉNEMENT. Un rejeu crée un NOUVEL envoi avec le MÊME `event_id` : dédupliquez sur `event_id`, pas sur l’`id` d’envoi.",
                "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": "Code `domain.reason` (voir le tableau des erreurs).",
                "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": "Votre référence de commande (`external_ref` envoyé à l'achat), pour rattacher l'eSIM à votre commande. `null` si absente.",
                "nullable": true,
                "type": "string"
              },
              "iccid": {
                "example": "8984012345678901234",
                "nullable": true,
                "type": "string"
              },
              "id": {
                "description": "ID de l’eSIM, utilisé dans tous les chemins `/v1/esims/{id}/…` et comme `order_item_id` des webhooks. Avec seulement l’ICCID, appeler `GET /v1/esims?iccid=…` pour obtenir l’`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": "CODE du forfait catalogue, utilisé pour rattacher l’eSIM à votre catalogue. `null` uniquement si le forfait source a été supprimé du catalogue.",
                "nullable": true,
                "type": "string"
              },
              "package_name": {
                "description": "NOM d’affichage lisible, susceptible de changer. Ne PAS l’utiliser comme clé de rapprochement.",
                "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": "Votre référence de commande (`external_ref` envoyé à l'achat), pour rattacher l'eSIM à votre commande. `null` si absente.",
            "nullable": true,
            "type": "string"
          },
          "iccid": {
            "example": "8984012345678901234",
            "nullable": true,
            "type": "string"
          },
          "id": {
            "description": "ID de l’eSIM, utilisé dans tous les chemins `/v1/esims/{id}/…` et comme `order_item_id` des webhooks. Avec seulement l’ICCID, appeler `GET /v1/esims?iccid=…` pour obtenir l’`id`.",
            "format": "uuid",
            "type": "string"
          },
          "install": {
            "$ref": "#/components/schemas/Install"
          },
          "msisdn": {
            "nullable": true,
            "type": "string"
          },
          "order_id": {
            "format": "uuid",
            "type": "string"
          },
          "package_code": {
            "description": "CODE du forfait catalogue, utilisé pour rattacher l’eSIM à votre catalogue. `null` uniquement si le forfait source a été supprimé du catalogue.",
            "nullable": true,
            "type": "string"
          },
          "package_name": {
            "description": "NOM d’affichage lisible, susceptible de changer. Ne PAS l’utiliser comme clé de rapprochement.",
            "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": "Renvoyez le curseur SANS MODIFICATION pour charger la page suivante. `null` est le SEUL signal de fin : une page plus courte que `page_size` peut être suivie d’autres pages.",
                "nullable": true,
                "type": "string"
              },
              "page_size": {
                "description": "Nombre MAXIMUM d’enregistrements utilisé par le serveur pour cette page, déjà plafonné ; ce n’est pas le nombre réel d’éléments dans `items`.",
                "nullable": true,
                "type": "integer"
              },
              "total": {
                "description": "Nombre de lignes correspondant au filtre. `null` si le comptage coûte trop cher. Ne l’utilisez pas pour détecter la fin : seul `next_cursor` l’indique.",
                "format": "int64",
                "nullable": true,
                "type": "integer"
              },
              "warnings": {
                "description": "Explications de la liste ; vide si rien d'inhabituel. Codes fermés : `filtered_by_key_package_filters`, `filtered_by_provider_scope`, `no_price_for_org`, `price_filter_removed_rows`, `provider_disabled`, `capability_missing`. Ils indiquent restrictions de clé, périmètre fournisseur, absence de prix, filtre de prix, fournisseur désactivé ou capacité absente. `count` donne le nombre de lignes concernées, sinon `null`.",
                "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": "Toutes les données nécessaires à l’INSTALLATION de cette eSIM. `null` signifie que vous ne les avez PAS DEMANDÉES (sans `include=lpa`). Si l’eSIM n’a pas encore de code, le bloc est présent avec `lpa: null`.\n\nUne réponse contenant ce bloc porte toujours `Cache-Control: no-store`.",
        "nullable": true,
        "properties": {
          "android_url": {
            "nullable": true,
            "type": "string"
          },
          "apn": {
            "description": "Nom du point d’accès (APN) à saisir si la configuration automatique échoue. Toujours `null` dans cette version : `null` signifie inconnu, pas inutile.",
            "nullable": true,
            "type": "string"
          },
          "ios_url": {
            "nullable": true,
            "type": "string"
          },
          "lpa": {
            "description": "Code d’activation de l’eSIM, secret commercialisable. Cette chaîne suffit pour générer votre QR code.",
            "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": "QR code en data-URI PNG. Présent seulement avec `include=qr` sur un endpoint de DÉTAIL ; jamais dans les listes (environ 11 Ko par ligne, soit 1,1 Mo pour 100 lignes). Générez plutôt le QR à partir de `lpa`.",
            "nullable": true,
            "type": "string"
          },
          "smdp_address": {
            "example": "smdp.example.com",
            "nullable": true,
            "type": "string"
          }
        },
        "type": "object"
      },
      "ListWarning": {
        "description": "Explications de la liste ; vide si rien d'inhabituel. Codes fermés : `filtered_by_key_package_filters`, `filtered_by_provider_scope`, `no_price_for_org`, `price_filter_removed_rows`, `provider_disabled`, `capability_missing`. Ils indiquent restrictions de clé, périmètre fournisseur, absence de prix, filtre de prix, fournisseur désactivé ou capacité absente. `count` donne le nombre de lignes concernées, sinon `null`.",
        "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": "Clés membre uniquement. Sans données, le flux consommateur ne nécessite aucune organisation ni entité juridique vietnamienne. Les données fournies suivent le validateur personnel : full_name, tax_code personnel de 12 chiffres et règles de paiement pour address/email. Les factures professionnelles sont demandées après achat. Le test valide seulement les données et n’émet aucune facture MISA.",
        "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": "Motif du remboursement, de 5 à 1000 caractères après suppression des espaces en début et fin.",
            "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": "Renvoyez le curseur SANS MODIFICATION pour charger la page suivante. `null` est le SEUL signal de fin : une page plus courte que `page_size` peut être suivie d’autres pages.",
                "nullable": true,
                "type": "string"
              },
              "page_size": {
                "description": "Nombre MAXIMUM d’enregistrements utilisé par le serveur pour cette page, déjà plafonné ; ce n’est pas le nombre réel d’éléments dans `items`.",
                "nullable": true,
                "type": "integer"
              },
              "total": {
                "description": "Nombre de lignes correspondant au filtre. `null` si le comptage coûte trop cher. Ne l’utilisez pas pour détecter la fin : seul `next_cursor` l’indique.",
                "format": "int64",
                "nullable": true,
                "type": "integer"
              },
              "warnings": {
                "description": "Explications de la liste ; vide si rien d'inhabituel. Codes fermés : `filtered_by_key_package_filters`, `filtered_by_provider_scope`, `no_price_for_org`, `price_filter_removed_rows`, `provider_disabled`, `capability_missing`. Ils indiquent restrictions de clé, périmètre fournisseur, absence de prix, filtre de prix, fournisseur désactivé ou capacité absente. `count` donne le nombre de lignes concernées, sinon `null`.",
                "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": "Environnement de la clé appelante, identique à X-TKE-Mode. Les organisations utilisent tke_live_/tke_test_ et les membres tke_member_live_/tke_member_test_. Les hôtes réel et test imposent le mode de la clé.",
        "enum": [
          "test",
          "live"
        ],
        "example": "live",
        "type": "string"
      },
      "Money": {
        "description": "Montant monétaire : même structure partout sur `/v1`, jamais d'alternance entre objet et nombre brut.",
        "properties": {
          "amount": {
            "description": "Entier dans la plus petite unité de `currency` (le VND n’a pas de sous-unité).",
            "example": 273000,
            "format": "int64",
            "type": "integer"
          },
          "currency": {
            "example": "VND",
            "type": "string"
          }
        },
        "type": "object"
      },
      "OkEnvelope": {
        "properties": {
          "data": {
            "description": "La charge utile varie selon l’endpoint.",
            "type": "object"
          },
          "ok": {
            "enum": [
              true
            ],
            "type": "boolean"
          }
        },
        "required": [
          "ok",
          "data"
        ],
        "type": "object"
      },
      "OrderItem": {
        "properties": {
          "cancellable": {
            "$ref": "#/components/schemas/Cancellable"
          },
          "external_ref": {
            "description": "Votre référence de commande (`external_ref` envoyé à l'achat), pour rattacher l'eSIM à votre commande. `null` si absente.",
            "nullable": true,
            "type": "string"
          },
          "iccid": {
            "example": "8984012345678901234",
            "nullable": true,
            "type": "string"
          },
          "id": {
            "description": "ID de l’eSIM, utilisé dans tous les chemins `/v1/esims/{id}/…` et comme `order_item_id` des webhooks. Avec seulement l’ICCID, appeler `GET /v1/esims?iccid=…` pour obtenir l’`id`.",
            "format": "uuid",
            "type": "string"
          },
          "install": {
            "$ref": "#/components/schemas/Install"
          },
          "msisdn": {
            "nullable": true,
            "type": "string"
          },
          "order_id": {
            "format": "uuid",
            "type": "string"
          },
          "package_code": {
            "description": "CODE du forfait catalogue, utilisé pour rattacher l’eSIM à votre catalogue. `null` uniquement si le forfait source a été supprimé du catalogue.",
            "nullable": true,
            "type": "string"
          },
          "package_name": {
            "description": "NOM d’affichage lisible, susceptible de changer. Ne PAS l’utiliser comme clé de rapprochement.",
            "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": "Renvoyez le curseur SANS MODIFICATION pour charger la page suivante. `null` est le SEUL signal de fin : une page plus courte que `page_size` peut être suivie d’autres pages.",
                "nullable": true,
                "type": "string"
              },
              "page_size": {
                "description": "Nombre MAXIMUM d’enregistrements utilisé par le serveur pour cette page, déjà plafonné ; ce n’est pas le nombre réel d’éléments dans `items`.",
                "nullable": true,
                "type": "integer"
              },
              "total": {
                "description": "Nombre de lignes correspondant au filtre. `null` si le comptage coûte trop cher. Ne l’utilisez pas pour détecter la fin : seul `next_cursor` l’indique.",
                "format": "int64",
                "nullable": true,
                "type": "integer"
              },
              "warnings": {
                "description": "Explications de la liste ; vide si rien d'inhabituel. Codes fermés : `filtered_by_key_package_filters`, `filtered_by_provider_scope`, `no_price_for_org`, `price_filter_removed_rows`, `provider_disabled`, `capability_missing`. Ils indiquent restrictions de clé, périmètre fournisseur, absence de prix, filtre de prix, fournisseur désactivé ou capacité absente. `count` donne le nombre de lignes concernées, sinon `null`.",
                "example": [],
                "items": {
                  "$ref": "#/components/schemas/ListWarning"
                },
                "type": "array"
              }
            },
            "type": "object"
          },
          "ok": {
            "enum": [
              true
            ],
            "type": "boolean"
          }
        },
        "required": [
          "ok",
          "data"
        ],
        "type": "object"
      },
      "OrderResponse": {
        "properties": {
          "data": {
            "description": "Commande partenaire.",
            "properties": {
              "created_at": {
                "format": "date-time",
                "type": "string"
              },
              "external_ref": {
                "example": "po-1001",
                "nullable": true,
                "type": "string"
              },
              "fulfillment_status": {
                "description": "Quantité réellement livrée, axe DISTINCT de `status`. Une commande de 3 eSIM dont 2 livrées et 1 en échec reste `status=completed`, avec `fulfillment_status=partially_fulfilled` et `refunded_amount > 0`.",
                "enum": [
                  "fulfilled",
                  "partially_fulfilled",
                  "unfulfilled"
                ],
                "type": "string"
              },
              "id": {
                "description": "ID de la commande (UUID).",
                "format": "uuid",
                "type": "string"
              },
              "idempotent_replay": {
                "description": "Vrai si cette requête rejoue un succès antérieur de manière idempotente.",
                "nullable": true,
                "type": "boolean"
              },
              "invoice_no": {
                "description": "Numéro de facture après émission.",
                "nullable": true,
                "type": "string"
              },
              "invoice_status": {
                "description": "État de la facture TVA de la commande. `none` = pas encore émise.",
                "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": "Montant de commande soldé, sans remboursement en cours. `false` empêche l'émission d'une facture.",
                "type": "boolean"
              },
              "net": {
                "$ref": "#/components/schemas/Money"
              },
              "public_ref": {
                "description": "Code court de commande, à utiliser dans les demandes d'assistance.",
                "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` signifie INFORMATION INCONNUE, ni oui ni non.** Ne déduisez aucun engagement d'un champ vide. Seuls `true`/`false` et les entrées présentes dans `features.apps[]` sont déclarés par le fournisseur.",
        "properties": {
          "capabilities": {
            "$ref": "#/components/schemas/PackageCapabilities"
          },
          "code": {
            "description": "Code de forfait de VOTRE catalogue, soit `public_sku`. IMMUABLE par forfait, même si le fournisseur change ; utilisable comme clé de rapprochement. L’exemple a le bon format mais ne fonctionne PAS : lire un vrai code via `GET /v1/catalog/packages`.",
            "example": "TKE-VN-30D-X3K9F",
            "type": "string"
          },
          "coverage": {
            "description": "Zones où le forfait fonctionne.",
            "properties": {
              "countries": {
                "description": "Codes ISO-3166 alpha-2. Tableau vide pour les forfaits mondiaux (sans énumération de 200 pays) et les forfaits sans couverture déclarée.",
                "example": [
                  "CN"
                ],
                "items": {
                  "type": "string"
                },
                "type": "array"
              },
              "country_count": {
                "description": "Nombre de pays couverts, pour trier ou filtrer les forfaits multi-pays sans tout télécharger ni compter vous-même le tableau.",
                "example": 1,
                "type": "integer"
              },
              "group": {
                "description": "Couverture : pays (`country`), région (`region`) ou monde (`global`). Énumération ouverte : ignorer les valeurs inconnues.",
                "example": "country",
                "type": "string"
              },
              "region": {
                "description": "Toujours `null` : les régions sont associées aux pays, pas aux forfaits. Le filtre `?region=` reste utilisable.",
                "nullable": true,
                "type": "string"
              }
            },
            "type": "object"
          },
          "data": {
            "description": "Quota de données du forfait. Toutes les valeurs sont en **OCTETS**, pas en Mo/Go, pour éviter un double arrondi.",
            "properties": {
              "daily_bytes": {
                "description": "Quota QUOTIDIEN en octets. `null` pour les forfaits `fixed` ou `unlimited`.",
                "example": 1073741824,
                "format": "int64",
                "nullable": true,
                "type": "integer"
              },
              "label": {
                "description": "Libellé lisible généré par la même fonction que dans toutes nos interfaces.",
                "example": "1GB/ngày",
                "type": "string"
              },
              "model": {
                "description": "Mode de calcul : `daily` (quota quotidien), `fixed` (total sur la période), `unlimited`. ÉNUMÉRATION OUVERTE : les clients DOIVENT ignorer les valeurs inconnues.",
                "enum": [
                  "daily",
                  "fixed",
                  "unlimited"
                ],
                "example": "daily",
                "type": "string"
              },
              "reset": {
                "description": "Moment de remise à zéro du quota quotidien, important pour une vente en fin de journée. `null` = non déclaré.",
                "nullable": true,
                "properties": {
                  "at": {
                    "description": "Heure de remise à zéro dans le fuseau `timezone` (`00:00`, `00:00:01`).",
                    "example": "00:00",
                    "nullable": true,
                    "type": "string"
                  },
                  "note": {
                    "nullable": true,
                    "type": "string"
                  },
                  "timezone": {
                    "description": "Fuseau horaire de la remise à zéro (`UTC+8`, `UTC+7`).",
                    "example": "UTC+8",
                    "nullable": true,
                    "type": "string"
                  }
                },
                "type": "object"
              },
              "total_bytes": {
                "description": "Quota total pour la période, en octets. `null` pour `daily` ou `unlimited` : multiplier un quota journalier par le nombre de jours ne crée pas un engagement fournisseur.",
                "example": null,
                "format": "int64",
                "nullable": true,
                "type": "integer"
              }
            },
            "type": "object"
          },
          "description": {
            "description": "Description commerciale du forfait. `null` si non rédigée.",
            "nullable": true,
            "type": "string"
          },
          "description_en": {
            "description": "Description anglaise ; `null` si non fournie.",
            "nullable": true,
            "type": "string"
          },
          "features": {
            "description": "Fonctionnalités du forfait dans un objet typé, pas un tableau : `false` et `null` distinguent l'absence de partage Wi-Fi de l'information inconnue, contrairement à `[]`.",
            "properties": {
              "apps": {
                "description": "État de prise en charge par SYSTÈME D’EXPLOITATION.\n\n**Une application ABSENTE du tableau signifie information inconnue, PAS compatibilité.** Seules les entrées présentes sont déclarées par le fournisseur ; actuellement seul TikTok est déclaré.\n\nL’absence de drapeau global est volontaire : sur le catalogue réel, 2 465 forfaits prennent en charge TikTok sur iPhone mais PAS sur Android. Un drapeau unique les classerait tous « non », faussant près d’un tiers du catalogue.",
                "items": {
                  "properties": {
                    "android": {
                      "description": "`supported` (compatibilité déclarée), `blocked` (incompatibilité déclarée), `unknown` (OS non couvert par la déclaration). Énumération ouverte.",
                      "enum": [
                        "supported",
                        "blocked",
                        "unknown"
                      ],
                      "example": "blocked",
                      "type": "string"
                    },
                    "app": {
                      "description": "Code d'application en minuscules (ex. `tiktok`).",
                      "example": "tiktok",
                      "type": "string"
                    },
                    "ios": {
                      "description": "`supported` (compatibilité déclarée), `blocked` (incompatibilité déclarée), `unknown` (OS non couvert par la déclaration). Énumération ouverte.",
                      "enum": [
                        "supported",
                        "blocked",
                        "unknown"
                      ],
                      "example": "supported",
                      "type": "string"
                    },
                    "note": {
                      "nullable": true,
                      "type": "string"
                    }
                  },
                  "type": "object"
                },
                "type": "array"
              },
              "hotspot": {
                "description": "Partage Wi-Fi avec d'autres appareils. `null` = non déclaré, différent de `false`.",
                "example": true,
                "nullable": true,
                "type": "boolean"
              },
              "kyc_required": {
                "description": "Vérification d’identité (KYC) requise avant utilisation. `null` = non déclaré.",
                "example": false,
                "nullable": true,
                "type": "boolean"
              },
              "sms": {
                "description": "SMS. `false` signifie AUCUNE réception de SMS, donc aucun OTP bancaire. `null` = non déclaré.",
                "example": false,
                "nullable": true,
                "type": "boolean"
              },
              "voice": {
                "description": "Appels vocaux. `false` signifie absence de voix déclarée : une ligne de données seule ne reçoit pas d'appels. `null` = non déclaré.",
                "example": false,
                "nullable": true,
                "type": "boolean"
              }
            },
            "type": "object"
          },
          "fup": {
            "description": "Comportement après épuisement du quota à pleine vitesse. `null` = aucune politique de réduction déclarée.",
            "nullable": true,
            "properties": {
              "behavior": {
                "description": "`throttle` (débit réduit), `cutoff` (déconnexion) ou `null` (non déclaré). Énumération ouverte.",
                "enum": [
                  "throttle",
                  "cutoff",
                  null
                ],
                "nullable": true,
                "type": "string"
              },
              "high_speed_bytes": {
                "description": "Octets à pleine vitesse avant réduction du débit.",
                "example": 1048576,
                "format": "int64",
                "nullable": true,
                "type": "integer"
              },
              "note": {
                "nullable": true,
                "type": "string"
              },
              "speed_kbps": {
                "description": "Débit APRÈS épuisement du quota rapide (kbps). `null` si la ligne est coupée ou l’information non déclarée. Distinct de `network.speed_cap_kbps`, plafond du forfait entier.",
                "example": 128,
                "nullable": true,
                "type": "integer"
              }
            },
            "type": "object"
          },
          "market": {
            "description": "SYSTÈME DE FOURNITURE du forfait, énumération OUVERTE : ignorer les valeurs inconnues. Détermine la présence d’un stock et les opérations après-vente. `domestic` vend des kits avec recharge, annulation et consultation d’usage ; `international` commande au fournisseur à la demande, sans 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": "Nom anglais ; `null` si non fourni.",
            "nullable": true,
            "type": "string"
          },
          "network": {
            "description": "Réseaux utilisables par le forfait.",
            "properties": {
              "apn": {
                "description": "APN à saisir manuellement sur certains appareils Android sans configuration automatique. Nécessaire pour rédiger le guide d’installation et éviter des demandes d’assistance. `null` = non déclaré.",
                "example": "e-ideas",
                "nullable": true,
                "type": "string"
              },
              "carriers": {
                "description": "Opérateurs réseau utilisés par le forfait. Il s’agit d’une caractéristique de l’eSIM (réseau radio), pas du fournisseur.",
                "items": {
                  "properties": {
                    "name": {
                      "example": "China Unicom",
                      "type": "string"
                    }
                  },
                  "type": "object"
                },
                "type": "array"
              },
              "generations": {
                "description": "Générations réseau utilisables (`[\"4g\", \"5g\"]`). `null` = non déclaré, et NON « 2G uniquement ».",
                "example": [
                  "4g",
                  "5g"
                ],
                "items": {
                  "type": "string"
                },
                "nullable": true,
                "type": "array"
              },
              "speed_cap_kbps": {
                "description": "Plafond de débit du forfait ENTIER (kbps), distinct de `fup.speed_kbps`, applicable après épuisement du quota rapide.",
                "nullable": true,
                "type": "integer"
              }
            },
            "type": "object"
          },
          "price": {
            "$ref": "#/components/schemas/Money",
            "description": "Prix payé par votre organisation via l'API, exactement celui débité par `POST /v1/orders`. Toujours un objet `{amount, currency}`, jamais un nombre brut. Aucun coût fournisseur n'est exposé."
          },
          "tags": {
            "description": "Étiquettes commerciales normalisées (minuscules, tirets), également utilisées par les restrictions de catalogue de votre clé. Aucun forfait n’est étiqueté actuellement ; `?tag=` renvoie donc une liste vide jusqu’à l’ajout d’étiquettes par le vendeur.",
            "example": [],
            "items": {
              "type": "string"
            },
            "type": "array"
          },
          "validity": {
            "description": "Durée de validité du forfait et début du décompte.",
            "properties": {
              "days": {
                "example": 24,
                "type": "integer"
              },
              "install_by_days": {
                "description": "Délai en jours pour scanner le QR code et installer l’eSIM après achat. `null` = non déclaré, PAS « installation sans limite de temps ».",
                "example": 365,
                "nullable": true,
                "type": "integer"
              },
              "label": {
                "example": "24 ngày",
                "type": "string"
              },
              "reclaim_on_expiry": {
                "description": "Le fournisseur récupère l'abonnement à expiration : recharge et changement deviennent impossibles. Important pour les renouvellements. `null` = non déclaré.",
                "example": true,
                "nullable": true,
                "type": "boolean"
              },
              "starts_when": {
                "description": "Début du décompte : `on_install` (installation), `on_purchase` (achat), ou `null`. Information essentielle pour calculer l'expiration : avec `null`, ne promettez pas de date précise.",
                "enum": [
                  "on_install",
                  "on_purchase",
                  null
                ],
                "example": "on_install",
                "nullable": true,
                "type": "string"
              },
              "unused_valid_days": {
                "description": "Nombre de jours avant expiration si le forfait reste inutilisé en stock. `null` = non déclaré, PAS illimité. Distinct de `install_by_days` : l’un concerne la CONSERVATION, l’autre la date limite d’INSTALLATION.",
                "nullable": true,
                "type": "integer"
              }
            },
            "type": "object"
          }
        },
        "type": "object"
      },
      "PackageCapabilities": {
        "description": "Capacités après-vente déclarées par le forfait, jamais déduites du type de fournisseur. Sans déclaration, tous les indicateurs sont `false`, pour ne pas promettre une action renvoyant forcément `422 capability.not_supported`.",
        "properties": {
          "cancel": {
            "properties": {
              "restriction": {
                "description": "`code_not_disclosed` : annulation uniquement si personne n'a vu le code (ni client, ni tableau de bord, ni `?include=lpa`). Condition réelle de l'annulation, sans lien avec l'installation, que le système ne peut pas connaître. L'ancien libellé `unused_only` était trompeur.",
                "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": "Renvoyez le curseur SANS MODIFICATION pour charger la page suivante. `null` est le SEUL signal de fin : une page plus courte que `page_size` peut être suivie d’autres pages.",
                "nullable": true,
                "type": "string"
              },
              "page_size": {
                "description": "Nombre MAXIMUM d’enregistrements utilisé par le serveur pour cette page, déjà plafonné ; ce n’est pas le nombre réel d’éléments dans `items`.",
                "nullable": true,
                "type": "integer"
              },
              "total": {
                "description": "Nombre de forfaits correspondant aux filtres ; `null` si le comptage est coûteux (international ou sans `market`). Calculé avant restrictions de clé et prix organisation : ce total décrit le catalogue, pas les forfaits achetables.",
                "format": "int64",
                "nullable": true,
                "type": "integer"
              },
              "warnings": {
                "description": "Explications de la liste ; vide si rien d'inhabituel. Codes fermés : `filtered_by_key_package_filters`, `filtered_by_provider_scope`, `no_price_for_org`, `price_filter_removed_rows`, `provider_disabled`, `capability_missing`. Ils indiquent restrictions de clé, périmètre fournisseur, absence de prix, filtre de prix, fournisseur désactivé ou capacité absente. `count` donne le nombre de lignes concernées, sinon `null`.",
                "example": [],
                "items": {
                  "$ref": "#/components/schemas/ListWarning"
                },
                "type": "array"
              }
            },
            "type": "object"
          },
          "ok": {
            "enum": [
              true
            ],
            "type": "boolean"
          }
        },
        "required": [
          "ok",
          "data"
        ],
        "type": "object"
      },
      "PlanChangePolicy": {
        "description": "Politique du fournisseur lors de la RECHARGE AVEC UN NOUVEAU FORFAIT. `null` signifie que la recharge est impossible OU que cette politique n’a pas été mesurée. `null` interdit de supposer ; un objet entièrement `false` garantirait au contraire qu’aucun forfait n’est annulé.",
        "nullable": true,
        "properties": {
          "note": {
            "nullable": true,
            "type": "string"
          },
          "requires_cancel_first": {
            "description": "Le forfait EN COURS est annulé avant l’achat du nouveau : ce fournisseur ne cumule pas les forfaits. Informez le client AVANT qu’il lance la recharge d’un nouveau forfait.",
            "type": "boolean"
          },
          "stacking": {
            "description": "Indique si durée et quota du nouveau forfait s'ajoutent au forfait en cours.",
            "type": "boolean"
          }
        },
        "type": "object"
      },
      "PlanOptionsResponse": {
        "properties": {
          "data": {
            "description": "Forfaits de recharge disponibles pour cette eSIM, filtrés par son fournisseur. Chaque entrée est un `Package` complet, de même structure que le catalogue. `policy` décrit le fournisseur actuel de l'eSIM ; `null` signifie politique inconnue, pas absence d'annulation.",
            "properties": {
              "items": {
                "items": {
                  "$ref": "#/components/schemas/Package"
                },
                "type": "array"
              },
              "mode": {
                "$ref": "#/components/schemas/ModeEcho"
              },
              "next_cursor": {
                "description": "Renvoyez le curseur SANS MODIFICATION pour charger la page suivante. `null` est le SEUL signal de fin : une page plus courte que `page_size` peut être suivie d’autres pages.",
                "nullable": true,
                "type": "string"
              },
              "page_size": {
                "description": "Nombre MAXIMUM d’enregistrements utilisé par le serveur pour cette page, déjà plafonné ; ce n’est pas le nombre réel d’éléments dans `items`.",
                "type": "integer"
              },
              "policy": {
                "$ref": "#/components/schemas/PlanChangePolicy"
              },
              "total": {
                "description": "Nombre de forfaits correspondant aux filtres ; `null` si le comptage est coûteux (international ou sans `market`). Calculé avant restrictions de clé et prix organisation : ce total décrit le catalogue, pas les forfaits achetables.",
                "format": "int64",
                "nullable": true,
                "type": "integer"
              },
              "warnings": {
                "description": "Explications de la liste ; vide si rien d'inhabituel. Codes fermés : `filtered_by_key_package_filters`, `filtered_by_provider_scope`, `no_price_for_org`, `price_filter_removed_rows`, `provider_disabled`, `capability_missing`. Ils indiquent restrictions de clé, périmètre fournisseur, absence de prix, filtre de prix, fournisseur désactivé ou capacité absente. `count` donne le nombre de lignes concernées, sinon `null`.",
                "example": [],
                "items": {
                  "$ref": "#/components/schemas/ListWarning"
                },
                "type": "array"
              }
            },
            "type": "object"
          },
          "ok": {
            "enum": [
              true
            ],
            "type": "boolean"
          }
        },
        "required": [
          "ok",
          "data"
        ],
        "type": "object"
      },
      "PlanView": {
        "description": "Le forfait actuellement appliqué à cette 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 de la commande qui vient d’être renvoyée.",
                "format": "uuid",
                "type": "string"
              },
              "mode": {
                "$ref": "#/components/schemas/ModeEcho"
              },
              "resent_at": {
                "format": "date-time",
                "type": "string"
              },
              "resent_to": {
                "description": "Adresse du destinataire partiellement masquée ; l'interface partenaire ne restitue jamais l'adresse e-mail complète du client.",
                "example": "ab***@x.com",
                "type": "string"
              }
            },
            "type": "object"
          },
          "ok": {
            "enum": [
              true
            ],
            "type": "boolean"
          }
        },
        "required": [
          "ok",
          "data"
        ],
        "type": "object"
      },
      "SimulateRequest": {
        "properties": {
          "data": {
            "description": "Remplacer partiellement le champ `data` de la charge utile d’exemple.",
            "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": "Votre référence de commande (`external_ref` envoyé à l'achat), pour rattacher l'eSIM à votre commande. `null` si absente.",
                "nullable": true,
                "type": "string"
              },
              "iccid": {
                "example": "8984012345678901234",
                "nullable": true,
                "type": "string"
              },
              "id": {
                "description": "ID de l’eSIM, utilisé dans tous les chemins `/v1/esims/{id}/…` et comme `order_item_id` des webhooks. Avec seulement l’ICCID, appeler `GET /v1/esims?iccid=…` pour obtenir l’`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": "CODE du forfait catalogue, utilisé pour rattacher l’eSIM à votre catalogue. `null` uniquement si le forfait source a été supprimé du catalogue.",
                "nullable": true,
                "type": "string"
              },
              "package_name": {
                "description": "NOM d’affichage lisible, susceptible de changer. Ne PAS l’utiliser comme clé de rapprochement.",
                "type": "string"
              },
              "plan": {
                "$ref": "#/components/schemas/PlanView"
              },
              "simulated": {
                "description": "État simulé après application du scénario.",
                "properties": {
                  "esim_state": {
                    "description": "Cycle TECHNIQUE de l’eSIM de test. En production il provient de `smdp_status`, mais une eSIM de test n’a aucun profil SM-DP+ ; il figure donc ici avec les autres paramètres propres au test.",
                    "enum": [
                      "released",
                      "installed",
                      "enabled",
                      "disabled"
                    ],
                    "type": "string"
                  },
                  "last_via": {
                    "description": "Flux ayant MODIFIÉ cette eSIM le plus récemment.",
                    "enum": [
                      "open_api",
                      "simulate",
                      "seed"
                    ],
                    "type": "string"
                  },
                  "via": {
                    "description": "Flux ayant CRÉÉ cette eSIM de test.",
                    "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": "Solde du portefeuille test APRÈS réinitialisation, toujours 0. Le grand livre n’est PAS supprimé : une écriture compensatrice remet le solde à zéro.",
                "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": "Scénario à appliquer : tous les champs sont facultatifs, mais au moins un est requis. Un corps vide renvoie `422 testmode.invalid_scenario`, pas un faux succès.",
        "properties": {
          "data_used_mb": {
            "description": "Données déjà utilisées en Mo (1 MiB binaire). Une valeur dépassant le quota est plafonnée : c'est le scénario d'épuisement, pas une erreur.",
            "example": 4096,
            "format": "int64",
            "minimum": 0,
            "type": "integer"
          },
          "expire_at": {
            "description": "Définir une date d’expiration précise (ISO-8601 UTC).",
            "format": "date-time",
            "type": "string"
          },
          "expire_now": {
            "description": "`true` : faire expirer le forfait immédiatement (`esim_status` devient `expired`).",
            "type": "boolean"
          },
          "provider_reject": {
            "description": "Simuler un refus fournisseur à la prochaine opération (recharge ou annulation).",
            "type": "boolean"
          },
          "status": {
            "description": "Cycle de vie TECHNIQUE du profil eSIM, selon le vocabulaire du fournisseur. Distinct du cycle commercial `esim_status` renvoyé par les endpoints eSIM.",
            "enum": [
              "released",
              "installed",
              "enabled",
              "disabled"
            ],
            "example": "enabled",
            "type": "string"
          },
          "throttled": {
            "description": "`true` : ligne ralentie (forfait illimité ayant dépassé le quota à pleine vitesse).",
            "type": "boolean"
          }
        },
        "type": "object"
      },
      "TestModeStatus": {
        "properties": {
          "data": {
            "properties": {
              "base_url_testing": {
                "description": "Hôte du mode test. Utilisez uniquement des clés tke_test_ ou tke_member_test_ ; un appel à l’hôte réel renvoie 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": "Les étapes sont calculées à partir des appels API réels du même propriétaire en mode test. Les webhooks concernent uniquement les organisations.",
                "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": "Boucle de consommation simulée : activation, intervalle et dernière exécution. Les webhooks de seuil concernent uniquement les organisations compatibles.",
                "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": "Indique si le propriétaire de la clé a terminé toutes les étapes prises en charge. Les membres ne doivent pas tester les webhooks.",
                "type": "boolean"
              },
              "sample_counts": {
                "properties": {
                  "test_esims": {
                    "format": "int64",
                    "type": "integer"
                  },
                  "test_orders": {
                    "format": "int64",
                    "type": "integer"
                  }
                },
                "type": "object"
              },
              "seeded": {
                "description": "Indique si le propriétaire de la clé dispose déjà de commandes et d’eSIM de test.",
                "type": "boolean"
              },
              "test_key_count": {
                "format": "int64",
                "type": "integer"
              },
              "wallet_balance": {
                "description": "Solde du portefeuille test en VND virtuels du propriétaire de la clé, séparé du portefeuille réel.",
                "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": "Remplacer partiellement le champ `data` de la charge utile d’exemple.",
            "type": "object"
          },
          "event_type": {
            "description": "Type d'événement webhook à simuler.",
            "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": "Statistiques d'utilisation des données de l'eSIM. `updated_at = null` si jamais interrogée.",
        "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` : aucun nouvel événement accepté, après suspension dans le tableau de bord ou révocation de clé (arrêt dans la même transaction). Un `PUT /v1/webhooks/{id}` validé par CHECK_HEALTH le réactive.",
                "type": "boolean"
              },
              "created_at": {
                "format": "date-time",
                "type": "string"
              },
              "events": {
                "description": "Vide = tout recevoir.",
                "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": "ID de l’endpoint pour `/v1/webhooks/{id}`. Un NOUVEL ID est généré à chaque création : supprimer puis recréer produit un autre endpoint, pas la restauration de l’ancien.",
                "format": "uuid",
                "type": "string"
              },
              "last_delivery_at": {
                "description": "Dernier envoi via cet endpoint. `null` = jamais envoyé, distinct d’un échec d’envoi ; à vérifier d’abord lorsqu’un partenaire ne reçoit aucun webhook.",
                "format": "date-time",
                "nullable": true,
                "type": "string"
              },
              "mode": {
                "$ref": "#/components/schemas/ModeEcho"
              },
              "secret_last4": {
                "description": "Quatre derniers caractères du secret webhook, pour vérifier que votre secret correspond à celui utilisé pour signer sans le révéler. Le secret complet n’est affiché qu’UNE FOIS, à la création.",
                "type": "string"
              },
              "url": {
                "type": "string"
              }
            },
            "type": "object"
          },
          "ok": {
            "enum": [
              true
            ],
            "type": "boolean"
          }
        },
        "required": [
          "ok",
          "data"
        ],
        "type": "object"
      },
      "WebhookEndpoint": {
        "properties": {
          "active": {
            "description": "`false` : aucun nouvel événement accepté, après suspension dans le tableau de bord ou révocation de clé (arrêt dans la même transaction). Un `PUT /v1/webhooks/{id}` validé par CHECK_HEALTH le réactive.",
            "type": "boolean"
          },
          "created_at": {
            "format": "date-time",
            "type": "string"
          },
          "events": {
            "description": "Vide = tout recevoir.",
            "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": "ID de l’endpoint pour `/v1/webhooks/{id}`. Un NOUVEL ID est généré à chaque création : supprimer puis recréer produit un autre endpoint, pas la restauration de l’ancien.",
            "format": "uuid",
            "type": "string"
          },
          "last_delivery_at": {
            "description": "Dernier envoi via cet endpoint. `null` = jamais envoyé, distinct d’un échec d’envoi ; à vérifier d’abord lorsqu’un partenaire ne reçoit aucun webhook.",
            "format": "date-time",
            "nullable": true,
            "type": "string"
          },
          "secret_last4": {
            "description": "Quatre derniers caractères du secret webhook, pour vérifier que votre secret correspond à celui utilisé pour signer sans le révéler. Le secret complet n’est affiché qu’UNE FOIS, à la création.",
            "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": "Renvoyez le curseur SANS MODIFICATION pour charger la page suivante. `null` est le SEUL signal de fin : une page plus courte que `page_size` peut être suivie d’autres pages.",
                "nullable": true,
                "type": "string"
              },
              "page_size": {
                "description": "Nombre MAXIMUM d’enregistrements utilisé par le serveur pour cette page, déjà plafonné ; ce n’est pas le nombre réel d’éléments dans `items`.",
                "nullable": true,
                "type": "integer"
              },
              "total": {
                "description": "Nombre de lignes correspondant au filtre. `null` si le comptage coûte trop cher. Ne l’utilisez pas pour détecter la fin : seul `next_cursor` l’indique.",
                "format": "int64",
                "nullable": true,
                "type": "integer"
              },
              "warnings": {
                "description": "Explications de la liste ; vide si rien d'inhabituel. Codes fermés : `filtered_by_key_package_filters`, `filtered_by_provider_scope`, `no_price_for_org`, `price_filter_removed_rows`, `provider_disabled`, `capability_missing`. Ils indiquent restrictions de clé, périmètre fournisseur, absence de prix, filtre de prix, fournisseur désactivé ou capacité absente. `count` donne le nombre de lignes concernées, sinon `null`.",
                "example": [],
                "items": {
                  "$ref": "#/components/schemas/ListWarning"
                },
                "type": "array"
              }
            },
            "type": "object"
          },
          "ok": {
            "enum": [
              true
            ],
            "type": "boolean"
          }
        },
        "required": [
          "ok",
          "data"
        ],
        "type": "object"
      },
      "WebhookRequest": {
        "properties": {
          "events": {
            "description": "Vide = tout recevoir. Valeur inconnue : 400 `internal.validation_failed` (champ `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": "Authentification HMAC-SHA256 (§4). Envoyer 4 en-têtes : `X-TKE-Key` (key_id public), `X-TKE-Timestamp` (époque en ms), `X-TKE-Nonce` (UUID par requête), `X-TKE-Signature` (hexadécimal minuscule). signing_string = method\npath\ntimestamp\nnonce\nbody_hash ; body_hash = SHA256(raw_body) hexadécimal. Comparaison à temps constant. path INCLUT la chaîne de requête.",
        "in": "header",
        "name": "X-TKE-Signature",
        "type": "apiKey"
      }
    }
  },
  "info": {
    "description": "Open API destinée aux partenaires de vente eSIM (un seul niveau). Chaque endpoint `/v1/*` exige une authentification **HMAC-SHA256** avec quatre en-têtes `X-TKE-*` ; aucun mode sans signature. Les POST portant sur l’argent ou les commandes exigent `Idempotency-Key`. Une clé `tke_test_` utilise le mode test (portefeuille, stock et livraison virtuels) sur l’hôte de test. REST fait foi, avec rapprochement par `updated_since` et curseur ; les webhooks sont complémentaires. La clé primaire est toujours `id` ; les clés étrangères portent un préfixe tel que `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": "Autorisation requise: `catalog:read`.\n\nCatalogue de votre organisation : une surface commune aux deux systèmes de fourniture, filtrée par `?market=`.\n\nSeuls les forfaits réellement ACHETABLES apparaissent : publiés, toujours distribués par le fournisseur, autorisés par la clé ET tarifés pour l’organisation. Un forfait sans prix est absent parce qu’il ne peut être commandé.\n\nPagination par curseur : premier appel sans `cursor`, puis renvoyer `next_cursor` à l’identique jusqu’à `null`. Une page plus courte que `page_size` n’indique PAS la fin.\n\n`capabilities` reflète uniquement les déclarations réelles du forfait : sans déclaration, tous les indicateurs valent `false`. Mieux vaut omettre un bouton possible que promettre un endpoint qui renverrait `422`.\n\nLe catalogue ne présente AUCUNE notion de stock, ni quantité ni niveau disponible/épuisé. Si le fournisseur ne peut exécuter la commande, `POST /v1/orders` renvoie directement une erreur claire (`esim.out_of_stock`…), sans déduction à partir d’un champ préalable.",
        "operationId": "get_catalog_packages",
        "parameters": [
          {
            "description": "Lignes par page. Valeur par défaut 50, maximum 100. Une valeur supérieure est ramenée à 100 sans erreur.",
            "in": "query",
            "name": "page_size",
            "required": false,
            "schema": {
              "default": 50,
              "maximum": 100,
              "minimum": 1,
              "type": "integer"
            }
          },
          {
            "description": "Système de fourniture : `domestic` | `international`. Une valeur hors liste renvoie `internal.invalid_filter` (400), et non le catalogue complet : une faute ne doit pas ressembler à un filtre sans effet.",
            "in": "query",
            "name": "market",
            "required": false,
            "schema": {
              "type": "string"
            }
          },
          {
            "description": "Code pays ISO-3166 alpha-2 (ex. `VN`). Uniquement les forfaits couvrant ce pays.",
            "in": "query",
            "name": "country",
            "required": false,
            "schema": {
              "type": "string"
            }
          },
          {
            "description": "Identifiant de région (par exemple `chau-a`). Inclut les forfaits couvrant au moins un pays de cette région et tous les forfaits mondiaux.",
            "in": "query",
            "name": "region",
            "required": false,
            "schema": {
              "type": "string"
            }
          },
          {
            "description": "Recherche libre sur le code public et le nom du forfait (partielle, sans distinction de casse).",
            "in": "query",
            "name": "q",
            "required": false,
            "schema": {
              "type": "string"
            }
          },
          {
            "description": "Filtrer sur UNE étiquette commerciale. La normalisation supprime les espaces superflus, utilise les minuscules et les tirets : `Ban Le` et `ban-le` désignent la même étiquette.",
            "in": "query",
            "name": "tag",
            "required": false,
            "schema": {
              "type": "string"
            }
          },
          {
            "description": "Modèle de données : `daily` | `fixed` | `unlimited`, remplaçant `supports_topup`. Valeur inconnue : `internal.invalid_filter` (400).",
            "in": "query",
            "name": "data_model",
            "required": false,
            "schema": {
              "type": "string"
            }
          },
          {
            "description": "Quota minimum en **OCTETS**. Les forfaits quotidiens sont comparés sur leur quota PAR JOUR ; les forfaits illimités satisfont TOUJOURS une borne minimale.",
            "in": "query",
            "name": "data_min_bytes",
            "required": false,
            "schema": {
              "format": "int64",
              "type": "integer"
            }
          },
          {
            "description": "Quota maximum en **OCTETS**. Les forfaits illimités sont EXCLUS : un plafond ne peut s’appliquer à l’illimité et laisser passer ces forfaits fausserait une recherche du plus petit forfait.",
            "in": "query",
            "name": "data_max_bytes",
            "required": false,
            "schema": {
              "format": "int64",
              "type": "integer"
            }
          },
          {
            "description": "Validité minimum en jours.",
            "in": "query",
            "name": "days_min",
            "required": false,
            "schema": {
              "format": "int64",
              "type": "integer"
            }
          },
          {
            "description": "Validité maximum en jours.",
            "in": "query",
            "name": "days_max",
            "required": false,
            "schema": {
              "format": "int64",
              "type": "integer"
            }
          },
          {
            "description": "Prix minimum (VND), comparé au prix de VOTRE ORGANISATION. Ce filtre est appliqué APRÈS la pagination serveur : une page plus courte ne signifie donc pas la fin des données.",
            "in": "query",
            "name": "price_min",
            "required": false,
            "schema": {
              "format": "int64",
              "type": "integer"
            }
          },
          {
            "description": "Prix maximum (VND), mêmes règles que `price_min`.",
            "in": "query",
            "name": "price_max",
            "required": false,
            "schema": {
              "format": "int64",
              "type": "integer"
            }
          },
          {
            "description": "Uniquement les forfaits compatibles avec cette génération (`2g` | `3g` | `4g` | `5g`). Une valeur inconnue renvoie `internal.invalid_filter` (400).",
            "in": "query",
            "name": "network_gen",
            "required": false,
            "schema": {
              "type": "string"
            }
          },
          {
            "description": "`true` : partage Wi-Fi déclaré ; `false` : absence déclarée. Sans déclaration (`features.hotspot = null`), aucun des deux filtres ne correspond. Accepte seulement les chaînes `true` et `false`.",
            "in": "query",
            "name": "hotspot",
            "required": false,
            "schema": {
              "enum": [
                "true",
                "false"
              ],
              "type": "string"
            }
          },
          {
            "description": "Uniquement les forfaits DÉCLARANT prendre en charge cette application sur au moins un système (`app=tiktok`). Une absence de déclaration ne correspond PAS au filtre : le silence n’est pas un engagement. Seul `tiktok` est accepté ; tout autre code renvoie `internal.invalid_filter` (400).",
            "in": "query",
            "name": "app",
            "required": false,
            "schema": {
              "type": "string"
            }
          },
          {
            "description": "`true` : voix déclarée ; `false` : absence de voix déclarée. Aucune déclaration ne correspond à aucun côté. Accepte seulement les chaînes `true` et `false`.",
            "in": "query",
            "name": "voice",
            "required": false,
            "schema": {
              "enum": [
                "true",
                "false"
              ],
              "type": "string"
            }
          },
          {
            "description": "Renvoyez `next_cursor` sans modification. **Arrêtez uniquement à `null`, pas sur une page courte** : restrictions de clé, prix de l'organisation et filtre de prix s'appliquent après pagination. Le curseur contient l'empreinte des filtres : changement ou corruption → `catalog.cursor_mismatch` (400), pour éviter de fusionner des pages de requêtes différentes.",
            "in": "query",
            "name": "cursor",
            "required": false,
            "schema": {
              "type": "string"
            }
          }
        ],
        "responses": {
          "200": {
            "content": {
              "application/json": {
                "schema": {
                  "$ref": "#/components/schemas/PackageListResponse"
                }
              }
            },
            "description": "Succès"
          },
          "400": {
            "content": {
              "application/json": {
                "schema": {
                  "$ref": "#/components/schemas/ErrorEnvelope"
                }
              }
            },
            "description": "Erreur: `internal.validation_failed`, `internal.invalid_filter`, `catalog.cursor_mismatch`"
          },
          "401": {
            "content": {
              "application/json": {
                "schema": {
                  "$ref": "#/components/schemas/ErrorEnvelope"
                }
              }
            },
            "description": "Erreur: `partner.invalid_key`, `partner.invalid_signature`, `partner.timestamp_expired`, `partner.nonce_reused`"
          },
          "403": {
            "content": {
              "application/json": {
                "schema": {
                  "$ref": "#/components/schemas/ErrorEnvelope"
                }
              }
            },
            "description": "Erreur: `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": "Erreur: `partner.rate_limited`"
          },
          "503": {
            "content": {
              "application/json": {
                "schema": {
                  "$ref": "#/components/schemas/ErrorEnvelope"
                }
              }
            },
            "description": "Erreur: `partner.endpoint_disabled`, `internal.service_unavailable`"
          }
        },
        "summary": "Lister les forfaits",
        "tags": [
          "catalog"
        ],
        "x-tke-modes": [
          "live",
          "test"
        ],
        "x-tke-owner-types": [
          "organization",
          "member"
        ]
      }
    },
    "/v1/catalog/packages/{code}": {
      "get": {
        "description": "Autorisation requise: `catalog:read`.\n\nDétail d’UN SEUL forfait par son code public. `data` contient les MÊMES CHAMPS qu’un élément de `GET /v1/catalog/packages`, plus `mode`. Aucun champ propre au détail : un seul type `Package` suffit.\n\nUn code invisible renvoie `partner.package_not_visible` (404), identique pour toutes les causes : code inexistant, restrictions de clé, tarif absent pour l’organisation ou lien fournisseur perdu. Distinguer les causes permettrait à un inconnu d’énumérer le catalogue.",
        "operationId": "get_catalog_packages__code_",
        "parameters": [
          {
            "description": "Code du forfait, soit `code` dans le catalogue (`public_sku`). IMMUABLE même en cas de changement de fournisseur ; utilisable comme clé de rapprochement. Code hors restrictions de clé ou sans prix pour l’organisation : `404 partner.package_not_visible`, comme pour un code inexistant.",
            "in": "path",
            "name": "code",
            "required": true,
            "schema": {
              "type": "string"
            }
          }
        ],
        "responses": {
          "200": {
            "content": {
              "application/json": {
                "schema": {
                  "$ref": "#/components/schemas/PackageDetail"
                }
              }
            },
            "description": "Succès"
          },
          "400": {
            "content": {
              "application/json": {
                "schema": {
                  "$ref": "#/components/schemas/ErrorEnvelope"
                }
              }
            },
            "description": "Erreur: `internal.validation_failed`"
          },
          "401": {
            "content": {
              "application/json": {
                "schema": {
                  "$ref": "#/components/schemas/ErrorEnvelope"
                }
              }
            },
            "description": "Erreur: `partner.invalid_key`, `partner.invalid_signature`, `partner.timestamp_expired`, `partner.nonce_reused`"
          },
          "403": {
            "content": {
              "application/json": {
                "schema": {
                  "$ref": "#/components/schemas/ErrorEnvelope"
                }
              }
            },
            "description": "Erreur: `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": "Erreur: `partner.package_not_visible`"
          },
          "429": {
            "content": {
              "application/json": {
                "schema": {
                  "$ref": "#/components/schemas/ErrorEnvelope"
                }
              }
            },
            "description": "Erreur: `partner.rate_limited`"
          },
          "503": {
            "content": {
              "application/json": {
                "schema": {
                  "$ref": "#/components/schemas/ErrorEnvelope"
                }
              }
            },
            "description": "Erreur: `partner.endpoint_disabled`, `internal.service_unavailable`"
          }
        },
        "summary": "Lire un forfait",
        "tags": [
          "catalog"
        ],
        "x-tke-modes": [
          "live",
          "test"
        ],
        "x-tke-owner-types": [
          "organization",
          "member"
        ]
      }
    },
    "/v1/esims": {
      "get": {
        "description": "Autorisation requise: `esims:read`.",
        "operationId": "get_esims",
        "parameters": [
          {
            "description": "Lignes par page. Valeur par défaut 50, maximum 200. Une valeur supérieure est ramenée à 200 sans erreur.",
            "in": "query",
            "name": "page_size",
            "required": false,
            "schema": {
              "default": 50,
              "maximum": 200,
              "minimum": 1,
              "type": "integer"
            }
          },
          {
            "description": "Filtrer par `esim_status`. Une valeur hors liste renvoie `internal.invalid_filter` (400), et non une liste vide : une faute comme `canceled` au lieu de `cancelled` ne doit pas passer pour une absence d’eSIM.",
            "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": "Uniquement les eSIM d’une même commande.",
            "in": "query",
            "name": "order_id",
            "required": false,
            "schema": {
              "format": "uuid",
              "type": "string"
            }
          },
          {
            "description": "Filtrer par ICCID (chaîne, correspondance exacte).",
            "in": "query",
            "name": "iccid",
            "required": false,
            "schema": {
              "type": "string"
            }
          },
          {
            "description": "Toutes les eSIM des commandes portant votre référence `external_ref` (correspondance exacte).",
            "in": "query",
            "name": "external_ref",
            "required": false,
            "schema": {
              "type": "string"
            }
          },
          {
            "description": "Date ISO-8601 UTC. Renvoie les enregistrements où `updated_at >= updated_since`, dans l’ordre croissant : voie standard de RAPPROCHEMENT PAR INTERROGATION (§8.5), permettant de retrouver l’état même après un webhook manqué. Format incorrect : `internal.invalid_filter` (400).",
            "in": "query",
            "name": "updated_since",
            "required": false,
            "schema": {
              "format": "date-time",
              "type": "string"
            }
          },
          {
            "description": "Curseur suivant : renvoyez `next_cursor` sans modification. Un curseur mal formé relit le début sans erreur pour préserver les intégrations lors des changements d'encodage. Vérifiez que le curseur change pour éviter une boucle infinie sur la première page.",
            "in": "query",
            "name": "cursor",
            "required": false,
            "schema": {
              "type": "string"
            }
          },
          {
            "description": "Blocs supplémentaires séparés par des virgules : `lpa` retourne `install`, `qr` ajoute `install.qr_code_base64` (détail uniquement). **RETOURNER LE CODE LE DIVULGUE** et ferme définitivement l'annulation de chaque eSIM concernée (`cancellable.allowed = false`, `reason = code_disclosed`). Demandez-le à la livraison, omettez-le pour conserver l'annulation. Valeur inconnue : `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": "Succès"
          },
          "400": {
            "content": {
              "application/json": {
                "schema": {
                  "$ref": "#/components/schemas/ErrorEnvelope"
                }
              }
            },
            "description": "Erreur: `internal.validation_failed`, `internal.invalid_filter`"
          },
          "401": {
            "content": {
              "application/json": {
                "schema": {
                  "$ref": "#/components/schemas/ErrorEnvelope"
                }
              }
            },
            "description": "Erreur: `partner.invalid_key`, `partner.invalid_signature`, `partner.timestamp_expired`, `partner.nonce_reused`"
          },
          "403": {
            "content": {
              "application/json": {
                "schema": {
                  "$ref": "#/components/schemas/ErrorEnvelope"
                }
              }
            },
            "description": "Erreur: `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": "Erreur: `partner.rate_limited`"
          },
          "503": {
            "content": {
              "application/json": {
                "schema": {
                  "$ref": "#/components/schemas/ErrorEnvelope"
                }
              }
            },
            "description": "Erreur: `partner.endpoint_disabled`, `internal.service_unavailable`"
          }
        },
        "summary": "Lister les eSIM (rapprochement par interrogation)",
        "tags": [
          "esims"
        ],
        "x-tke-modes": [
          "live",
          "test"
        ],
        "x-tke-owner-types": [
          "organization",
          "member"
        ]
      }
    },
    "/v1/esims/{id}": {
      "get": {
        "description": "Autorisation requise: `esims:read`.",
        "operationId": "get_esims__id_",
        "parameters": [
          {
            "description": "Identifiant stable d'une eSIM vendue, différent de l'ICCID qui peut être réutilisé.",
            "in": "path",
            "name": "id",
            "required": true,
            "schema": {
              "type": "string"
            }
          },
          {
            "description": "Blocs supplémentaires séparés par des virgules : `lpa` retourne `install`, `qr` ajoute `install.qr_code_base64` (détail uniquement). **RETOURNER LE CODE LE DIVULGUE** et ferme définitivement l'annulation de chaque eSIM concernée (`cancellable.allowed = false`, `reason = code_disclosed`). Demandez-le à la livraison, omettez-le pour conserver l'annulation. Valeur inconnue : `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": "Succès"
          },
          "400": {
            "content": {
              "application/json": {
                "schema": {
                  "$ref": "#/components/schemas/ErrorEnvelope"
                }
              }
            },
            "description": "Erreur: `internal.validation_failed`, `internal.invalid_filter`"
          },
          "401": {
            "content": {
              "application/json": {
                "schema": {
                  "$ref": "#/components/schemas/ErrorEnvelope"
                }
              }
            },
            "description": "Erreur: `partner.invalid_key`, `partner.invalid_signature`, `partner.timestamp_expired`, `partner.nonce_reused`"
          },
          "403": {
            "content": {
              "application/json": {
                "schema": {
                  "$ref": "#/components/schemas/ErrorEnvelope"
                }
              }
            },
            "description": "Erreur: `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": "Erreur: `internal.not_found`"
          },
          "429": {
            "content": {
              "application/json": {
                "schema": {
                  "$ref": "#/components/schemas/ErrorEnvelope"
                }
              }
            },
            "description": "Erreur: `partner.rate_limited`"
          },
          "503": {
            "content": {
              "application/json": {
                "schema": {
                  "$ref": "#/components/schemas/ErrorEnvelope"
                }
              }
            },
            "description": "Erreur: `partner.endpoint_disabled`, `internal.service_unavailable`"
          }
        },
        "summary": "Lire une eSIM",
        "tags": [
          "esims"
        ],
        "x-tke-modes": [
          "live",
          "test"
        ],
        "x-tke-owner-types": [
          "organization",
          "member"
        ]
      }
    },
    "/v1/esims/{id}/cancel": {
      "post": {
        "description": "Autorisation requise: `esims:write`.",
        "operationId": "post_esims__id__cancel",
        "parameters": [
          {
            "description": "Identifiant stable d'une eSIM vendue, différent de l'ICCID qui peut être réutilisé.",
            "in": "path",
            "name": "id",
            "required": true,
            "schema": {
              "type": "string"
            }
          },
          {
            "description": "Chaîne créée par le client : même clé et même corps exécutés une seule fois, avec résultat initial aux reprises. Portée par `api_key_id` (§11) : deux clés distinctes sont deux commandes.",
            "in": "header",
            "name": "Idempotency-Key",
            "required": true,
            "schema": {
              "type": "string"
            }
          }
        ],
        "responses": {
          "200": {
            "content": {
              "application/json": {
                "schema": {
                  "$ref": "#/components/schemas/CancelResponse"
                }
              }
            },
            "description": "Succès"
          },
          "400": {
            "content": {
              "application/json": {
                "schema": {
                  "$ref": "#/components/schemas/ErrorEnvelope"
                }
              }
            },
            "description": "Erreur: `internal.validation_failed`"
          },
          "401": {
            "content": {
              "application/json": {
                "schema": {
                  "$ref": "#/components/schemas/ErrorEnvelope"
                }
              }
            },
            "description": "Erreur: `partner.invalid_key`, `partner.invalid_signature`, `partner.timestamp_expired`, `partner.nonce_reused`"
          },
          "403": {
            "content": {
              "application/json": {
                "schema": {
                  "$ref": "#/components/schemas/ErrorEnvelope"
                }
              }
            },
            "description": "Erreur: `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": "Erreur: `esim.not_cancellable`, `idempotency.conflict`"
          },
          "422": {
            "content": {
              "application/json": {
                "schema": {
                  "$ref": "#/components/schemas/ErrorEnvelope"
                }
              }
            },
            "description": "Erreur: `capability.not_supported`"
          },
          "429": {
            "content": {
              "application/json": {
                "schema": {
                  "$ref": "#/components/schemas/ErrorEnvelope"
                }
              }
            },
            "description": "Erreur: `partner.rate_limited`"
          },
          "503": {
            "content": {
              "application/json": {
                "schema": {
                  "$ref": "#/components/schemas/ErrorEnvelope"
                }
              }
            },
            "description": "Erreur: `partner.endpoint_disabled`, `internal.service_unavailable`"
          }
        },
        "summary": "Annuler une eSIM inutilisée",
        "tags": [
          "esims"
        ],
        "x-tke-modes": [
          "live",
          "test"
        ],
        "x-tke-owner-types": [
          "organization",
          "member"
        ]
      }
    },
    "/v1/esims/{id}/change-plan": {
      "post": {
        "description": "Autorisation requise: `esims:write`.",
        "operationId": "post_esims__id__change-plan",
        "parameters": [
          {
            "description": "Identifiant stable d'une eSIM vendue, différent de l'ICCID qui peut être réutilisé.",
            "in": "path",
            "name": "id",
            "required": true,
            "schema": {
              "type": "string"
            }
          },
          {
            "description": "Chaîne créée par le client : même clé et même corps exécutés une seule fois, avec résultat initial aux reprises. Portée par `api_key_id` (§11) : deux clés distinctes sont deux commandes.",
            "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": "Succès"
          },
          "400": {
            "content": {
              "application/json": {
                "schema": {
                  "$ref": "#/components/schemas/ErrorEnvelope"
                }
              }
            },
            "description": "Erreur: `internal.validation_failed`"
          },
          "401": {
            "content": {
              "application/json": {
                "schema": {
                  "$ref": "#/components/schemas/ErrorEnvelope"
                }
              }
            },
            "description": "Erreur: `partner.invalid_key`, `partner.invalid_signature`, `partner.timestamp_expired`, `partner.nonce_reused`"
          },
          "402": {
            "content": {
              "application/json": {
                "schema": {
                  "$ref": "#/components/schemas/ErrorEnvelope"
                }
              }
            },
            "description": "Erreur: `wallet.insufficient_balance`"
          },
          "403": {
            "content": {
              "application/json": {
                "schema": {
                  "$ref": "#/components/schemas/ErrorEnvelope"
                }
              }
            },
            "description": "Erreur: `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": "Erreur: `partner.package_not_visible`"
          },
          "409": {
            "content": {
              "application/json": {
                "schema": {
                  "$ref": "#/components/schemas/ErrorEnvelope"
                }
              }
            },
            "description": "Erreur: `esim.topup_not_supported`, `esim.topup_provider_mismatch`, `esim.not_eligible`, `idempotency.conflict`"
          },
          "422": {
            "content": {
              "application/json": {
                "schema": {
                  "$ref": "#/components/schemas/ErrorEnvelope"
                }
              }
            },
            "description": "Erreur: `capability.not_supported`"
          },
          "429": {
            "content": {
              "application/json": {
                "schema": {
                  "$ref": "#/components/schemas/ErrorEnvelope"
                }
              }
            },
            "description": "Erreur: `partner.rate_limited`"
          },
          "503": {
            "content": {
              "application/json": {
                "schema": {
                  "$ref": "#/components/schemas/ErrorEnvelope"
                }
              }
            },
            "description": "Erreur: `partner.endpoint_disabled`, `internal.service_unavailable`"
          }
        },
        "summary": "Recharger une eSIM",
        "tags": [
          "esims"
        ],
        "x-tke-modes": [
          "live",
          "test"
        ],
        "x-tke-owner-types": [
          "organization",
          "member"
        ]
      }
    },
    "/v1/esims/{id}/plan-options": {
      "get": {
        "description": "Autorisation requise: `esims:read`.\n\nRenvoie les recharges valides pour CETTE eSIM. Le serveur déduit le fournisseur de son `id` et ne renvoie que ses forfaits. Aucun `provider_id` n’est exposé sur `/v1` : ce filtrage empêche structurellement de choisir un forfait d’un autre fournisseur. Envoyer à `POST /v1/esims/{id}/change-plan` un code absent de cette liste renvoie `409 esim.topup_provider_mismatch`.\n\nUne eSIM sans fournisseur associé (import brut ou origine inconnue) renvoie une liste vide (`total: 0`), résultat normal et non une erreur.",
        "operationId": "get_esims__id__plan-options",
        "parameters": [
          {
            "description": "Identifiant stable d'une eSIM vendue, différent de l'ICCID qui peut être réutilisé.",
            "in": "path",
            "name": "id",
            "required": true,
            "schema": {
              "type": "string"
            }
          },
          {
            "description": "Lignes par page. Valeur par défaut 50, maximum 200. Une valeur supérieure est ramenée à 200 sans erreur.",
            "in": "query",
            "name": "page_size",
            "required": false,
            "schema": {
              "default": 50,
              "maximum": 200,
              "minimum": 1,
              "type": "integer"
            }
          },
          {
            "description": "Curseur suivant : renvoyez `next_cursor` sans modification. Un curseur mal formé relit le début sans erreur pour préserver les intégrations lors des changements d'encodage. Vérifiez que le curseur change pour éviter une boucle infinie sur la première page.",
            "in": "query",
            "name": "cursor",
            "required": false,
            "schema": {
              "type": "string"
            }
          }
        ],
        "responses": {
          "200": {
            "content": {
              "application/json": {
                "schema": {
                  "$ref": "#/components/schemas/PlanOptionsResponse"
                }
              }
            },
            "description": "Succès"
          },
          "400": {
            "content": {
              "application/json": {
                "schema": {
                  "$ref": "#/components/schemas/ErrorEnvelope"
                }
              }
            },
            "description": "Erreur: `internal.validation_failed`"
          },
          "401": {
            "content": {
              "application/json": {
                "schema": {
                  "$ref": "#/components/schemas/ErrorEnvelope"
                }
              }
            },
            "description": "Erreur: `partner.invalid_key`, `partner.invalid_signature`, `partner.timestamp_expired`, `partner.nonce_reused`"
          },
          "403": {
            "content": {
              "application/json": {
                "schema": {
                  "$ref": "#/components/schemas/ErrorEnvelope"
                }
              }
            },
            "description": "Erreur: `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": "Erreur: `internal.not_found`"
          },
          "429": {
            "content": {
              "application/json": {
                "schema": {
                  "$ref": "#/components/schemas/ErrorEnvelope"
                }
              }
            },
            "description": "Erreur: `partner.rate_limited`"
          },
          "503": {
            "content": {
              "application/json": {
                "schema": {
                  "$ref": "#/components/schemas/ErrorEnvelope"
                }
              }
            },
            "description": "Erreur: `partner.endpoint_disabled`, `internal.service_unavailable`"
          }
        },
        "summary": "Forfaits de recharge valides pour cette eSIM",
        "tags": [
          "esims"
        ],
        "x-tke-modes": [
          "live",
          "test"
        ],
        "x-tke-owner-types": [
          "organization",
          "member"
        ]
      }
    },
    "/v1/esims/{id}/refund-requests": {
      "get": {
        "description": "Autorisation requise: `esims:read`.\n\nClés membre réelles autorisées uniquement. Renvoie au maximum 50 demandes pour l’eSIM du membre, les plus récentes d’abord ; next_cursor et total valent null. has_open_request signale une validation en attente. Les clés organisation et test reçoivent capability.not_supported.",
        "operationId": "get_esims__id__refund-requests",
        "parameters": [
          {
            "description": "Identifiant stable d'une eSIM vendue, différent de l'ICCID qui peut être réutilisé.",
            "in": "path",
            "name": "id",
            "required": true,
            "schema": {
              "type": "string"
            }
          }
        ],
        "responses": {
          "200": {
            "content": {
              "application/json": {
                "schema": {
                  "$ref": "#/components/schemas/MemberRefundRequestListResponse"
                }
              }
            },
            "description": "Succès"
          },
          "400": {
            "content": {
              "application/json": {
                "schema": {
                  "$ref": "#/components/schemas/ErrorEnvelope"
                }
              }
            },
            "description": "Erreur: `internal.validation_failed`"
          },
          "401": {
            "content": {
              "application/json": {
                "schema": {
                  "$ref": "#/components/schemas/ErrorEnvelope"
                }
              }
            },
            "description": "Erreur: `partner.invalid_key`, `partner.invalid_signature`, `partner.timestamp_expired`, `partner.nonce_reused`"
          },
          "403": {
            "content": {
              "application/json": {
                "schema": {
                  "$ref": "#/components/schemas/ErrorEnvelope"
                }
              }
            },
            "description": "Erreur: `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": "Erreur: `internal.not_found`"
          },
          "422": {
            "content": {
              "application/json": {
                "schema": {
                  "$ref": "#/components/schemas/ErrorEnvelope"
                }
              }
            },
            "description": "Erreur: `capability.not_supported`"
          },
          "429": {
            "content": {
              "application/json": {
                "schema": {
                  "$ref": "#/components/schemas/ErrorEnvelope"
                }
              }
            },
            "description": "Erreur: `partner.rate_limited`"
          },
          "503": {
            "content": {
              "application/json": {
                "schema": {
                  "$ref": "#/components/schemas/ErrorEnvelope"
                }
              }
            },
            "description": "Erreur: `partner.endpoint_disabled`, `internal.service_unavailable`"
          }
        },
        "summary": "Historique des demandes de remboursement eSIM (membres uniquement)",
        "tags": [
          "esims"
        ],
        "x-tke-modes": [
          "live"
        ],
        "x-tke-owner-types": [
          "member"
        ]
      },
      "post": {
        "description": "Autorisation requise: `esims:write`.\n\nClés membre réelles autorisées uniquement. Soumettez une demande pour l’eSIM du membre avec Idempotency-Key ; aucun transfert avant validation interne. Les clés organisation ou test reçoivent capability.not_supported. En test, annulez une eSIM pour essayer le remboursement virtuel.",
        "operationId": "post_esims__id__refund-requests",
        "parameters": [
          {
            "description": "Identifiant stable d'une eSIM vendue, différent de l'ICCID qui peut être réutilisé.",
            "in": "path",
            "name": "id",
            "required": true,
            "schema": {
              "type": "string"
            }
          },
          {
            "description": "Chaîne créée par le client : même clé et même corps exécutés une seule fois, avec résultat initial aux reprises. Portée par `api_key_id` (§11) : deux clés distinctes sont deux commandes.",
            "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": "Succès"
          },
          "400": {
            "content": {
              "application/json": {
                "schema": {
                  "$ref": "#/components/schemas/ErrorEnvelope"
                }
              }
            },
            "description": "Erreur: `internal.validation_failed`, `internal.validation_failed`"
          },
          "401": {
            "content": {
              "application/json": {
                "schema": {
                  "$ref": "#/components/schemas/ErrorEnvelope"
                }
              }
            },
            "description": "Erreur: `partner.invalid_key`, `partner.invalid_signature`, `partner.timestamp_expired`, `partner.nonce_reused`"
          },
          "403": {
            "content": {
              "application/json": {
                "schema": {
                  "$ref": "#/components/schemas/ErrorEnvelope"
                }
              }
            },
            "description": "Erreur: `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": "Erreur: `internal.not_found`"
          },
          "409": {
            "content": {
              "application/json": {
                "schema": {
                  "$ref": "#/components/schemas/ErrorEnvelope"
                }
              }
            },
            "description": "Erreur: `esim.refund_not_eligible`, `esim.refund_request_pending`, `idempotency.conflict`"
          },
          "422": {
            "content": {
              "application/json": {
                "schema": {
                  "$ref": "#/components/schemas/ErrorEnvelope"
                }
              }
            },
            "description": "Erreur: `capability.not_supported`"
          },
          "429": {
            "content": {
              "application/json": {
                "schema": {
                  "$ref": "#/components/schemas/ErrorEnvelope"
                }
              }
            },
            "description": "Erreur: `partner.rate_limited`"
          },
          "503": {
            "content": {
              "application/json": {
                "schema": {
                  "$ref": "#/components/schemas/ErrorEnvelope"
                }
              }
            },
            "description": "Erreur: `partner.endpoint_disabled`, `internal.service_unavailable`"
          }
        },
        "summary": "Créer une demande de remboursement eSIM (membres uniquement)",
        "tags": [
          "esims"
        ],
        "x-tke-modes": [
          "live"
        ],
        "x-tke-owner-types": [
          "member"
        ]
      }
    },
    "/v1/esims/{id}/usage": {
      "get": {
        "description": "Autorisation requise: `esims:read`.\n\nLit les données consommées et restantes d’une eSIM, avec l’horodatage `updated_at` des chiffres (fraîcheur réelle). Pour une eSIM d’un fournisseur lu uniquement à la demande (actuellement SkyFi), cet appel met un scan en file d’attente et ATTEND le résultat jusqu’à environ 15 secondes ; la réponse peut donc prendre jusqu’à ~15 secondes et les chiffres renvoyés proviennent du scan qui vient de se terminer. Si le scan échoue, si l’eSIM a été scannée depuis moins de 15 secondes ou si l’attente expire, AUCUNE erreur n’est renvoyée : les derniers chiffres enregistrés sont renvoyés, vérifiez `updated_at`. Les autres fournisseurs renvoient les chiffres enregistrés, actualisés périodiquement.",
        "operationId": "get_esims__id__usage",
        "parameters": [
          {
            "description": "Identifiant stable d'une eSIM vendue, différent de l'ICCID qui peut être réutilisé.",
            "in": "path",
            "name": "id",
            "required": true,
            "schema": {
              "type": "string"
            }
          }
        ],
        "responses": {
          "200": {
            "content": {
              "application/json": {
                "schema": {
                  "$ref": "#/components/schemas/EsimUsage"
                }
              }
            },
            "description": "Succès"
          },
          "400": {
            "content": {
              "application/json": {
                "schema": {
                  "$ref": "#/components/schemas/ErrorEnvelope"
                }
              }
            },
            "description": "Erreur: `internal.validation_failed`"
          },
          "401": {
            "content": {
              "application/json": {
                "schema": {
                  "$ref": "#/components/schemas/ErrorEnvelope"
                }
              }
            },
            "description": "Erreur: `partner.invalid_key`, `partner.invalid_signature`, `partner.timestamp_expired`, `partner.nonce_reused`"
          },
          "403": {
            "content": {
              "application/json": {
                "schema": {
                  "$ref": "#/components/schemas/ErrorEnvelope"
                }
              }
            },
            "description": "Erreur: `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": "Erreur: `capability.not_supported`"
          },
          "429": {
            "content": {
              "application/json": {
                "schema": {
                  "$ref": "#/components/schemas/ErrorEnvelope"
                }
              }
            },
            "description": "Erreur: `partner.rate_limited`"
          },
          "503": {
            "content": {
              "application/json": {
                "schema": {
                  "$ref": "#/components/schemas/ErrorEnvelope"
                }
              }
            },
            "description": "Erreur: `partner.endpoint_disabled`, `internal.service_unavailable`"
          }
        },
        "summary": "Données utilisées",
        "tags": [
          "esims"
        ],
        "x-tke-modes": [
          "live",
          "test"
        ],
        "x-tke-owner-types": [
          "organization",
          "member"
        ]
      }
    },
    "/v1/orders": {
      "get": {
        "description": "Autorisation requise: `orders:read`.",
        "operationId": "get_orders",
        "parameters": [
          {
            "description": "Lignes par page. Valeur par défaut 50, maximum 200. Une valeur supérieure est ramenée à 200 sans erreur.",
            "in": "query",
            "name": "page_size",
            "required": false,
            "schema": {
              "default": 50,
              "maximum": 200,
              "minimum": 1,
              "type": "integer"
            }
          },
          {
            "description": "Filtrer par état de commande. Une valeur hors liste renvoie `internal.invalid_filter` (400).",
            "in": "query",
            "name": "status",
            "required": false,
            "schema": {
              "enum": [
                "completed",
                "pending_provider",
                "provider_failed",
                "cancelled"
              ],
              "type": "string"
            }
          },
          {
            "description": "Filtrer par la référence envoyée à la validation de commande (correspondance EXACTE).",
            "in": "query",
            "name": "external_ref",
            "required": false,
            "schema": {
              "type": "string"
            }
          },
          {
            "description": "Date ISO-8601 UTC. Renvoie les enregistrements où `updated_at >= updated_since`, dans l’ordre croissant : voie standard de RAPPROCHEMENT PAR INTERROGATION (§8.5), permettant de retrouver l’état même après un webhook manqué. Format incorrect : `internal.invalid_filter` (400).",
            "in": "query",
            "name": "updated_since",
            "required": false,
            "schema": {
              "format": "date-time",
              "type": "string"
            }
          },
          {
            "description": "Curseur suivant : renvoyez `next_cursor` sans modification. Un curseur mal formé relit le début sans erreur pour préserver les intégrations lors des changements d'encodage. Vérifiez que le curseur change pour éviter une boucle infinie sur la première page.",
            "in": "query",
            "name": "cursor",
            "required": false,
            "schema": {
              "type": "string"
            }
          }
        ],
        "responses": {
          "200": {
            "content": {
              "application/json": {
                "schema": {
                  "$ref": "#/components/schemas/OrderListResponse"
                }
              }
            },
            "description": "Succès"
          },
          "400": {
            "content": {
              "application/json": {
                "schema": {
                  "$ref": "#/components/schemas/ErrorEnvelope"
                }
              }
            },
            "description": "Erreur: `internal.validation_failed`, `internal.invalid_filter`"
          },
          "401": {
            "content": {
              "application/json": {
                "schema": {
                  "$ref": "#/components/schemas/ErrorEnvelope"
                }
              }
            },
            "description": "Erreur: `partner.invalid_key`, `partner.invalid_signature`, `partner.timestamp_expired`, `partner.nonce_reused`"
          },
          "403": {
            "content": {
              "application/json": {
                "schema": {
                  "$ref": "#/components/schemas/ErrorEnvelope"
                }
              }
            },
            "description": "Erreur: `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": "Erreur: `partner.rate_limited`"
          },
          "503": {
            "content": {
              "application/json": {
                "schema": {
                  "$ref": "#/components/schemas/ErrorEnvelope"
                }
              }
            },
            "description": "Erreur: `partner.endpoint_disabled`, `internal.service_unavailable`"
          }
        },
        "summary": "Lister les commandes (rapprochement par interrogation)",
        "tags": [
          "orders"
        ],
        "x-tke-modes": [
          "live",
          "test"
        ],
        "x-tke-owner-types": [
          "organization",
          "member"
        ]
      },
      "post": {
        "description": "Autorisation requise: `orders:write`.",
        "operationId": "post_orders",
        "parameters": [
          {
            "description": "Chaîne créée par le client : même clé et même corps exécutés une seule fois, avec résultat initial aux reprises. Portée par `api_key_id` (§11) : deux clés distinctes sont deux commandes.",
            "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": "Reprise dans les 24 h avec les mêmes `Idempotency-Key` et corps : corps IDENTIQUE, aucun nouveau débit, en-tête `Idempotent-Replayed: true`. Une nouvelle commande renvoie `201` ; ne déduisez pas le succès de ce seul statut."
          },
          "201": {
            "content": {
              "application/json": {
                "schema": {
                  "$ref": "#/components/schemas/OrderResponse"
                }
              }
            },
            "description": "Succès"
          },
          "400": {
            "content": {
              "application/json": {
                "schema": {
                  "$ref": "#/components/schemas/ErrorEnvelope"
                }
              }
            },
            "description": "Erreur: `internal.validation_failed`, `internal.invalid_filter`"
          },
          "401": {
            "content": {
              "application/json": {
                "schema": {
                  "$ref": "#/components/schemas/ErrorEnvelope"
                }
              }
            },
            "description": "Erreur: `partner.invalid_key`, `partner.invalid_signature`, `partner.timestamp_expired`, `partner.nonce_reused`"
          },
          "402": {
            "content": {
              "application/json": {
                "schema": {
                  "$ref": "#/components/schemas/ErrorEnvelope"
                }
              }
            },
            "description": "Erreur: `wallet.insufficient_balance`"
          },
          "403": {
            "content": {
              "application/json": {
                "schema": {
                  "$ref": "#/components/schemas/ErrorEnvelope"
                }
              }
            },
            "description": "Erreur: `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": "Erreur: `partner.package_not_visible`"
          },
          "409": {
            "content": {
              "application/json": {
                "schema": {
                  "$ref": "#/components/schemas/ErrorEnvelope"
                }
              }
            },
            "description": "Erreur: `esim.out_of_stock`, `idempotency.conflict`"
          },
          "422": {
            "content": {
              "application/json": {
                "schema": {
                  "$ref": "#/components/schemas/ErrorEnvelope"
                }
              }
            },
            "description": "Erreur: `invoice.buyer_invalid`"
          },
          "429": {
            "content": {
              "application/json": {
                "schema": {
                  "$ref": "#/components/schemas/ErrorEnvelope"
                }
              }
            },
            "description": "Erreur: `partner.rate_limited`"
          },
          "503": {
            "content": {
              "application/json": {
                "schema": {
                  "$ref": "#/components/schemas/ErrorEnvelope"
                }
              }
            },
            "description": "Erreur: `partner.endpoint_disabled`, `internal.service_unavailable`"
          }
        },
        "summary": "Créer une commande (achat direct)",
        "tags": [
          "orders"
        ],
        "x-tke-modes": [
          "live",
          "test"
        ],
        "x-tke-owner-types": [
          "organization",
          "member"
        ]
      }
    },
    "/v1/orders/bulk": {
      "post": {
        "description": "Autorisation requise: `orders:write`.\n\nCommander un lot multi-lignes : chaque ligne crée une commande indépendante, suivant le cycle quote→commit de `POST /v1/orders`.\n\nSUCCÈS PARTIEL : les erreurs d’une ligne ne bloquent pas les autres, et seules les lignes réussies sont débitées. Les erreurs métier (rupture, solde insuffisant, forfait indisponible, seuil anti-abus) figurent dans `lines[].error`, sans changer le statut HTTP du lot. Même après épuisement du solde, les lignes suivantes sont tentées car elles peuvent coûter moins cher.\n\nLe statut est toujours `200` : résultats par ligne, pas une nouvelle ressource unique (`201`), et traitement terminé dans la requête, sans `202` ni interrogation.\n\nRéessayer le MÊME `Idempotency-Key` avec le MÊME corps recharge le lot existant, conserve les résultats terminés et traite uniquement les lignes `pending`, sans double débit. Si aucune ligne n’a été exécutée, `Idempotent-Replayed: true`. Même clé avec autre corps : `idempotency.conflict`.\n\nDeux lots au contenu identique pour une même organisation en 10 minutes sont traités comme un doublon accidentel, même avec des clés différentes : l’ancien lot est renvoyé avec `Idempotent-Replayed: true`, sans nouveau débit. Pour un nouvel achat réellement identique, changez `external_ref` : inclus dans le contenu signé, il distingue les lots et facilite le rapprochement. Un lot totalement échoué (aucune commande créée) n’est jamais bloqué ; après 10 minutes l’achat est à nouveau normal.\n\nLes références de ligne sont renvoyées dans `lines[].external_ref`, celle du lot en tête de réponse.\n\nAucun LPA/QR dans la réponse : lire `GET /v1/orders/{order_id}` pour chaque commande.",
        "operationId": "post_orders_bulk",
        "parameters": [
          {
            "description": "Chaîne créée par le client : même clé et même corps exécutés une seule fois, avec résultat initial aux reprises. Portée par `api_key_id` (§11) : deux clés distinctes sont deux commandes.",
            "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": "Succès"
          },
          "400": {
            "content": {
              "application/json": {
                "schema": {
                  "$ref": "#/components/schemas/ErrorEnvelope"
                }
              }
            },
            "description": "Erreur: `internal.validation_failed`"
          },
          "401": {
            "content": {
              "application/json": {
                "schema": {
                  "$ref": "#/components/schemas/ErrorEnvelope"
                }
              }
            },
            "description": "Erreur: `partner.invalid_key`, `partner.invalid_signature`, `partner.timestamp_expired`, `partner.nonce_reused`"
          },
          "403": {
            "content": {
              "application/json": {
                "schema": {
                  "$ref": "#/components/schemas/ErrorEnvelope"
                }
              }
            },
            "description": "Erreur: `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": "Erreur: `partner.package_not_visible`"
          },
          "409": {
            "content": {
              "application/json": {
                "schema": {
                  "$ref": "#/components/schemas/ErrorEnvelope"
                }
              }
            },
            "description": "Erreur: `idempotency.conflict`"
          },
          "422": {
            "content": {
              "application/json": {
                "schema": {
                  "$ref": "#/components/schemas/ErrorEnvelope"
                }
              }
            },
            "description": "Erreur: `internal.limit_exceeded`"
          },
          "429": {
            "content": {
              "application/json": {
                "schema": {
                  "$ref": "#/components/schemas/ErrorEnvelope"
                }
              }
            },
            "description": "Erreur: `partner.rate_limited`"
          },
          "503": {
            "content": {
              "application/json": {
                "schema": {
                  "$ref": "#/components/schemas/ErrorEnvelope"
                }
              }
            },
            "description": "Erreur: `partner.endpoint_disabled`, `internal.service_unavailable`"
          }
        },
        "summary": "Passer un lot multi-lignes (une commande par ligne)",
        "tags": [
          "orders"
        ],
        "x-tke-modes": [
          "live",
          "test"
        ],
        "x-tke-owner-types": [
          "organization"
        ]
      }
    },
    "/v1/orders/bulk/{id}": {
      "get": {
        "description": "Autorisation requise: `orders:read`.\n\nRelire les résultats par ligne d’un lot déjà soumis, pour récupérer une réponse perdue sans renvoyer le corps. Un lot d’une autre organisation ou d’un autre environnement (production/test) renvoie `404`.",
        "operationId": "get_orders_bulk__id_",
        "parameters": [
          {
            "description": "ID du lot (UUID) renvoyé par POST /v1/orders/bulk.",
            "in": "path",
            "name": "id",
            "required": true,
            "schema": {
              "type": "string"
            }
          }
        ],
        "responses": {
          "200": {
            "content": {
              "application/json": {
                "schema": {
                  "$ref": "#/components/schemas/BulkBatchResponse"
                }
              }
            },
            "description": "Succès"
          },
          "400": {
            "content": {
              "application/json": {
                "schema": {
                  "$ref": "#/components/schemas/ErrorEnvelope"
                }
              }
            },
            "description": "Erreur: `internal.validation_failed`"
          },
          "401": {
            "content": {
              "application/json": {
                "schema": {
                  "$ref": "#/components/schemas/ErrorEnvelope"
                }
              }
            },
            "description": "Erreur: `partner.invalid_key`, `partner.invalid_signature`, `partner.timestamp_expired`, `partner.nonce_reused`"
          },
          "403": {
            "content": {
              "application/json": {
                "schema": {
                  "$ref": "#/components/schemas/ErrorEnvelope"
                }
              }
            },
            "description": "Erreur: `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": "Erreur: `internal.not_found`"
          },
          "429": {
            "content": {
              "application/json": {
                "schema": {
                  "$ref": "#/components/schemas/ErrorEnvelope"
                }
              }
            },
            "description": "Erreur: `partner.rate_limited`"
          },
          "503": {
            "content": {
              "application/json": {
                "schema": {
                  "$ref": "#/components/schemas/ErrorEnvelope"
                }
              }
            },
            "description": "Erreur: `partner.endpoint_disabled`, `internal.service_unavailable`"
          }
        },
        "summary": "Résultat d’une commande par lot",
        "tags": [
          "orders"
        ],
        "x-tke-modes": [
          "live",
          "test"
        ],
        "x-tke-owner-types": [
          "organization"
        ]
      }
    },
    "/v1/orders/{id}": {
      "get": {
        "description": "Autorisation requise: `orders:read`.",
        "operationId": "get_orders__id_",
        "parameters": [
          {
            "description": "ID de la commande (UUID).",
            "in": "path",
            "name": "id",
            "required": true,
            "schema": {
              "type": "string"
            }
          },
          {
            "description": "Blocs supplémentaires séparés par des virgules : `lpa` retourne `install`, `qr` ajoute `install.qr_code_base64` (détail uniquement). **RETOURNER LE CODE LE DIVULGUE** et ferme définitivement l'annulation de chaque eSIM concernée (`cancellable.allowed = false`, `reason = code_disclosed`). Demandez-le à la livraison, omettez-le pour conserver l'annulation. Valeur inconnue : `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": "Succès"
          },
          "400": {
            "content": {
              "application/json": {
                "schema": {
                  "$ref": "#/components/schemas/ErrorEnvelope"
                }
              }
            },
            "description": "Erreur: `internal.validation_failed`, `internal.invalid_filter`"
          },
          "401": {
            "content": {
              "application/json": {
                "schema": {
                  "$ref": "#/components/schemas/ErrorEnvelope"
                }
              }
            },
            "description": "Erreur: `partner.invalid_key`, `partner.invalid_signature`, `partner.timestamp_expired`, `partner.nonce_reused`"
          },
          "403": {
            "content": {
              "application/json": {
                "schema": {
                  "$ref": "#/components/schemas/ErrorEnvelope"
                }
              }
            },
            "description": "Erreur: `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": "Erreur: `internal.not_found`"
          },
          "429": {
            "content": {
              "application/json": {
                "schema": {
                  "$ref": "#/components/schemas/ErrorEnvelope"
                }
              }
            },
            "description": "Erreur: `partner.rate_limited`"
          },
          "503": {
            "content": {
              "application/json": {
                "schema": {
                  "$ref": "#/components/schemas/ErrorEnvelope"
                }
              }
            },
            "description": "Erreur: `partner.endpoint_disabled`, `internal.service_unavailable`"
          }
        },
        "summary": "Détail de commande et eSIM",
        "tags": [
          "orders"
        ],
        "x-tke-modes": [
          "live",
          "test"
        ],
        "x-tke-owner-types": [
          "organization",
          "member"
        ]
      }
    },
    "/v1/orders/{id}/resend": {
      "post": {
        "description": "Autorisation requise: `orders:write`.",
        "operationId": "post_orders__id__resend",
        "parameters": [
          {
            "description": "ID de la commande (UUID).",
            "in": "path",
            "name": "id",
            "required": true,
            "schema": {
              "type": "string"
            }
          }
        ],
        "responses": {
          "200": {
            "content": {
              "application/json": {
                "schema": {
                  "$ref": "#/components/schemas/ResendResponse"
                }
              }
            },
            "description": "Succès"
          },
          "400": {
            "content": {
              "application/json": {
                "schema": {
                  "$ref": "#/components/schemas/ErrorEnvelope"
                }
              }
            },
            "description": "Erreur: `internal.validation_failed`"
          },
          "401": {
            "content": {
              "application/json": {
                "schema": {
                  "$ref": "#/components/schemas/ErrorEnvelope"
                }
              }
            },
            "description": "Erreur: `partner.invalid_key`, `partner.invalid_signature`, `partner.timestamp_expired`, `partner.nonce_reused`"
          },
          "403": {
            "content": {
              "application/json": {
                "schema": {
                  "$ref": "#/components/schemas/ErrorEnvelope"
                }
              }
            },
            "description": "Erreur: `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": "Erreur: `order.not_deliverable`"
          },
          "429": {
            "content": {
              "application/json": {
                "schema": {
                  "$ref": "#/components/schemas/ErrorEnvelope"
                }
              }
            },
            "description": "Erreur: `partner.rate_limited`"
          },
          "503": {
            "content": {
              "application/json": {
                "schema": {
                  "$ref": "#/components/schemas/ErrorEnvelope"
                }
              }
            },
            "description": "Erreur: `partner.endpoint_disabled`, `internal.service_unavailable`"
          }
        },
        "summary": "Renvoyer l’e-mail ou le QR de la commande",
        "tags": [
          "orders"
        ],
        "x-tke-modes": [
          "live",
          "test"
        ],
        "x-tke-owner-types": [
          "organization",
          "member"
        ]
      }
    },
    "/v1/testmode/esims/{id}/simulate": {
      "post": {
        "description": "Autorisation requise: `esims:write`.\n\nClés test tke_test_ ou tke_member_test_ uniquement. Appliquez un scénario à l’eSIM test du propriétaire : consommation, installation, expiration, débit limité ou rejet simulé de la prochaine opération. Le corps doit contenir au moins un champ ; sinon 422 testmode.invalid_scenario. Consultez le résultat par GET /v1/esims/{id} et /usage comme en mode réel. Les politiques de changement de forfait et d’annulation sont communes, sans appel au fournisseur réel.",
        "operationId": "post_testmode_esims__id__simulate",
        "parameters": [
          {
            "description": "Identifiant stable d'une eSIM vendue, différent de l'ICCID qui peut être réutilisé.",
            "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": "Succès"
          },
          "400": {
            "content": {
              "application/json": {
                "schema": {
                  "$ref": "#/components/schemas/ErrorEnvelope"
                }
              }
            },
            "description": "Erreur: `internal.validation_failed`"
          },
          "401": {
            "content": {
              "application/json": {
                "schema": {
                  "$ref": "#/components/schemas/ErrorEnvelope"
                }
              }
            },
            "description": "Erreur: `partner.invalid_key`, `partner.invalid_signature`, `partner.timestamp_expired`, `partner.nonce_reused`"
          },
          "403": {
            "content": {
              "application/json": {
                "schema": {
                  "$ref": "#/components/schemas/ErrorEnvelope"
                }
              }
            },
            "description": "Erreur: `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": "Erreur: `internal.not_found`"
          },
          "422": {
            "content": {
              "application/json": {
                "schema": {
                  "$ref": "#/components/schemas/ErrorEnvelope"
                }
              }
            },
            "description": "Erreur: `testmode.invalid_scenario`"
          },
          "429": {
            "content": {
              "application/json": {
                "schema": {
                  "$ref": "#/components/schemas/ErrorEnvelope"
                }
              }
            },
            "description": "Erreur: `partner.rate_limited`"
          },
          "503": {
            "content": {
              "application/json": {
                "schema": {
                  "$ref": "#/components/schemas/ErrorEnvelope"
                }
              }
            },
            "description": "Erreur: `partner.endpoint_disabled`, `internal.service_unavailable`"
          }
        },
        "summary": "Simuler le cycle de vie d’une eSIM de test (clés de test uniquement)",
        "tags": [
          "testmode"
        ],
        "x-tke-modes": [
          "test"
        ],
        "x-tke-owner-types": [
          "organization",
          "member"
        ]
      }
    },
    "/v1/testmode/reset": {
      "post": {
        "description": "Autorisation requise: `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": "Succès"
          },
          "400": {
            "content": {
              "application/json": {
                "schema": {
                  "$ref": "#/components/schemas/ErrorEnvelope"
                }
              }
            },
            "description": "Erreur: `internal.validation_failed`"
          },
          "401": {
            "content": {
              "application/json": {
                "schema": {
                  "$ref": "#/components/schemas/ErrorEnvelope"
                }
              }
            },
            "description": "Erreur: `partner.invalid_key`, `partner.invalid_signature`, `partner.timestamp_expired`, `partner.nonce_reused`"
          },
          "403": {
            "content": {
              "application/json": {
                "schema": {
                  "$ref": "#/components/schemas/ErrorEnvelope"
                }
              }
            },
            "description": "Erreur: `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": "Erreur: `partner.rate_limited`"
          },
          "503": {
            "content": {
              "application/json": {
                "schema": {
                  "$ref": "#/components/schemas/ErrorEnvelope"
                }
              }
            },
            "description": "Erreur: `partner.endpoint_disabled`, `internal.service_unavailable`"
          }
        },
        "summary": "Réinitialiser les données de test (clés de test uniquement)",
        "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": "Autorisation requise: `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": "Succès"
          },
          "400": {
            "content": {
              "application/json": {
                "schema": {
                  "$ref": "#/components/schemas/ErrorEnvelope"
                }
              }
            },
            "description": "Erreur: `internal.validation_failed`"
          },
          "401": {
            "content": {
              "application/json": {
                "schema": {
                  "$ref": "#/components/schemas/ErrorEnvelope"
                }
              }
            },
            "description": "Erreur: `partner.invalid_key`, `partner.invalid_signature`, `partner.timestamp_expired`, `partner.nonce_reused`"
          },
          "403": {
            "content": {
              "application/json": {
                "schema": {
                  "$ref": "#/components/schemas/ErrorEnvelope"
                }
              }
            },
            "description": "Erreur: `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": "Erreur: `testmode.no_sellable_package`"
          },
          "429": {
            "content": {
              "application/json": {
                "schema": {
                  "$ref": "#/components/schemas/ErrorEnvelope"
                }
              }
            },
            "description": "Erreur: `partner.rate_limited`"
          },
          "503": {
            "content": {
              "application/json": {
                "schema": {
                  "$ref": "#/components/schemas/ErrorEnvelope"
                }
              }
            },
            "description": "Erreur: `partner.endpoint_disabled`, `internal.service_unavailable`"
          }
        },
        "summary": "Créer les données d’exemple du mode test (clés de test uniquement)",
        "tags": [
          "testmode"
        ],
        "x-tke-idempotency-owner-types": [
          "member"
        ],
        "x-tke-modes": [
          "test"
        ],
        "x-tke-owner-types": [
          "organization",
          "member"
        ]
      }
    },
    "/v1/testmode/status": {
      "get": {
        "description": "Autorisation requise: `esims:read`.",
        "operationId": "get_testmode_status",
        "responses": {
          "200": {
            "content": {
              "application/json": {
                "schema": {
                  "$ref": "#/components/schemas/TestModeStatus"
                }
              }
            },
            "description": "Succès"
          },
          "400": {
            "content": {
              "application/json": {
                "schema": {
                  "$ref": "#/components/schemas/ErrorEnvelope"
                }
              }
            },
            "description": "Erreur: `internal.validation_failed`"
          },
          "401": {
            "content": {
              "application/json": {
                "schema": {
                  "$ref": "#/components/schemas/ErrorEnvelope"
                }
              }
            },
            "description": "Erreur: `partner.invalid_key`, `partner.invalid_signature`, `partner.timestamp_expired`, `partner.nonce_reused`"
          },
          "403": {
            "content": {
              "application/json": {
                "schema": {
                  "$ref": "#/components/schemas/ErrorEnvelope"
                }
              }
            },
            "description": "Erreur: `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": "Erreur: `partner.rate_limited`"
          },
          "503": {
            "content": {
              "application/json": {
                "schema": {
                  "$ref": "#/components/schemas/ErrorEnvelope"
                }
              }
            },
            "description": "Erreur: `partner.endpoint_disabled`, `internal.service_unavailable`"
          }
        },
        "summary": "Vue d’ensemble du mode test (clés de test uniquement)",
        "tags": [
          "testmode"
        ],
        "x-tke-modes": [
          "test"
        ],
        "x-tke-owner-types": [
          "organization",
          "member"
        ]
      }
    },
    "/v1/testmode/webhooks/fail-next": {
      "post": {
        "description": "Autorisation requise: `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": "Succès"
          },
          "400": {
            "content": {
              "application/json": {
                "schema": {
                  "$ref": "#/components/schemas/ErrorEnvelope"
                }
              }
            },
            "description": "Erreur: `internal.validation_failed`, `internal.validation_failed`"
          },
          "401": {
            "content": {
              "application/json": {
                "schema": {
                  "$ref": "#/components/schemas/ErrorEnvelope"
                }
              }
            },
            "description": "Erreur: `partner.invalid_key`, `partner.invalid_signature`, `partner.timestamp_expired`, `partner.nonce_reused`"
          },
          "403": {
            "content": {
              "application/json": {
                "schema": {
                  "$ref": "#/components/schemas/ErrorEnvelope"
                }
              }
            },
            "description": "Erreur: `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": "Erreur: `internal.not_found`"
          },
          "429": {
            "content": {
              "application/json": {
                "schema": {
                  "$ref": "#/components/schemas/ErrorEnvelope"
                }
              }
            },
            "description": "Erreur: `partner.rate_limited`"
          },
          "503": {
            "content": {
              "application/json": {
                "schema": {
                  "$ref": "#/components/schemas/ErrorEnvelope"
                }
              }
            },
            "description": "Erreur: `partner.endpoint_disabled`, `internal.service_unavailable`"
          }
        },
        "summary": "Simuler le prochain échec webhook (clés de test uniquement)",
        "tags": [
          "webhook"
        ],
        "x-tke-modes": [
          "test"
        ],
        "x-tke-owner-types": [
          "organization"
        ]
      }
    },
    "/v1/testmode/webhooks/fire": {
      "post": {
        "description": "Autorisation requise: `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": "Succès"
          },
          "400": {
            "content": {
              "application/json": {
                "schema": {
                  "$ref": "#/components/schemas/ErrorEnvelope"
                }
              }
            },
            "description": "Erreur: `internal.validation_failed`"
          },
          "401": {
            "content": {
              "application/json": {
                "schema": {
                  "$ref": "#/components/schemas/ErrorEnvelope"
                }
              }
            },
            "description": "Erreur: `partner.invalid_key`, `partner.invalid_signature`, `partner.timestamp_expired`, `partner.nonce_reused`"
          },
          "403": {
            "content": {
              "application/json": {
                "schema": {
                  "$ref": "#/components/schemas/ErrorEnvelope"
                }
              }
            },
            "description": "Erreur: `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": "Erreur: `webhook.not_configured`, `internal.not_found`"
          },
          "429": {
            "content": {
              "application/json": {
                "schema": {
                  "$ref": "#/components/schemas/ErrorEnvelope"
                }
              }
            },
            "description": "Erreur: `partner.rate_limited`"
          },
          "503": {
            "content": {
              "application/json": {
                "schema": {
                  "$ref": "#/components/schemas/ErrorEnvelope"
                }
              }
            },
            "description": "Erreur: `partner.endpoint_disabled`, `internal.service_unavailable`"
          }
        },
        "summary": "Envoyer un événement webhook de test (clés de test uniquement)",
        "tags": [
          "webhook"
        ],
        "x-tke-modes": [
          "test"
        ],
        "x-tke-owner-types": [
          "organization"
        ]
      }
    },
    "/v1/wallet/balance": {
      "get": {
        "description": "Autorisation requise: `wallet:read`.",
        "operationId": "get_wallet_balance",
        "responses": {
          "200": {
            "content": {
              "application/json": {
                "schema": {
                  "$ref": "#/components/schemas/WalletBalance"
                }
              }
            },
            "description": "Succès"
          },
          "400": {
            "content": {
              "application/json": {
                "schema": {
                  "$ref": "#/components/schemas/ErrorEnvelope"
                }
              }
            },
            "description": "Erreur: `internal.validation_failed`"
          },
          "401": {
            "content": {
              "application/json": {
                "schema": {
                  "$ref": "#/components/schemas/ErrorEnvelope"
                }
              }
            },
            "description": "Erreur: `partner.invalid_key`, `partner.invalid_signature`, `partner.timestamp_expired`, `partner.nonce_reused`"
          },
          "403": {
            "content": {
              "application/json": {
                "schema": {
                  "$ref": "#/components/schemas/ErrorEnvelope"
                }
              }
            },
            "description": "Erreur: `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": "Erreur: `partner.rate_limited`"
          },
          "503": {
            "content": {
              "application/json": {
                "schema": {
                  "$ref": "#/components/schemas/ErrorEnvelope"
                }
              }
            },
            "description": "Erreur: `partner.endpoint_disabled`, `internal.service_unavailable`"
          }
        },
        "summary": "Solde du portefeuille",
        "tags": [
          "wallet"
        ],
        "x-tke-modes": [
          "live",
          "test"
        ],
        "x-tke-owner-types": [
          "organization",
          "member"
        ]
      }
    },
    "/v1/webhooks": {
      "get": {
        "description": "Autorisation requise: `webhook:manage`.\n\nEndpoints de la clé appelante uniquement. Aucune clé ne voit l’endpoint d’une autre, même dans la même organisation (§5.11 : liaison à `api_key_id`, sans partage entre production et test).\n\nSans configuration : `200` avec un tableau vide, pas `404`. La collection existe toujours, même sans élément.",
        "operationId": "get_webhooks",
        "responses": {
          "200": {
            "content": {
              "application/json": {
                "schema": {
                  "$ref": "#/components/schemas/WebhookListResponse"
                }
              }
            },
            "description": "Succès"
          },
          "400": {
            "content": {
              "application/json": {
                "schema": {
                  "$ref": "#/components/schemas/ErrorEnvelope"
                }
              }
            },
            "description": "Erreur: `internal.validation_failed`"
          },
          "401": {
            "content": {
              "application/json": {
                "schema": {
                  "$ref": "#/components/schemas/ErrorEnvelope"
                }
              }
            },
            "description": "Erreur: `partner.invalid_key`, `partner.invalid_signature`, `partner.timestamp_expired`, `partner.nonce_reused`"
          },
          "403": {
            "content": {
              "application/json": {
                "schema": {
                  "$ref": "#/components/schemas/ErrorEnvelope"
                }
              }
            },
            "description": "Erreur: `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": "Erreur: `partner.rate_limited`"
          },
          "503": {
            "content": {
              "application/json": {
                "schema": {
                  "$ref": "#/components/schemas/ErrorEnvelope"
                }
              }
            },
            "description": "Erreur: `partner.endpoint_disabled`, `internal.service_unavailable`"
          }
        },
        "summary": "Lister les points de terminaison webhook",
        "tags": [
          "webhook"
        ],
        "x-tke-modes": [
          "live",
          "test"
        ],
        "x-tke-owner-types": [
          "organization"
        ]
      },
      "post": {
        "description": "Autorisation requise: `webhook:manage`.\n\nCréer un endpoint pour la clé appelante. Avant sauvegarde, le serveur envoie un POST signé `ping` à l’URL (CHECK_HEALTH). Sans 2xx, rien n’est sauvegardé : `422 webhook.health_check_failed`. URL publique `https` obligatoire ; adresse interne ou de métadonnées : `422 webhook.url_not_allowed`.\n\n`webhook_secret` n’est affiché qu’UNE FOIS et ne peut être relu. En cas de perte, effectuer une rotation dans le tableau de bord ; l’ancien secret reste valide pendant 24 h (§8.4).\n\nUn endpoint existe déjà : `409 webhook.already_configured`, même en pause (`is_active: false`). La pause conserve URL, secret et journal. Pour recommencer, utiliser `DELETE /v1/webhooks/{id}`, puis créer un nouvel endpoint.",
        "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": "Succès"
          },
          "400": {
            "content": {
              "application/json": {
                "schema": {
                  "$ref": "#/components/schemas/ErrorEnvelope"
                }
              }
            },
            "description": "Erreur: `internal.validation_failed`"
          },
          "401": {
            "content": {
              "application/json": {
                "schema": {
                  "$ref": "#/components/schemas/ErrorEnvelope"
                }
              }
            },
            "description": "Erreur: `partner.invalid_key`, `partner.invalid_signature`, `partner.timestamp_expired`, `partner.nonce_reused`"
          },
          "403": {
            "content": {
              "application/json": {
                "schema": {
                  "$ref": "#/components/schemas/ErrorEnvelope"
                }
              }
            },
            "description": "Erreur: `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": "Erreur: `webhook.already_configured`"
          },
          "422": {
            "content": {
              "application/json": {
                "schema": {
                  "$ref": "#/components/schemas/ErrorEnvelope"
                }
              }
            },
            "description": "Erreur: `webhook.health_check_failed`, `webhook.url_not_allowed`"
          },
          "429": {
            "content": {
              "application/json": {
                "schema": {
                  "$ref": "#/components/schemas/ErrorEnvelope"
                }
              }
            },
            "description": "Erreur: `partner.rate_limited`"
          },
          "503": {
            "content": {
              "application/json": {
                "schema": {
                  "$ref": "#/components/schemas/ErrorEnvelope"
                }
              }
            },
            "description": "Erreur: `partner.endpoint_disabled`, `internal.service_unavailable`"
          }
        },
        "summary": "Créer un endpoint webhook",
        "tags": [
          "webhook"
        ],
        "x-tke-modes": [
          "live",
          "test"
        ],
        "x-tke-owner-types": [
          "organization"
        ]
      }
    },
    "/v1/webhooks/deliveries": {
      "get": {
        "description": "Autorisation requise: `webhook:manage`.",
        "operationId": "get_webhooks_deliveries",
        "parameters": [
          {
            "description": "Numéro de page à partir de 1. Au-delà des données : `items: []` et le vrai `total` (200, pas 404).",
            "in": "query",
            "name": "page",
            "required": false,
            "schema": {
              "default": 1,
              "minimum": 1,
              "type": "integer"
            }
          },
          {
            "description": "Lignes par page. Valeur par défaut 50, maximum 200. Une valeur supérieure est ramenée à 200 sans erreur.",
            "in": "query",
            "name": "page_size",
            "required": false,
            "schema": {
              "default": 50,
              "maximum": 200,
              "minimum": 1,
              "type": "integer"
            }
          },
          {
            "description": "Filtrer par type d’événement (voir le catalogue des événements webhook).",
            "in": "query",
            "name": "event_type",
            "required": false,
            "schema": {
              "type": "string"
            }
          },
          {
            "description": "Filtrer par état d’envoi. `?status=failed` sert au rapprochement des webhooks par interrogation (§13). Une valeur inconnue est IGNORÉE sans erreur : le journal complet est renvoyé.",
            "in": "query",
            "name": "status",
            "required": false,
            "schema": {
              "enum": [
                "pending",
                "delivered",
                "failed"
              ],
              "type": "string"
            }
          },
          {
            "description": "Toutes les tentatives d'envoi d'un événement, reprises incluses.",
            "in": "query",
            "name": "event_id",
            "required": false,
            "schema": {
              "format": "uuid",
              "type": "string"
            }
          }
        ],
        "responses": {
          "200": {
            "content": {
              "application/json": {
                "schema": {
                  "$ref": "#/components/schemas/DeliveryListResponse"
                }
              }
            },
            "description": "Succès"
          },
          "400": {
            "content": {
              "application/json": {
                "schema": {
                  "$ref": "#/components/schemas/ErrorEnvelope"
                }
              }
            },
            "description": "Erreur: `internal.validation_failed`"
          },
          "401": {
            "content": {
              "application/json": {
                "schema": {
                  "$ref": "#/components/schemas/ErrorEnvelope"
                }
              }
            },
            "description": "Erreur: `partner.invalid_key`, `partner.invalid_signature`, `partner.timestamp_expired`, `partner.nonce_reused`"
          },
          "403": {
            "content": {
              "application/json": {
                "schema": {
                  "$ref": "#/components/schemas/ErrorEnvelope"
                }
              }
            },
            "description": "Erreur: `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": "Erreur: `webhook.not_configured`"
          },
          "429": {
            "content": {
              "application/json": {
                "schema": {
                  "$ref": "#/components/schemas/ErrorEnvelope"
                }
              }
            },
            "description": "Erreur: `partner.rate_limited`"
          },
          "503": {
            "content": {
              "application/json": {
                "schema": {
                  "$ref": "#/components/schemas/ErrorEnvelope"
                }
              }
            },
            "description": "Erreur: `partner.endpoint_disabled`, `internal.service_unavailable`"
          }
        },
        "summary": "Journal des envois webhook",
        "tags": [
          "webhook"
        ],
        "x-tke-modes": [
          "live",
          "test"
        ],
        "x-tke-owner-types": [
          "organization"
        ]
      }
    },
    "/v1/webhooks/deliveries/{id}/replay": {
      "post": {
        "description": "Autorisation requise: `webhook:manage`.",
        "operationId": "post_webhooks_deliveries__id__replay",
        "parameters": [
          {
            "description": "ID d’un envoi webhook (`items[].id` de `GET /v1/webhooks/deliveries`).",
            "in": "path",
            "name": "id",
            "required": true,
            "schema": {
              "type": "string"
            }
          }
        ],
        "responses": {
          "200": {
            "content": {
              "application/json": {
                "schema": {
                  "$ref": "#/components/schemas/DeliveryReplayResponse"
                }
              }
            },
            "description": "Succès"
          },
          "400": {
            "content": {
              "application/json": {
                "schema": {
                  "$ref": "#/components/schemas/ErrorEnvelope"
                }
              }
            },
            "description": "Erreur: `internal.validation_failed`"
          },
          "401": {
            "content": {
              "application/json": {
                "schema": {
                  "$ref": "#/components/schemas/ErrorEnvelope"
                }
              }
            },
            "description": "Erreur: `partner.invalid_key`, `partner.invalid_signature`, `partner.timestamp_expired`, `partner.nonce_reused`"
          },
          "403": {
            "content": {
              "application/json": {
                "schema": {
                  "$ref": "#/components/schemas/ErrorEnvelope"
                }
              }
            },
            "description": "Erreur: `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": "Erreur: `webhook.delivery_not_found`, `webhook.not_configured`"
          },
          "429": {
            "content": {
              "application/json": {
                "schema": {
                  "$ref": "#/components/schemas/ErrorEnvelope"
                }
              }
            },
            "description": "Erreur: `partner.rate_limited`"
          },
          "503": {
            "content": {
              "application/json": {
                "schema": {
                  "$ref": "#/components/schemas/ErrorEnvelope"
                }
              }
            },
            "description": "Erreur: `partner.endpoint_disabled`, `internal.service_unavailable`"
          }
        },
        "summary": "Renvoyer un webhook",
        "tags": [
          "webhook"
        ],
        "x-tke-modes": [
          "live",
          "test"
        ],
        "x-tke-owner-types": [
          "organization"
        ]
      }
    },
    "/v1/webhooks/{id}": {
      "delete": {
        "description": "Autorisation requise: `webhook:manage`.\n\nSupprimer l’endpoint arrête les nouveaux événements de la clé appelante.\n\nLe `webhook_secret` est perdu avec l’endpoint. Une recréation par `POST /v1/webhooks` génère un NOUVEL `id` et un NOUVEAU secret : adaptez votre vérification. Pour suspendre temporairement en conservant le secret, utilisez Pause dans le tableau de bord.\n\nLe journal complet reste disponible pour le rapprochement, mais les envois de cet endpoint supprimé ne peuvent plus être rejoués.\n\nLes envois `pending` en attente de réessai s’arrêtent avec `endpoint_disabled` et restent journalisés (§8.5).\n\nUn `id` d’une autre clé ou déjà supprimé renvoie `404 webhook.not_found`, jamais un 200 silencieux.",
        "operationId": "delete_webhooks__id_",
        "parameters": [
          {
            "description": "Identifiant de l'endpoint webhook (`id` de `POST /v1/webhooks` ou `GET /v1/webhooks`).",
            "in": "path",
            "name": "id",
            "required": true,
            "schema": {
              "type": "string"
            }
          }
        ],
        "responses": {
          "200": {
            "content": {
              "application/json": {
                "schema": {
                  "$ref": "#/components/schemas/DeleteWebhookResponse"
                }
              }
            },
            "description": "Succès"
          },
          "400": {
            "content": {
              "application/json": {
                "schema": {
                  "$ref": "#/components/schemas/ErrorEnvelope"
                }
              }
            },
            "description": "Erreur: `internal.validation_failed`"
          },
          "401": {
            "content": {
              "application/json": {
                "schema": {
                  "$ref": "#/components/schemas/ErrorEnvelope"
                }
              }
            },
            "description": "Erreur: `partner.invalid_key`, `partner.invalid_signature`, `partner.timestamp_expired`, `partner.nonce_reused`"
          },
          "403": {
            "content": {
              "application/json": {
                "schema": {
                  "$ref": "#/components/schemas/ErrorEnvelope"
                }
              }
            },
            "description": "Erreur: `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": "Erreur: `webhook.not_found`"
          },
          "429": {
            "content": {
              "application/json": {
                "schema": {
                  "$ref": "#/components/schemas/ErrorEnvelope"
                }
              }
            },
            "description": "Erreur: `partner.rate_limited`"
          },
          "503": {
            "content": {
              "application/json": {
                "schema": {
                  "$ref": "#/components/schemas/ErrorEnvelope"
                }
              }
            },
            "description": "Erreur: `partner.endpoint_disabled`, `internal.service_unavailable`"
          }
        },
        "summary": "Supprimer un endpoint webhook (arrêter les nouveaux événements)",
        "tags": [
          "webhook"
        ],
        "x-tke-modes": [
          "live",
          "test"
        ],
        "x-tke-owner-types": [
          "organization"
        ]
      },
      "get": {
        "description": "Autorisation requise: `webhook:manage`.",
        "operationId": "get_webhooks__id_",
        "parameters": [
          {
            "description": "Identifiant de l'endpoint webhook (`id` de `POST /v1/webhooks` ou `GET /v1/webhooks`).",
            "in": "path",
            "name": "id",
            "required": true,
            "schema": {
              "type": "string"
            }
          }
        ],
        "responses": {
          "200": {
            "content": {
              "application/json": {
                "schema": {
                  "$ref": "#/components/schemas/WebhookConfig"
                }
              }
            },
            "description": "Succès"
          },
          "400": {
            "content": {
              "application/json": {
                "schema": {
                  "$ref": "#/components/schemas/ErrorEnvelope"
                }
              }
            },
            "description": "Erreur: `internal.validation_failed`"
          },
          "401": {
            "content": {
              "application/json": {
                "schema": {
                  "$ref": "#/components/schemas/ErrorEnvelope"
                }
              }
            },
            "description": "Erreur: `partner.invalid_key`, `partner.invalid_signature`, `partner.timestamp_expired`, `partner.nonce_reused`"
          },
          "403": {
            "content": {
              "application/json": {
                "schema": {
                  "$ref": "#/components/schemas/ErrorEnvelope"
                }
              }
            },
            "description": "Erreur: `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": "Erreur: `webhook.not_found`"
          },
          "429": {
            "content": {
              "application/json": {
                "schema": {
                  "$ref": "#/components/schemas/ErrorEnvelope"
                }
              }
            },
            "description": "Erreur: `partner.rate_limited`"
          },
          "503": {
            "content": {
              "application/json": {
                "schema": {
                  "$ref": "#/components/schemas/ErrorEnvelope"
                }
              }
            },
            "description": "Erreur: `partner.endpoint_disabled`, `internal.service_unavailable`"
          }
        },
        "summary": "Lire un point de terminaison webhook",
        "tags": [
          "webhook"
        ],
        "x-tke-modes": [
          "live",
          "test"
        ],
        "x-tke-owner-types": [
          "organization"
        ]
      },
      "put": {
        "description": "Autorisation requise: `webhook:manage`.",
        "operationId": "put_webhooks__id_",
        "parameters": [
          {
            "description": "Identifiant de l'endpoint webhook (`id` de `POST /v1/webhooks` ou `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": "Succès"
          },
          "400": {
            "content": {
              "application/json": {
                "schema": {
                  "$ref": "#/components/schemas/ErrorEnvelope"
                }
              }
            },
            "description": "Erreur: `internal.validation_failed`"
          },
          "401": {
            "content": {
              "application/json": {
                "schema": {
                  "$ref": "#/components/schemas/ErrorEnvelope"
                }
              }
            },
            "description": "Erreur: `partner.invalid_key`, `partner.invalid_signature`, `partner.timestamp_expired`, `partner.nonce_reused`"
          },
          "403": {
            "content": {
              "application/json": {
                "schema": {
                  "$ref": "#/components/schemas/ErrorEnvelope"
                }
              }
            },
            "description": "Erreur: `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": "Erreur: `webhook.not_found`"
          },
          "422": {
            "content": {
              "application/json": {
                "schema": {
                  "$ref": "#/components/schemas/ErrorEnvelope"
                }
              }
            },
            "description": "Erreur: `webhook.health_check_failed`, `webhook.url_not_allowed`"
          },
          "429": {
            "content": {
              "application/json": {
                "schema": {
                  "$ref": "#/components/schemas/ErrorEnvelope"
                }
              }
            },
            "description": "Erreur: `partner.rate_limited`"
          },
          "503": {
            "content": {
              "application/json": {
                "schema": {
                  "$ref": "#/components/schemas/ErrorEnvelope"
                }
              }
            },
            "description": "Erreur: `partner.endpoint_disabled`, `internal.service_unavailable`"
          }
        },
        "summary": "Modifier un endpoint webhook",
        "tags": [
          "webhook"
        ],
        "x-tke-modes": [
          "live",
          "test"
        ],
        "x-tke-owner-types": [
          "organization"
        ]
      }
    }
  },
  "security": [
    {
      "TkeHmac": []
    }
  ],
  "servers": [
    {
      "description": "Live",
      "url": "https://api.tongkhoesim.com"
    },
    {
      "description": "Mode test",
      "url": "https://api-testing.tongkhoesim.com"
    }
  ],
  "tags": [
    {
      "description": "Catalogue des forfaits, surface commune aux deux systèmes de fourniture. Le système est un CHAMP (`market`: `domestic` | `international`) et un filtre (`?market=`), pas deux chemins distincts.\n\nAUCUNE notion de stock : ni quantité ni niveau `available`/`low`/`out_of_stock`. Le forfait apparaît simplement ; si le fournisseur ne peut livrer, `POST /v1/orders` renvoie directement une erreur (`esim.out_of_stock`…), sans annonce préalable du catalogue.\n\nPagination unique par curseur. `total` n’est calculé que si peu coûteux (filtre sur le système national), sinon `null`.",
      "name": "catalog"
    },
    {
      "description": "Passer des commandes et rapprocher",
      "name": "orders"
    },
    {
      "description": "Enregistrements eSIM, consommation, recharge et annulation",
      "name": "esims"
    },
    {
      "description": "Solde du portefeuille",
      "name": "wallet"
    },
    {
      "description": "Configuration webhook, journal d'envoi et simulateur",
      "name": "webhook"
    },
    {
      "description": "Utilisez uniquement les clés test tke_test_ ou tke_member_test_ sur api-testing. Simulez les données et eSIM du propriétaire sans fonds ni fournisseurs réels. Le mode test membre ne prend pas en charge webhooks, commandes groupées ou demandes de remboursement à valider.",
      "name": "testmode"
    }
  ]
}
