{
    "openapi": "3.0.3",
    "info": {
        "title": "eFacture Connect API",
        "version": "1.0.0",
        "description": "## Vue d'ensemble\n\nAPI REST pour intégrer la facture électronique tunisienne dans votre ERP. Envoyez du JSON, recevez un XML signé (TEIF v1.8.8 + XAdES) et un PDF avec QR code.\n\n## Quatre modes de signature\n\n| Mode | `method` | Endpoint | Synchrone ? | Interaction utilisateur |\n|---|---|---|---|---|\n| **Entreprise ID** (cachet serveur) | `seal` | `POST /einvoices/process` | Oui (~5 s) | Aucune |\n| **DigiGo** (PIN + OTP) | `digigo` | `POST /einvoices/prepare` | Non | Page de signature |\n| **Clé USB** (signature locale) | `usb` | `POST /einvoices/prepare` | Non | Token sur le poste du signataire |\n| **E-Houwiya** (Mobile ID) | `mobileid` | `POST /einvoices/prepare` | Non | Validation sur smartphone (option à activer par compte) |\n\nVoir le [guide d'intégration](/documentation-api/guide) pour les schémas de séquence, et la [documentation complète](/documentation-api) pour tous les champs et codes d'erreur.\n\n## Authentification\n\nToutes les requêtes portent l'en-tête `X-Api-Key`. Chaque facture s'émet avec la clé du **client** concerné : `ck_test_xxx` (signature et dépôt réels sur les environnements de TEST, aucun crédit consommé) ou `ck_live_xxx` (production, 1 crédit par signature). Votre clé développeur `sk_dev_xxx` sert à gérer vos clients (section **Clients**) et n'émet aucune facture (`403 DEVELOPER_KEY_NOT_ALLOWED`).\n\n## Banc d'essai (simulation) : ce n'est pas le mode test\n\nLe **mode test** (`ck_test_xxx`) signe et dépose **réellement**, sur les environnements de test : il demande un compte El Fatoora et un certificat. Le **banc d'essai** **simule** la signature et TTN, pour une seule raison : vous permettre d'intégrer l'API **pendant que votre client attend ses accès TTN et son certificat**. Rien n'est signé ni déposé, et aucun document n'a de valeur légale.\n\n- Il s'ouvre **sur demande au support, client par client**, et ne concerne que la clé `ck_test_` de ce client. Une clé `ck_live_` n'est **jamais** simulée.\n- **Mêmes routes, mêmes corps, mêmes réponses** que le mode test et la production : aucune ligne de votre code ne change quand le banc d'essai se ferme.\n- Une réponse du banc d'essai porte `\"simulation\": true` et l'en-tête `X-eFactureTN-Simulation: 1`. **Informatif seulement** : n'en faites dépendre aucune logique.\n- L'en-tête `X-Simulation-Scenario` provoque une panne simulée pour éprouver votre gestion des erreurs. **Réservé à vos tests** : ne l'ajoutez pas aux appels de votre intégration.\n\nDétails : [documentation, section Banc d'essai](/documentation-api#banc-essai).\n\n## Principaux codes d'erreur\n\n| Code | HTTP | Description |\n|---|---|---|\n| `AUTH_INVALID_KEY` | 401 | Clé API invalide ou inactive |\n| `DEVELOPER_KEY_NOT_ALLOWED` | 403 | Clé développeur utilisée pour émettre : utilisez la clé du client |\n| `INVALID_JSON` | 400 | Corps de requête non JSON |\n| `VALIDATION_FAILED` | 422 | Champs requis manquants ou invalides (voir `error.details`) |\n| `SELLER_MISMATCH` | 422 | Le vendeur envoyé n'est pas le titulaire de la clé |\n| `METHOD_REQUIRES_PREPARE` | 422 | DigiGo, clé USB ou E-Houwiya demandés sur `/einvoices/process` |\n| `INSUFFICIENT_CREDITS` | 402 | Portefeuille épuisé ou plafond du client atteint |\n| `SEAL_NOT_CONFIGURED` | 412 | Cachet non provisionné, ou code PIN du cachet manquant |\n| `SIGNER_NOT_CONFIGURED` | 412 | Compte de signature du client pas encore créé |\n| `SIGNER_EMAIL_MISSING` | 412 | DigiGo ou E-Houwiya sans adresse de signataire (`signer_email`) |\n| `INVOICE_IN_PROGRESS` | 409 | Une signature attend déjà sur ce numéro, avec un autre contenu |\n| `INVOICE_NUMBER_ALREADY_ISSUED` | 409 | Numéro déjà accepté par TTN pour un autre contenu |\n| `RATE_LIMIT` | 429 | Trop de requêtes (en-têtes `X-RateLimit-*`) |\n| `INVALID_SIMULATION_SCENARIO` | 422 | Banc d'essai uniquement : valeur inconnue de `X-Simulation-Scenario` |\n| `SANDBOX_DAILY_LIMIT` | 429 | Banc d'essai uniquement : plafond de factures simulées par 24 heures atteint |\n| `SIGNING_FAILED` | 503 | Service de signature indisponible |\n\n## Erreurs\n\nToute erreur rend du JSON : `{\"success\": false, \"error\": {\"code\", \"message\"}}`, y compris une erreur interne (`500 INTERNAL_ERROR`). Un service momentanément indisponible répond `503 SERVICE_UNAVAILABLE` avec l'en-tête `Retry-After` : réessayez après ce délai. Chaque réponse porte l'en-tête `X-Request-Id`, à nous communiquer pour retrouver un appel."
    },
    "servers": [
        {
            "url": "https://efacturetn.com",
            "description": "Production"
        }
    ],
    "security": [
        {
            "ApiKeyAuth": []
        }
    ],
    "components": {
        "securitySchemes": {
            "ApiKeyAuth": {
                "type": "apiKey",
                "in": "header",
                "name": "X-Api-Key",
                "description": "Clé du client (ck_test_xxx en test, ck_live_xxx en production) pour émettre ; clé développeur (sk_dev_xxx) pour la section Clients."
            }
        },
        "parameters": {
            "SimulationScenario": {
                "name": "X-Simulation-Scenario",
                "in": "header",
                "required": false,
                "description": "BANC D'ESSAI UNIQUEMENT (simulation, distinct du mode test). Provoque une panne simulée pour éprouver votre gestion des erreurs. Sans effet sur une clé qui n'est pas au banc d'essai, et jamais sur une clé ck_live_. Réservé à vos tests : ne l'ajoutez pas aux appels de votre intégration. ttn_indisponible et ttn_compte_desactive rendent next_action resubmit ; ttn_rejet_contrl05 et ttn_rejet_contrl02 rendent next_action resign ; signature_echouee rend 503 SIGNING_FAILED. Une valeur inconnue rend 422 INVALID_SIMULATION_SCENARIO.",
                "schema": {
                    "type": "string",
                    "enum": [
                        "ttn_indisponible",
                        "ttn_compte_desactive",
                        "ttn_rejet_contrl05",
                        "ttn_rejet_contrl02",
                        "signature_echouee"
                    ]
                }
            }
        },
        "schemas": {
            "InvoiceItem": {
                "type": "object",
                "required": [
                    "description",
                    "quantity",
                    "unit_price"
                ],
                "properties": {
                    "description": {
                        "type": "string",
                        "example": "Service de conseil"
                    },
                    "quantity": {
                        "type": "number",
                        "example": 1.0
                    },
                    "unit_price": {
                        "type": "number",
                        "example": 500.0,
                        "description": "Prix unitaire HT."
                    },
                    "vat_rate": {
                        "type": "integer",
                        "enum": [
                            0,
                            7,
                            13,
                            19
                        ],
                        "example": 19,
                        "description": "Taux de TVA. Facultatif si default_vat_rate est donné sur la facture, ou si la ligne porte exonerated: true."
                    }
                }
            },
            "SignRequest": {
                "type": "object",
                "required": [
                    "invoice_number",
                    "date",
                    "seller",
                    "buyer",
                    "items"
                ],
                "properties": {
                    "invoice_number": {
                        "type": "string",
                        "example": "F-2026-001",
                        "description": "Numéro de la facture côté ERP, 30 caractères au plus (lettres, chiffres, point, tiret, soulignement). Repris dans la réponse."
                    },
                    "date": {
                        "type": "string",
                        "format": "date",
                        "example": "2026-06-13",
                        "description": "Date d'émission, jamais dans le futur."
                    },
                    "due_date": {
                        "type": "string",
                        "format": "date",
                        "example": "2026-07-13",
                        "description": "Facultatif : date d'échéance."
                    },
                    "seller": {
                        "type": "object",
                        "required": [
                            "name",
                            "address"
                        ],
                        "properties": {
                            "name": {
                                "type": "string",
                                "example": "Mon Entreprise SARL"
                            },
                            "tax_id": {
                                "type": "string",
                                "example": "1234567MAM000",
                                "description": "Facultatif : la clé porte déjà le vendeur. Si vous l'envoyez, seuls les 8 premiers caractères sont comparés (1234567M et 1234567MAM000 sont équivalents) ; un autre contribuable est refusé en SELLER_MISMATCH."
                            },
                            "address": {
                                "type": "string",
                                "example": "Tunis"
                            }
                        }
                    },
                    "buyer": {
                        "type": "object",
                        "required": [
                            "name"
                        ],
                        "properties": {
                            "name": {
                                "type": "string",
                                "example": "Client SARL"
                            },
                            "tax_id": {
                                "type": "string",
                                "example": "7654321XAM000",
                                "description": "Matricule fiscal tunisien, forme longue (13 caractères) ou courte (8)."
                            },
                            "id_number": {
                                "type": "string",
                                "example": "552100554",
                                "description": "À la place de tax_id : CIN d'un particulier, ou numéro d'entreprise d'un client étranger."
                            },
                            "country": {
                                "type": "string",
                                "example": "TN",
                                "description": "Code pays ISO. Obligatoire en pratique pour un client étranger : il décide du type d'identifiant envoyé à TTN."
                            }
                        },
                        "description": "tax_id (matricule tunisien) OU id_number (CIN, numéro d'entreprise étranger, avec country)."
                    },
                    "items": {
                        "type": "array",
                        "items": {
                            "$ref": "#/components/schemas/InvoiceItem"
                        }
                    },
                    "stamp_duty": {
                        "oneOf": [
                            {
                                "type": "boolean"
                            },
                            {
                                "type": "number"
                            }
                        ],
                        "default": true,
                        "description": "Timbre fiscal : présent par défaut (1,000 TND). false pour le retirer, ou un montant."
                    },
                    "withholding_tax_rate": {
                        "type": "number",
                        "nullable": true,
                        "description": "Taux de retenue à la source (1.5, 3, 10, 15, 20). Base : HT + TVA."
                    },
                    "method": {
                        "type": "string",
                        "enum": [
                            "seal",
                            "digigo",
                            "usb",
                            "mobileid"
                        ],
                        "description": "Facultatif : le mode de signature de cette requête. Par défaut, celui configuré sur le client. seal sur /process ; digigo, usb ou mobileid sur /prepare."
                    },
                    "return_url": {
                        "type": "string",
                        "format": "uri",
                        "example": "https://votre-erp.tn/factures/F-2026-001",
                        "description": "Requis pour digigo, usb et mobileid : URL HTTPS de votre ERP, où l'utilisateur revient après la signature."
                    },
                    "type": {
                        "type": "string",
                        "enum": [
                            "facture",
                            "credit_note",
                            "debit_note"
                        ],
                        "default": "facture",
                        "description": "Nature du document. Un avoir (credit_note) exige original_invoice_number."
                    },
                    "original_invoice_number": {
                        "type": "string",
                        "description": "Numéro de la facture d'origine, requis pour un avoir ou une note de débit."
                    },
                    "pdf_base64": {
                        "type": "string",
                        "description": "Facultatif : votre PDF encodé en base64. Sans lui, nous le générons."
                    }
                }
            },
            "SignResponse": {
                "type": "object",
                "properties": {
                    "success": {
                        "type": "boolean",
                        "example": true
                    },
                    "data": {
                        "type": "object",
                        "properties": {
                            "status": {
                                "type": "string",
                                "example": "signed"
                            },
                            "invoice_number": {
                                "type": "string",
                                "example": "F-2026-001"
                            },
                            "ttn_reference": {
                                "type": "string",
                                "example": "TTN-2026-XXXXX"
                            },
                            "xml_base64": {
                                "type": "string",
                                "description": "XML TEIF signé, encodé en base64."
                            },
                            "pdf_base64": {
                                "type": "string",
                                "description": "PDF avec QR code, encodé en base64."
                            },
                            "test_mode": {
                                "type": "boolean"
                            },
                            "signing_id": {
                                "type": "string",
                                "example": "sg_3c9e1f7a2b4d6e8f0a1b2c3d",
                                "description": "Identifiant de la facture, pour GET /einvoices/{id}, /resubmit-ttn et /cancel."
                            }
                        }
                    },
                    "error": {
                        "type": "object",
                        "nullable": true
                    }
                }
            },
            "ErrorResponse": {
                "type": "object",
                "properties": {
                    "success": {
                        "type": "boolean",
                        "example": false
                    },
                    "data": {
                        "type": "object",
                        "nullable": true
                    },
                    "error": {
                        "type": "object",
                        "properties": {
                            "code": {
                                "type": "string",
                                "example": "VALIDATION_FAILED"
                            },
                            "message": {
                                "type": "string",
                                "example": "Invoice validation failed."
                            },
                            "details": {
                                "description": "Détail : liste des erreurs de validation, ou informations utiles (signing_id, signing_url…).",
                                "oneOf": [
                                    {
                                        "type": "array",
                                        "items": {
                                            "type": "string"
                                        }
                                    },
                                    {
                                        "type": "object"
                                    }
                                ]
                            }
                        }
                    }
                }
            }
        }
    },
    "paths": {
        "/api/v1/status": {
            "get": {
                "tags": [
                    "Général"
                ],
                "summary": "État global de la plateforme",
                "description": "Disponibilité globale des services. Aucune authentification requise. Le statut est calculé à partir des vérifications réelles effectuées chaque heure : il vaut celui du service le plus dégradé.",
                "security": [],
                "responses": {
                    "200": {
                        "description": "État global",
                        "content": {
                            "application/json": {
                                "schema": {
                                    "type": "object",
                                    "properties": {
                                        "success": {
                                            "type": "boolean",
                                            "example": true
                                        },
                                        "data": {
                                            "type": "object",
                                            "properties": {
                                                "status": {
                                                    "type": "string",
                                                    "enum": [
                                                        "operational",
                                                        "degraded",
                                                        "partial_outage",
                                                        "major_outage",
                                                        "maintenance"
                                                    ],
                                                    "example": "operational"
                                                },
                                                "version": {
                                                    "type": "string",
                                                    "example": "1.0.0"
                                                },
                                                "teif_version": {
                                                    "type": "string",
                                                    "example": "1.8.8"
                                                },
                                                "all_operational": {
                                                    "type": "boolean",
                                                    "example": true
                                                },
                                                "last_check": {
                                                    "type": "string",
                                                    "example": "17/07/2026 09:00"
                                                }
                                            }
                                        }
                                    }
                                }
                            }
                        }
                    },
                    "500": {
                        "description": "Erreur interne (INTERNAL_ERROR), toujours en JSON.",
                        "content": {
                            "application/json": {
                                "schema": {
                                    "$ref": "#/components/schemas/ErrorResponse"
                                }
                            }
                        }
                    },
                    "503": {
                        "description": "Service momentanément indisponible (SERVICE_UNAVAILABLE) : réessayer après Retry-After.",
                        "content": {
                            "application/json": {
                                "schema": {
                                    "$ref": "#/components/schemas/ErrorResponse"
                                }
                            }
                        },
                        "headers": {
                            "Retry-After": {
                                "description": "Secondes à attendre avant de réessayer.",
                                "schema": {
                                    "type": "integer",
                                    "example": 30
                                }
                            }
                        }
                    }
                }
            }
        },
        "/api/v1/status/services": {
            "get": {
                "tags": [
                    "Général"
                ],
                "summary": "Disponibilité détaillée par service",
                "description": "Détail par service : statut, latence et taux de disponibilité. Nécessite une clé API. Le slug de chaque service est stable et peut servir d'identifiant.",
                "parameters": [
                    {
                        "name": "days",
                        "in": "query",
                        "required": false,
                        "description": "Fenêtre du taux de disponibilité, en jours (1 à 180). Défaut : 90.",
                        "schema": {
                            "type": "integer",
                            "default": 90,
                            "minimum": 1,
                            "maximum": 180
                        }
                    }
                ],
                "responses": {
                    "200": {
                        "description": "Détail par service",
                        "content": {
                            "application/json": {
                                "schema": {
                                    "type": "object",
                                    "properties": {
                                        "success": {
                                            "type": "boolean",
                                            "example": true
                                        },
                                        "data": {
                                            "type": "object",
                                            "properties": {
                                                "status": {
                                                    "type": "string",
                                                    "example": "operational"
                                                },
                                                "all_operational": {
                                                    "type": "boolean",
                                                    "example": true
                                                },
                                                "last_check": {
                                                    "type": "string",
                                                    "example": "17/07/2026 09:00"
                                                },
                                                "uptime_window_days": {
                                                    "type": "integer",
                                                    "example": 90
                                                },
                                                "services": {
                                                    "type": "array",
                                                    "items": {
                                                        "type": "object",
                                                        "properties": {
                                                            "slug": {
                                                                "type": "string",
                                                                "enum": [
                                                                    "platform",
                                                                    "signature",
                                                                    "ttn_prod",
                                                                    "ttn_sandbox",
                                                                    "api",
                                                                    "tej",
                                                                    "email"
                                                                ],
                                                                "example": "signature"
                                                            },
                                                            "name": {
                                                                "type": "string",
                                                                "example": "Signature electronique"
                                                            },
                                                            "description": {
                                                                "type": "string",
                                                                "example": "Service de signature"
                                                            },
                                                            "status": {
                                                                "type": "string",
                                                                "enum": [
                                                                    "operational",
                                                                    "degraded",
                                                                    "partial_outage",
                                                                    "major_outage",
                                                                    "maintenance"
                                                                ],
                                                                "example": "operational"
                                                            },
                                                            "status_label": {
                                                                "type": "string",
                                                                "example": "Operationnel"
                                                            },
                                                            "operational": {
                                                                "type": "boolean",
                                                                "example": true
                                                            },
                                                            "latency_ms": {
                                                                "type": "integer",
                                                                "nullable": true,
                                                                "description": "Latence mesurée, uniquement pour les services sondés par le réseau.",
                                                                "example": 214
                                                            },
                                                            "detail": {
                                                                "type": "string",
                                                                "nullable": true,
                                                                "description": "Motif, renseigné uniquement quand le service n'est pas opérationnel.",
                                                                "example": null
                                                            },
                                                            "uptime_percent": {
                                                                "type": "number",
                                                                "nullable": true,
                                                                "description": "Taux de disponibilité sur la fenêtre. null tant qu'aucun historique n'est disponible.",
                                                                "example": 99.86
                                                            }
                                                        }
                                                    }
                                                }
                                            }
                                        }
                                    }
                                }
                            }
                        }
                    },
                    "401": {
                        "description": "Clé API invalide (AUTH_INVALID_KEY)",
                        "content": {
                            "application/json": {
                                "schema": {
                                    "$ref": "#/components/schemas/ErrorResponse"
                                }
                            }
                        }
                    },
                    "429": {
                        "description": "Trop de requêtes (RATE_LIMIT)",
                        "content": {
                            "application/json": {
                                "schema": {
                                    "$ref": "#/components/schemas/ErrorResponse"
                                }
                            }
                        }
                    },
                    "500": {
                        "description": "Erreur interne (INTERNAL_ERROR), toujours en JSON.",
                        "content": {
                            "application/json": {
                                "schema": {
                                    "$ref": "#/components/schemas/ErrorResponse"
                                }
                            }
                        }
                    },
                    "503": {
                        "description": "Service momentanément indisponible (SERVICE_UNAVAILABLE) : réessayer après Retry-After.",
                        "content": {
                            "application/json": {
                                "schema": {
                                    "$ref": "#/components/schemas/ErrorResponse"
                                }
                            }
                        },
                        "headers": {
                            "Retry-After": {
                                "description": "Secondes à attendre avant de réessayer.",
                                "schema": {
                                    "type": "integer",
                                    "example": 30
                                }
                            }
                        }
                    }
                }
            }
        },
        "/api/v1/einvoices/validate": {
            "post": {
                "tags": [
                    "Émettre : cachet serveur (synchrone)"
                ],
                "summary": "Valider une facture sans la signer",
                "description": "Contrôle le corps de la facture avec les mêmes règles que /process et /prepare, sans signer ni déposer. Gratuit, même en production. Les erreurs sont listées dans data.errors.",
                "requestBody": {
                    "required": true,
                    "content": {
                        "application/json": {
                            "schema": {
                                "$ref": "#/components/schemas/SignRequest"
                            }
                        }
                    }
                },
                "responses": {
                    "200": {
                        "description": "Résultat de la validation",
                        "content": {
                            "application/json": {
                                "schema": {
                                    "type": "object",
                                    "properties": {
                                        "success": {
                                            "type": "boolean"
                                        },
                                        "data": {
                                            "type": "object",
                                            "properties": {
                                                "valid": {
                                                    "type": "boolean"
                                                },
                                                "errors": {
                                                    "type": "array",
                                                    "items": {
                                                        "type": "string"
                                                    }
                                                }
                                            }
                                        }
                                    }
                                }
                            }
                        }
                    },
                    "401": {
                        "description": "Clé API invalide (AUTH_INVALID_KEY)",
                        "content": {
                            "application/json": {
                                "schema": {
                                    "$ref": "#/components/schemas/ErrorResponse"
                                }
                            }
                        }
                    },
                    "500": {
                        "description": "Erreur interne (INTERNAL_ERROR), toujours en JSON.",
                        "content": {
                            "application/json": {
                                "schema": {
                                    "$ref": "#/components/schemas/ErrorResponse"
                                }
                            }
                        }
                    },
                    "503": {
                        "description": "Service momentanément indisponible (SERVICE_UNAVAILABLE) : réessayer après Retry-After.",
                        "content": {
                            "application/json": {
                                "schema": {
                                    "$ref": "#/components/schemas/ErrorResponse"
                                }
                            }
                        },
                        "headers": {
                            "Retry-After": {
                                "description": "Secondes à attendre avant de réessayer.",
                                "schema": {
                                    "type": "integer",
                                    "example": 30
                                }
                            }
                        }
                    }
                }
            }
        },
        "/api/v1/einvoices/process": {
            "post": {
                "tags": [
                    "Émettre : cachet serveur (synchrone)"
                ],
                "summary": "Signer par cachet serveur et déposer chez TTN (synchrone)",
                "description": "## Cachet serveur (Entreprise ID)\n\n**Synchrone** (~5 secondes), **sans interaction utilisateur** : idéal pour les volumes élevés et les ERP back-office.\n\n### Déroulé\n1. Validation du corps\n2. Génération du TEIF v1.8.8\n3. Signature XAdES par le cachet électronique de l'entreprise\n4. Dépôt chez TTN\n5. Réponse : XML signé (base64) + PDF (base64) + référence TTN\n\n### Test et production\n- Clé `ck_test_xxx` : signature et dépôt réels sur les environnements de TEST, aucun crédit consommé\n- Clé `ck_live_xxx` : signature et dépôt réels, 1 crédit par signature\n\n### Prérequis\n- Cachet provisionné et code PIN renseigné pour l'environnement de la clé (sinon `SEAL_NOT_CONFIGURED`)\n- DigiGo, clé USB et E-Houwiya passent par `/einvoices/prepare` (sinon `METHOD_REQUIRES_PREPARE`)\n- Renvoyer la même facture ne la signe pas deux fois : le document existant est rendu (`\"replayed\": true`)",
                "parameters": [
                    {
                        "$ref": "#/components/parameters/SimulationScenario"
                    }
                ],
                "requestBody": {
                    "required": true,
                    "content": {
                        "application/json": {
                            "schema": {
                                "$ref": "#/components/schemas/SignRequest"
                            },
                            "examples": {
                                "Basique": {
                                    "summary": "Facture minimale",
                                    "value": {
                                        "invoice_number": "F-2026-001",
                                        "date": "2026-06-13",
                                        "seller": {
                                            "name": "Mon Entreprise SARL",
                                            "tax_id": "1234567MAM000",
                                            "address": "Tunis"
                                        },
                                        "buyer": {
                                            "name": "Client SARL",
                                            "tax_id": "7654321XAM000"
                                        },
                                        "items": [
                                            {
                                                "description": "Service de conseil",
                                                "quantity": 1,
                                                "unit_price": 500,
                                                "vat_rate": 19
                                            }
                                        ]
                                    }
                                },
                                "Complete avec timbre + RS": {
                                    "summary": "Facture avec timbre fiscal et retenue à la source de 1,5 %",
                                    "value": {
                                        "invoice_number": "F-2026-042",
                                        "date": "2026-06-13",
                                        "due_date": "2026-07-13",
                                        "seller": {
                                            "name": "Cabinet Conseil & Co",
                                            "tax_id": "1234567MAM000",
                                            "address": "Avenue Habib Bourguiba, Tunis"
                                        },
                                        "buyer": {
                                            "name": "Societe Industrielle Tunisie SA",
                                            "tax_id": "7654321XAM000"
                                        },
                                        "items": [
                                            {
                                                "description": "Audit comptable 2026",
                                                "quantity": 1,
                                                "unit_price": 3500,
                                                "vat_rate": 19
                                            },
                                            {
                                                "description": "Formation équipe (3 jours)",
                                                "quantity": 3,
                                                "unit_price": 800,
                                                "vat_rate": 19
                                            }
                                        ],
                                        "stamp_duty": true,
                                        "withholding_tax_rate": 1.5
                                    }
                                }
                            }
                        }
                    }
                },
                "responses": {
                    "200": {
                        "description": "Facture signée et déposée",
                        "content": {
                            "application/json": {
                                "schema": {
                                    "$ref": "#/components/schemas/SignResponse"
                                }
                            }
                        }
                    },
                    "400": {
                        "description": "Corps de requête invalide (INVALID_JSON)",
                        "content": {
                            "application/json": {
                                "schema": {
                                    "$ref": "#/components/schemas/ErrorResponse"
                                }
                            }
                        }
                    },
                    "401": {
                        "description": "Clé API invalide (AUTH_INVALID_KEY)"
                    },
                    "402": {
                        "description": "Crédits insuffisants (INSUFFICIENT_CREDITS)"
                    },
                    "412": {
                        "description": "Cachet non provisionné ou code PIN manquant (SEAL_NOT_CONFIGURED)"
                    },
                    "422": {
                        "description": "Validation échouée (VALIDATION_FAILED, SELLER_MISMATCH, METHOD_REQUIRES_PREPARE) : voir error.details"
                    },
                    "429": {
                        "description": "Trop de requêtes (RATE_LIMIT)"
                    },
                    "503": {
                        "description": "Service de signature indisponible (SIGNING_FAILED)"
                    },
                    "403": {
                        "description": "Clé développeur utilisée pour émettre (DEVELOPER_KEY_NOT_ALLOWED)",
                        "content": {
                            "application/json": {
                                "schema": {
                                    "$ref": "#/components/schemas/ErrorResponse"
                                }
                            }
                        }
                    },
                    "409": {
                        "description": "Numéro déjà émis pour un autre contenu, ou signature en cours (INVOICE_NUMBER_ALREADY_ISSUED, INVOICE_IN_PROGRESS)",
                        "content": {
                            "application/json": {
                                "schema": {
                                    "$ref": "#/components/schemas/ErrorResponse"
                                }
                            }
                        }
                    },
                    "500": {
                        "description": "Erreur interne (INTERNAL_ERROR), toujours en JSON.",
                        "content": {
                            "application/json": {
                                "schema": {
                                    "$ref": "#/components/schemas/ErrorResponse"
                                }
                            }
                        }
                    }
                }
            }
        },
        "/api/v1/einvoices/prepare": {
            "post": {
                "tags": [
                    "Émettre : signature par l'utilisateur (asynchrone)"
                ],
                "summary": "Préparer une signature DigiGo, clé USB ou E-Houwiya (asynchrone)",
                "description": "## Signature par l'utilisateur\n\n**Asynchrone** : le signataire valide lui-même, sur la page de signature (DigiGo : PIN + OTP ; clé USB : token sur son poste ; E-Houwiya : smartphone).\n\n### Déroulé\n1. Votre ERP appelle `POST /einvoices/prepare` avec `method` (`digigo`, `usb` ou `mobileid`) et `return_url`\n2. Réponse : `signing_id` + `signing_url`\n3. Votre ERP redirige l'utilisateur vers `signing_url`, sans l'analyser\n4. Il signe ; la signature est déposée chez TTN\n5. Il revient sur votre `return_url`\n6. Vous lisez le résultat par `GET /einvoices/{signing_id}` jusqu'à `status: validated`\n\n### À savoir\n- DigiGo et E-Houwiya exigent l'adresse du signataire sur le client (`signer_email`), sinon `SIGNER_EMAIL_MISSING`\n- Lien perdu : renvoyez la même facture, vous recevez le même lien (`\"resumed\": true`)\n- Arrêter : `POST /einvoices/{id}/cancel`, sans crédit consommé\n- Sans signature, la tentative s'abandonne d'elle-même au bout de 30 minutes",
                "parameters": [
                    {
                        "$ref": "#/components/parameters/SimulationScenario"
                    }
                ],
                "requestBody": {
                    "required": true,
                    "content": {
                        "application/json": {
                            "schema": {
                                "$ref": "#/components/schemas/SignRequest"
                            },
                            "examples": {
                                "DigiGo (PIN + OTP)": {
                                    "summary": "Signature DigiGo avec redirection",
                                    "value": {
                                        "invoice_number": "F-2026-001",
                                        "date": "2026-06-13",
                                        "seller": {
                                            "name": "Mon Entreprise SARL",
                                            "tax_id": "1234567MAM000",
                                            "address": "Tunis"
                                        },
                                        "buyer": {
                                            "name": "Client SARL",
                                            "tax_id": "7654321XAM000"
                                        },
                                        "items": [
                                            {
                                                "description": "Service de conseil",
                                                "quantity": 1,
                                                "unit_price": 500,
                                                "vat_rate": 19
                                            }
                                        ],
                                        "method": "digigo",
                                        "return_url": "https://votre-erp.tn/factures/F-2026-001/retour-signature"
                                    }
                                }
                            }
                        }
                    }
                },
                "responses": {
                    "200": {
                        "description": "Signature préparée : redirigez l'utilisateur vers signing_url",
                        "content": {
                            "application/json": {
                                "schema": {
                                    "type": "object",
                                    "properties": {
                                        "success": {
                                            "type": "boolean",
                                            "example": true
                                        },
                                        "data": {
                                            "type": "object",
                                            "properties": {
                                                "status": {
                                                    "type": "string",
                                                    "example": "pending_signature"
                                                },
                                                "signing_id": {
                                                    "type": "string",
                                                    "example": "sg_a7f9c21b3e4d8f6a9b2c1d4e",
                                                    "description": "Identifiant unique pour suivre cette signature par GET /einvoices/{signing_id}."
                                                },
                                                "signing_url": {
                                                    "type": "string",
                                                    "example": "https://signature-securisee.tn/sign/xa7f9c21",
                                                    "description": "Adresse opaque vers laquelle rediriger l'utilisateur."
                                                },
                                                "method": {
                                                    "type": "string",
                                                    "example": "digigo"
                                                },
                                                "invoice_number": {
                                                    "type": "string",
                                                    "example": "F-2026-001"
                                                },
                                                "resumed": {
                                                    "type": "boolean",
                                                    "description": "Présent et vrai quand une signature attendait déjà sur la même facture : le lien rendu est celui déjà ouvert."
                                                }
                                            }
                                        }
                                    }
                                }
                            }
                        }
                    },
                    "401": {
                        "description": "Clé API invalide (AUTH_INVALID_KEY)"
                    },
                    "422": {
                        "description": "Validation échouée (VALIDATION_FAILED, INVALID_METHOD) : return_url requis pour digigo, usb et mobileid"
                    },
                    "429": {
                        "description": "Trop de requêtes (RATE_LIMIT)"
                    },
                    "503": {
                        "description": "Service de signature indisponible (SIGNING_FAILED)"
                    },
                    "403": {
                        "description": "Clé développeur utilisée pour émettre (DEVELOPER_KEY_NOT_ALLOWED)",
                        "content": {
                            "application/json": {
                                "schema": {
                                    "$ref": "#/components/schemas/ErrorResponse"
                                }
                            }
                        }
                    },
                    "409": {
                        "description": "Signature en cours avec un autre contenu (INVOICE_IN_PROGRESS) : error.details porte signing_url et can_prepare_again_at",
                        "content": {
                            "application/json": {
                                "schema": {
                                    "$ref": "#/components/schemas/ErrorResponse"
                                }
                            }
                        }
                    },
                    "412": {
                        "description": "Compte de signature pas encore créé, ou adresse du signataire manquante (SIGNER_NOT_CONFIGURED, SIGNER_EMAIL_MISSING)",
                        "content": {
                            "application/json": {
                                "schema": {
                                    "$ref": "#/components/schemas/ErrorResponse"
                                }
                            }
                        }
                    },
                    "500": {
                        "description": "Erreur interne (INTERNAL_ERROR), toujours en JSON.",
                        "content": {
                            "application/json": {
                                "schema": {
                                    "$ref": "#/components/schemas/ErrorResponse"
                                }
                            }
                        }
                    }
                }
            }
        },
        "/api/v1/einvoices/{id}": {
            "get": {
                "tags": [
                    "Émettre : signature par l'utilisateur (asynchrone)"
                ],
                "summary": "Lire une e-facture : statut et documents (polling)",
                "description": "## Lire le résultat\n\nPour le **polling** après `POST /einvoices/prepare`, ou pour relire une facture.\n\n| Statut | Description |\n|---|---|\n| `pending_signature` | En attente du signataire ; `signing_url` donne le lien |\n| `signed` | Signature reçue, dépôt TTN en cours |\n| `submitted_ttn` | Déposée chez TTN, confirmation attendue |\n| `validated` | Acceptée par TTN : `xml_base64`, `pdf_base64` et référence disponibles |\n| `rejected` | Annulée ou refusée : voir `signature`, `ttn` et `ttn.next_action` |\n\nLa référence définitive et le QR officiel arrivent quelques minutes après le dépôt : `ttn.reference_confirmee` passe alors à `true`.\n\nChaque clé ne lit que son environnement (`WRONG_ENVIRONMENT` sinon) ; la clé développeur voit les deux.\n\n### Polling recommandé\n- Premier appel 5 secondes après `/prepare`, puis 10 s, 20 s, 30 s, 60 s…\n- **Arrêtez d'interroger** dès que `status` vaut `rejected`, ou `validated` avec `ttn.reference_confirmee: true`.\n- Une signature non terminée en 7 jours passe d'elle-même en `rejected`, `error.code = SIGNATURE_EXPIRED`, sans crédit débité. Relancez-la par un nouveau `POST /einvoices/prepare` du même numéro, et suivez le **nouveau** `signing_id`.\n- Le service de signature n'est interrogé qu'une fois toutes les 30 secondes par facture : interroger plus souvent rend le même statut.\n\n### Quel message afficher\n`error` (à la racine) porte le motif de tout rejet. `ttn.error` n'est rempli que si TTN a refusé un document **déjà signé** ; il porte alors le même code et le même message. Affichez `error.message`.",
                "parameters": [
                    {
                        "name": "id",
                        "in": "path",
                        "required": true,
                        "schema": {
                            "type": "string"
                        },
                        "description": "Le signing_id rendu à l'émission (format sg_...)."
                    }
                ],
                "responses": {
                    "200": {
                        "description": "Statut courant, et documents quand ils existent",
                        "content": {
                            "application/json": {
                                "schema": {
                                    "type": "object",
                                    "properties": {
                                        "success": {
                                            "type": "boolean"
                                        },
                                        "data": {
                                            "type": "object",
                                            "properties": {
                                                "signing_id": {
                                                    "type": "string"
                                                },
                                                "invoice_number": {
                                                    "type": "string",
                                                    "example": "F-2026-001"
                                                },
                                                "status": {
                                                    "type": "string",
                                                    "enum": [
                                                        "pending_signature",
                                                        "signed",
                                                        "submitted_ttn",
                                                        "validated",
                                                        "rejected"
                                                    ],
                                                    "example": "validated"
                                                },
                                                "ttn_reference": {
                                                    "type": "string",
                                                    "example": "TTN-2026-XXXXX"
                                                },
                                                "xml_base64": {
                                                    "type": "string",
                                                    "description": "XML signé, dès la signature."
                                                },
                                                "pdf_base64": {
                                                    "type": "string",
                                                    "description": "PDF, dès la signature ; QR officiel apposé à la confirmation."
                                                },
                                                "method": {
                                                    "type": "string",
                                                    "example": "digigo"
                                                },
                                                "signing_url": {
                                                    "type": "string",
                                                    "nullable": true,
                                                    "description": "Lien de signature, tant que la signature attend."
                                                },
                                                "error": {
                                                    "type": "object",
                                                    "nullable": true,
                                                    "properties": {
                                                        "code": {
                                                            "type": "string",
                                                            "example": "SIGNATURE_EXPIRED"
                                                        },
                                                        "message": {
                                                            "type": "string"
                                                        }
                                                    },
                                                    "description": "Motif du rejet quand status = rejected (SIGNATURE_EXPIRED, ABANDONED_ATTEMPT, TTN_REJECTED…). Null sinon. C'est lui qu'il faut afficher."
                                                },
                                                "signature": {
                                                    "type": "object",
                                                    "properties": {
                                                        "status": {
                                                            "type": "string",
                                                            "enum": [
                                                                "pending",
                                                                "signed",
                                                                "rejected",
                                                                "unknown"
                                                            ]
                                                        },
                                                        "signed_xml_available": {
                                                            "type": "boolean"
                                                        }
                                                    }
                                                },
                                                "ttn": {
                                                    "type": "object",
                                                    "properties": {
                                                        "status": {
                                                            "type": "string",
                                                            "enum": [
                                                                "not_reached",
                                                                "submitted",
                                                                "validated",
                                                                "rejected"
                                                            ]
                                                        },
                                                        "reference": {
                                                            "type": "string",
                                                            "nullable": true
                                                        },
                                                        "reference_confirmee": {
                                                            "type": "boolean",
                                                            "description": "Vrai quand la référence définitive et le QR officiel de TTN sont reçus."
                                                        },
                                                        "confirmed_at": {
                                                            "type": "string",
                                                            "format": "date-time",
                                                            "nullable": true
                                                        },
                                                        "qr_png_base64": {
                                                            "type": "string",
                                                            "nullable": true,
                                                            "description": "Le QR officiel (cachet) fabriqué par TTN, image PNG."
                                                        },
                                                        "error": {
                                                            "type": "object",
                                                            "nullable": true,
                                                            "properties": {
                                                                "code": {
                                                                    "type": "string",
                                                                    "example": "SIGNATURE_EXPIRED"
                                                                },
                                                                "message": {
                                                                    "type": "string"
                                                                }
                                                            },
                                                            "description": "Rempli seulement si TTN a refusé un document déjà signé : même code et même message que error."
                                                        },
                                                        "next_action": {
                                                            "type": "string",
                                                            "nullable": true,
                                                            "enum": [
                                                                "resubmit",
                                                                "resign"
                                                            ],
                                                            "description": "Après un refus TTN : redéposer tel quel, ou re-signer."
                                                        }
                                                    }
                                                },
                                                "xml_validettn_base64": {
                                                    "type": "string",
                                                    "nullable": true,
                                                    "description": "Le document final validé par TTN (avec sa référence et son cachet) : à archiver."
                                                },
                                                "can_prepare_again_at": {
                                                    "type": "string",
                                                    "format": "date-time",
                                                    "nullable": true,
                                                    "description": "Tant qu'une signature attend : heure à laquelle le numéro peut se préparer de nouveau."
                                                },
                                                "test_mode": {
                                                    "type": "boolean"
                                                }
                                            }
                                        }
                                    }
                                }
                            }
                        }
                    },
                    "401": {
                        "description": "Clé API invalide (AUTH_INVALID_KEY)"
                    },
                    "404": {
                        "description": "Aucune facture ne porte ce signing_id (NOT_FOUND)"
                    },
                    "403": {
                        "description": "Facture d'un autre compte ou d'un autre environnement (PERMISSION_DENIED, WRONG_ENVIRONMENT)",
                        "content": {
                            "application/json": {
                                "schema": {
                                    "$ref": "#/components/schemas/ErrorResponse"
                                }
                            }
                        }
                    },
                    "500": {
                        "description": "Erreur interne (INTERNAL_ERROR), toujours en JSON.",
                        "content": {
                            "application/json": {
                                "schema": {
                                    "$ref": "#/components/schemas/ErrorResponse"
                                }
                            }
                        }
                    },
                    "503": {
                        "description": "Service momentanément indisponible (SERVICE_UNAVAILABLE) : réessayer après Retry-After.",
                        "content": {
                            "application/json": {
                                "schema": {
                                    "$ref": "#/components/schemas/ErrorResponse"
                                }
                            }
                        },
                        "headers": {
                            "Retry-After": {
                                "description": "Secondes à attendre avant de réessayer.",
                                "schema": {
                                    "type": "integer",
                                    "example": 30
                                }
                            }
                        }
                    }
                }
            }
        },
        "/api/v1/einvoices/{id}/resubmit-ttn": {
            "post": {
                "tags": [
                    "Suivre une facture"
                ],
                "summary": "Redéposer chez TTN sans re-signer",
                "description": "Redépose le document signé tel quel, sans nouvelle signature et sans crédit, quand le dépôt a échoué pour une cause extérieure au document (ttn.next_action = resubmit). Corps vide.",
                "parameters": [
                    {
                        "$ref": "#/components/parameters/SimulationScenario"
                    },
                    {
                        "name": "id",
                        "in": "path",
                        "required": true,
                        "schema": {
                            "type": "string"
                        },
                        "description": "Le signing_id rendu à l'émission (format sg_...)."
                    }
                ],
                "responses": {
                    "200": {
                        "description": "Document redéposé : statut validated ou submitted_ttn",
                        "content": {
                            "application/json": {
                                "schema": {
                                    "type": "object",
                                    "properties": {
                                        "success": {
                                            "type": "boolean",
                                            "example": true
                                        },
                                        "data": {
                                            "type": "object",
                                            "properties": {
                                                "signing_id": {
                                                    "type": "string"
                                                },
                                                "status": {
                                                    "type": "string",
                                                    "example": "validated"
                                                },
                                                "ttn_reference": {
                                                    "type": "string"
                                                },
                                                "credits_consumed": {
                                                    "type": "integer",
                                                    "example": 0
                                                }
                                            }
                                        }
                                    }
                                }
                            }
                        }
                    },
                    "403": {
                        "description": "Clé développeur, facture d'un autre compte ou d'un autre environnement",
                        "content": {
                            "application/json": {
                                "schema": {
                                    "$ref": "#/components/schemas/ErrorResponse"
                                }
                            }
                        }
                    },
                    "404": {
                        "description": "Aucune facture ne porte ce signing_id (NOT_FOUND)",
                        "content": {
                            "application/json": {
                                "schema": {
                                    "$ref": "#/components/schemas/ErrorResponse"
                                }
                            }
                        }
                    },
                    "409": {
                        "description": "Rien à renvoyer ou renvoi inutile (NOT_SIGNED, AWAITING_TTN, ALREADY_VALIDATED, DEPOSIT_DELEGATED, RESIGN_REQUIRED, DOCUMENTS_PURGED)",
                        "content": {
                            "application/json": {
                                "schema": {
                                    "$ref": "#/components/schemas/ErrorResponse"
                                }
                            }
                        }
                    },
                    "502": {
                        "description": "TTN refuse ou ne répond pas (TTN_DEPOSIT_FAILED)",
                        "content": {
                            "application/json": {
                                "schema": {
                                    "$ref": "#/components/schemas/ErrorResponse"
                                }
                            }
                        }
                    },
                    "500": {
                        "description": "Erreur interne (INTERNAL_ERROR), toujours en JSON.",
                        "content": {
                            "application/json": {
                                "schema": {
                                    "$ref": "#/components/schemas/ErrorResponse"
                                }
                            }
                        }
                    },
                    "503": {
                        "description": "Service momentanément indisponible (SERVICE_UNAVAILABLE) : réessayer après Retry-After.",
                        "content": {
                            "application/json": {
                                "schema": {
                                    "$ref": "#/components/schemas/ErrorResponse"
                                }
                            }
                        },
                        "headers": {
                            "Retry-After": {
                                "description": "Secondes à attendre avant de réessayer.",
                                "schema": {
                                    "type": "integer",
                                    "example": 30
                                }
                            }
                        }
                    }
                }
            }
        },
        "/api/v1/einvoices/{id}/cancel": {
            "post": {
                "tags": [
                    "Suivre une facture"
                ],
                "summary": "Annuler une signature en attente",
                "description": "Arrête une signature ouverte par /prepare et pas encore faite. Aucun crédit n'est consommé ; si l'utilisateur signe au même instant, la facture est traitée comme signée, avec un seul crédit. Corps vide.",
                "parameters": [
                    {
                        "name": "id",
                        "in": "path",
                        "required": true,
                        "schema": {
                            "type": "string"
                        },
                        "description": "Le signing_id rendu à l'émission (format sg_...)."
                    }
                ],
                "responses": {
                    "200": {
                        "description": "Signature annulée : le numéro se prépare de nouveau aussitôt",
                        "content": {
                            "application/json": {
                                "schema": {
                                    "type": "object",
                                    "properties": {
                                        "success": {
                                            "type": "boolean",
                                            "example": true
                                        },
                                        "data": {
                                            "type": "object",
                                            "properties": {
                                                "signing_id": {
                                                    "type": "string"
                                                },
                                                "invoice_number": {
                                                    "type": "string"
                                                },
                                                "status": {
                                                    "type": "string",
                                                    "example": "cancelled"
                                                },
                                                "credits_consumed": {
                                                    "type": "integer",
                                                    "example": 0
                                                }
                                            }
                                        }
                                    }
                                }
                            }
                        }
                    },
                    "403": {
                        "description": "Clé développeur, facture d'un autre compte ou d'un autre environnement",
                        "content": {
                            "application/json": {
                                "schema": {
                                    "$ref": "#/components/schemas/ErrorResponse"
                                }
                            }
                        }
                    },
                    "404": {
                        "description": "Aucune facture ne porte ce signing_id (NOT_FOUND)",
                        "content": {
                            "application/json": {
                                "schema": {
                                    "$ref": "#/components/schemas/ErrorResponse"
                                }
                            }
                        }
                    },
                    "409": {
                        "description": "Trop tard ou non disponible (ALREADY_SIGNED, SIGNATURE_ALREADY_CLOSED, CANCEL_NOT_SUPPORTED)",
                        "content": {
                            "application/json": {
                                "schema": {
                                    "$ref": "#/components/schemas/ErrorResponse"
                                }
                            }
                        }
                    },
                    "502": {
                        "description": "Annulation non confirmée, rien n'est annulé ni débité (CANCEL_UNCONFIRMED)",
                        "content": {
                            "application/json": {
                                "schema": {
                                    "$ref": "#/components/schemas/ErrorResponse"
                                }
                            }
                        }
                    },
                    "500": {
                        "description": "Erreur interne (INTERNAL_ERROR), toujours en JSON.",
                        "content": {
                            "application/json": {
                                "schema": {
                                    "$ref": "#/components/schemas/ErrorResponse"
                                }
                            }
                        }
                    },
                    "503": {
                        "description": "Service momentanément indisponible (SERVICE_UNAVAILABLE) : réessayer après Retry-After.",
                        "content": {
                            "application/json": {
                                "schema": {
                                    "$ref": "#/components/schemas/ErrorResponse"
                                }
                            }
                        },
                        "headers": {
                            "Retry-After": {
                                "description": "Secondes à attendre avant de réessayer.",
                                "schema": {
                                    "type": "integer",
                                    "example": 30
                                }
                            }
                        }
                    }
                }
            }
        },
        "/api/v1/credits/balance": {
            "get": {
                "tags": [
                    "Crédits"
                ],
                "summary": "Solde de crédits",
                "description": "Solde courant, total consommé et total acheté du portefeuille.",
                "responses": {
                    "200": {
                        "description": "Solde de crédits",
                        "content": {
                            "application/json": {
                                "schema": {
                                    "type": "object",
                                    "properties": {
                                        "success": {
                                            "type": "boolean",
                                            "example": true
                                        },
                                        "data": {
                                            "type": "object",
                                            "properties": {
                                                "balance": {
                                                    "type": "integer",
                                                    "example": 4230
                                                },
                                                "total_used": {
                                                    "type": "integer",
                                                    "example": 770
                                                },
                                                "total_purchased": {
                                                    "type": "integer",
                                                    "example": 5000
                                                }
                                            }
                                        }
                                    }
                                }
                            }
                        }
                    },
                    "500": {
                        "description": "Erreur interne (INTERNAL_ERROR), toujours en JSON.",
                        "content": {
                            "application/json": {
                                "schema": {
                                    "$ref": "#/components/schemas/ErrorResponse"
                                }
                            }
                        }
                    },
                    "503": {
                        "description": "Service momentanément indisponible (SERVICE_UNAVAILABLE) : réessayer après Retry-After.",
                        "content": {
                            "application/json": {
                                "schema": {
                                    "$ref": "#/components/schemas/ErrorResponse"
                                }
                            }
                        },
                        "headers": {
                            "Retry-After": {
                                "description": "Secondes à attendre avant de réessayer.",
                                "schema": {
                                    "type": "integer",
                                    "example": 30
                                }
                            }
                        }
                    }
                }
            }
        },
        "/api/v1/clients": {
            "get": {
                "tags": [
                    "Clients (clé développeur)"
                ],
                "security": [
                    {
                        "ApiKeyAuth": []
                    }
                ],
                "summary": "Lister vos clients",
                "description": "Clé développeur (sk_dev_). Tous vos clients, secrets masqués, avec crédits, statistiques et solde du portefeuille.",
                "responses": {
                    "200": {
                        "description": "Liste des clients et portefeuille"
                    },
                    "403": {
                        "description": "Clé de client utilisée à la place de la clé développeur",
                        "content": {
                            "application/json": {
                                "schema": {
                                    "$ref": "#/components/schemas/ErrorResponse"
                                }
                            }
                        }
                    },
                    "500": {
                        "description": "Erreur interne (INTERNAL_ERROR), toujours en JSON.",
                        "content": {
                            "application/json": {
                                "schema": {
                                    "$ref": "#/components/schemas/ErrorResponse"
                                }
                            }
                        }
                    },
                    "503": {
                        "description": "Service momentanément indisponible (SERVICE_UNAVAILABLE) : réessayer après Retry-After.",
                        "content": {
                            "application/json": {
                                "schema": {
                                    "$ref": "#/components/schemas/ErrorResponse"
                                }
                            }
                        },
                        "headers": {
                            "Retry-After": {
                                "description": "Secondes à attendre avant de réessayer.",
                                "schema": {
                                    "type": "integer",
                                    "example": 30
                                }
                            }
                        }
                    }
                }
            },
            "post": {
                "tags": [
                    "Clients (clé développeur)"
                ],
                "security": [
                    {
                        "ApiKeyAuth": []
                    }
                ],
                "summary": "Créer un client",
                "description": "Clé développeur (sk_dev_). Crée un client et rend ses clés ck_test_ / ck_live_. Ajoutez provision pour créer en même temps son compte de signature.",
                "requestBody": {
                    "required": true,
                    "content": {
                        "application/json": {
                            "schema": {
                                "type": "object",
                                "properties": {
                                    "name": {
                                        "type": "string",
                                        "example": "Société ABC"
                                    },
                                    "matricule": {
                                        "type": "string",
                                        "example": "1234567AAM000"
                                    },
                                    "signer_method": {
                                        "type": "string",
                                        "enum": [
                                            "seal",
                                            "usb",
                                            "digigo",
                                            "mobileid"
                                        ]
                                    },
                                    "signer_email": {
                                        "type": "string",
                                        "format": "email",
                                        "description": "Adresse du certificat DigiGo ou E-Houwiya du signataire. Obligatoire pour digigo et mobileid."
                                    },
                                    "seal_passphrase_test": {
                                        "type": "string",
                                        "description": "Code PIN du cachet sandbox. Masqué en lecture."
                                    },
                                    "seal_passphrase_prod": {
                                        "type": "string",
                                        "description": "Code PIN du cachet production. Masqué en lecture."
                                    },
                                    "submission_channel": {
                                        "type": "string",
                                        "enum": [
                                            "delegated",
                                            "soap",
                                            "sftp"
                                        ],
                                        "description": "Qui dépose au TTN : delegated (le service de signature, défaut), soap ou sftp (nous, avec les accès TTN du client)."
                                    },
                                    "credit_cap": {
                                        "type": "integer",
                                        "nullable": true,
                                        "description": "Plafond de crédits du client ; null = illimité."
                                    },
                                    "ttn": {
                                        "type": "object",
                                        "description": "Accès TTN par environnement : sandbox / prod, chacun { login, password }."
                                    },
                                    "provision": {
                                        "type": "string",
                                        "enum": [
                                            "sandbox",
                                            "production",
                                            "both"
                                        ],
                                        "description": "Crée ou met à jour le compte de signature du client."
                                    }
                                }
                            }
                        }
                    }
                },
                "responses": {
                    "201": {
                        "description": "Client créé, clés dans data.keys"
                    },
                    "422": {
                        "description": "Champ invalide, matricule absent ou déjà enregistré (VALIDATION_FAILED)",
                        "content": {
                            "application/json": {
                                "schema": {
                                    "$ref": "#/components/schemas/ErrorResponse"
                                }
                            }
                        }
                    },
                    "500": {
                        "description": "Erreur interne (INTERNAL_ERROR), toujours en JSON.",
                        "content": {
                            "application/json": {
                                "schema": {
                                    "$ref": "#/components/schemas/ErrorResponse"
                                }
                            }
                        }
                    },
                    "503": {
                        "description": "Service momentanément indisponible (SERVICE_UNAVAILABLE) : réessayer après Retry-After.",
                        "content": {
                            "application/json": {
                                "schema": {
                                    "$ref": "#/components/schemas/ErrorResponse"
                                }
                            }
                        },
                        "headers": {
                            "Retry-After": {
                                "description": "Secondes à attendre avant de réessayer.",
                                "schema": {
                                    "type": "integer",
                                    "example": 30
                                }
                            }
                        }
                    }
                }
            }
        },
        "/api/v1/clients/{id}": {
            "get": {
                "tags": [
                    "Clients (clé développeur)"
                ],
                "security": [
                    {
                        "ApiKeyAuth": []
                    }
                ],
                "summary": "Lire un client",
                "parameters": [
                    {
                        "name": "id",
                        "in": "path",
                        "required": true,
                        "schema": {
                            "type": "string"
                        },
                        "description": "Identifiant du client."
                    }
                ],
                "description": "Configuration, crédits, statistiques et clés API du client (mots de passe masqués).",
                "responses": {
                    "200": {
                        "description": "Le client"
                    },
                    "404": {
                        "description": "Client introuvable (NOT_FOUND)",
                        "content": {
                            "application/json": {
                                "schema": {
                                    "$ref": "#/components/schemas/ErrorResponse"
                                }
                            }
                        }
                    },
                    "500": {
                        "description": "Erreur interne (INTERNAL_ERROR), toujours en JSON.",
                        "content": {
                            "application/json": {
                                "schema": {
                                    "$ref": "#/components/schemas/ErrorResponse"
                                }
                            }
                        }
                    },
                    "503": {
                        "description": "Service momentanément indisponible (SERVICE_UNAVAILABLE) : réessayer après Retry-After.",
                        "content": {
                            "application/json": {
                                "schema": {
                                    "$ref": "#/components/schemas/ErrorResponse"
                                }
                            }
                        },
                        "headers": {
                            "Retry-After": {
                                "description": "Secondes à attendre avant de réessayer.",
                                "schema": {
                                    "type": "integer",
                                    "example": 30
                                }
                            }
                        }
                    }
                }
            },
            "patch": {
                "tags": [
                    "Clients (clé développeur)"
                ],
                "security": [
                    {
                        "ApiKeyAuth": []
                    }
                ],
                "summary": "Modifier un client",
                "parameters": [
                    {
                        "name": "id",
                        "in": "path",
                        "required": true,
                        "schema": {
                            "type": "string"
                        },
                        "description": "Identifiant du client."
                    }
                ],
                "description": "Mise à jour partielle : seuls les champs envoyés changent. Un mot de passe omis est conservé.",
                "requestBody": {
                    "required": true,
                    "content": {
                        "application/json": {
                            "schema": {
                                "type": "object",
                                "properties": {
                                    "name": {
                                        "type": "string",
                                        "example": "Société ABC"
                                    },
                                    "matricule": {
                                        "type": "string",
                                        "example": "1234567AAM000"
                                    },
                                    "signer_method": {
                                        "type": "string",
                                        "enum": [
                                            "seal",
                                            "usb",
                                            "digigo",
                                            "mobileid"
                                        ]
                                    },
                                    "signer_email": {
                                        "type": "string",
                                        "format": "email",
                                        "description": "Adresse du certificat DigiGo ou E-Houwiya du signataire. Obligatoire pour digigo et mobileid."
                                    },
                                    "seal_passphrase_test": {
                                        "type": "string",
                                        "description": "Code PIN du cachet sandbox. Masqué en lecture."
                                    },
                                    "seal_passphrase_prod": {
                                        "type": "string",
                                        "description": "Code PIN du cachet production. Masqué en lecture."
                                    },
                                    "submission_channel": {
                                        "type": "string",
                                        "enum": [
                                            "delegated",
                                            "soap",
                                            "sftp"
                                        ],
                                        "description": "Qui dépose au TTN : delegated (le service de signature, défaut), soap ou sftp (nous, avec les accès TTN du client)."
                                    },
                                    "credit_cap": {
                                        "type": "integer",
                                        "nullable": true,
                                        "description": "Plafond de crédits du client ; null = illimité."
                                    },
                                    "ttn": {
                                        "type": "object",
                                        "description": "Accès TTN par environnement : sandbox / prod, chacun { login, password }."
                                    },
                                    "provision": {
                                        "type": "string",
                                        "enum": [
                                            "sandbox",
                                            "production",
                                            "both"
                                        ],
                                        "description": "Crée ou met à jour le compte de signature du client."
                                    }
                                }
                            }
                        }
                    }
                },
                "responses": {
                    "200": {
                        "description": "Le client mis à jour"
                    },
                    "404": {
                        "description": "Client introuvable (NOT_FOUND)",
                        "content": {
                            "application/json": {
                                "schema": {
                                    "$ref": "#/components/schemas/ErrorResponse"
                                }
                            }
                        }
                    },
                    "422": {
                        "description": "Champ invalide (VALIDATION_FAILED)",
                        "content": {
                            "application/json": {
                                "schema": {
                                    "$ref": "#/components/schemas/ErrorResponse"
                                }
                            }
                        }
                    },
                    "500": {
                        "description": "Erreur interne (INTERNAL_ERROR), toujours en JSON.",
                        "content": {
                            "application/json": {
                                "schema": {
                                    "$ref": "#/components/schemas/ErrorResponse"
                                }
                            }
                        }
                    },
                    "503": {
                        "description": "Service momentanément indisponible (SERVICE_UNAVAILABLE) : réessayer après Retry-After.",
                        "content": {
                            "application/json": {
                                "schema": {
                                    "$ref": "#/components/schemas/ErrorResponse"
                                }
                            }
                        },
                        "headers": {
                            "Retry-After": {
                                "description": "Secondes à attendre avant de réessayer.",
                                "schema": {
                                    "type": "integer",
                                    "example": 30
                                }
                            }
                        }
                    }
                }
            },
            "delete": {
                "tags": [
                    "Clients (clé développeur)"
                ],
                "security": [
                    {
                        "ApiKeyAuth": []
                    }
                ],
                "summary": "Supprimer un client",
                "parameters": [
                    {
                        "name": "id",
                        "in": "path",
                        "required": true,
                        "schema": {
                            "type": "string"
                        },
                        "description": "Identifiant du client."
                    }
                ],
                "responses": {
                    "200": {
                        "description": "Client supprimé (data.deleted = true)"
                    },
                    "404": {
                        "description": "Client introuvable (NOT_FOUND)",
                        "content": {
                            "application/json": {
                                "schema": {
                                    "$ref": "#/components/schemas/ErrorResponse"
                                }
                            }
                        }
                    },
                    "500": {
                        "description": "Erreur interne (INTERNAL_ERROR), toujours en JSON.",
                        "content": {
                            "application/json": {
                                "schema": {
                                    "$ref": "#/components/schemas/ErrorResponse"
                                }
                            }
                        }
                    },
                    "503": {
                        "description": "Service momentanément indisponible (SERVICE_UNAVAILABLE) : réessayer après Retry-After.",
                        "content": {
                            "application/json": {
                                "schema": {
                                    "$ref": "#/components/schemas/ErrorResponse"
                                }
                            }
                        },
                        "headers": {
                            "Retry-After": {
                                "description": "Secondes à attendre avant de réessayer.",
                                "schema": {
                                    "type": "integer",
                                    "example": 30
                                }
                            }
                        }
                    }
                }
            }
        },
        "/api/v1/clients/{id}/keys": {
            "post": {
                "tags": [
                    "Clients (clé développeur)"
                ],
                "security": [
                    {
                        "ApiKeyAuth": []
                    }
                ],
                "summary": "Régénérer les clés d'un client",
                "parameters": [
                    {
                        "name": "id",
                        "in": "path",
                        "required": true,
                        "schema": {
                            "type": "string"
                        },
                        "description": "Identifiant du client."
                    }
                ],
                "description": "Les anciennes clés cessent immédiatement de fonctionner ; les nouvelles sont rendues dans data.keys.",
                "responses": {
                    "200": {
                        "description": "Nouvelles clés"
                    },
                    "404": {
                        "description": "Client introuvable (NOT_FOUND)",
                        "content": {
                            "application/json": {
                                "schema": {
                                    "$ref": "#/components/schemas/ErrorResponse"
                                }
                            }
                        }
                    },
                    "500": {
                        "description": "Erreur interne (INTERNAL_ERROR), toujours en JSON.",
                        "content": {
                            "application/json": {
                                "schema": {
                                    "$ref": "#/components/schemas/ErrorResponse"
                                }
                            }
                        }
                    },
                    "503": {
                        "description": "Service momentanément indisponible (SERVICE_UNAVAILABLE) : réessayer après Retry-After.",
                        "content": {
                            "application/json": {
                                "schema": {
                                    "$ref": "#/components/schemas/ErrorResponse"
                                }
                            }
                        },
                        "headers": {
                            "Retry-After": {
                                "description": "Secondes à attendre avant de réessayer.",
                                "schema": {
                                    "type": "integer",
                                    "example": 30
                                }
                            }
                        }
                    }
                }
            }
        }
    },
    "tags": [
        {
            "name": "Général",
            "description": "État de la plateforme."
        },
        {
            "name": "Émettre : cachet serveur (synchrone)",
            "description": "Valider, puis signer et déposer en une requête."
        },
        {
            "name": "Émettre : signature par l'utilisateur (asynchrone)",
            "description": "DigiGo, clé USB, E-Houwiya."
        },
        {
            "name": "Suivre une facture",
            "description": "Lire, redéposer chez TTN, annuler une signature en attente."
        },
        {
            "name": "Crédits",
            "description": "Solde du portefeuille."
        },
        {
            "name": "Clients (clé développeur)",
            "description": "Créer et configurer vos clients avec votre clé sk_dev_."
        }
    ]
}
