API eFacture Connect
Intégrez la facture électronique tunisienne dans votre ERP en quelques minutes. Envoyez du JSON, recevez un XML TEIF v1.8.8 signé (XAdES) et un PDF avec QR code, prêts à être déposés chez TunisieTradeNet.
https://efacturetn.com/api/v1Premier appel en 30 secondes
Copiez la commande à droite dans votre terminal pour tester l'API en mode sandbox. Remplacez ck_test_xxx par la clé de test d'un de vos clients, affichée dans le portail développeur à sa création (Comptes et clients). Votre clé développeur sk_dev_ n'émet pas de facture.
Vous recevrez en retour un XML réellement signé (environnement sandbox) et un PDF, exactement comme en production. Aucun crédit n'est consommé en mode test.
Authentification
L'API utilise des clés API passées dans le header X-Api-Key. Chaque requête doit inclure une clé valide.
| Préfixe | Mode | Usage |
|---|---|---|
sk_dev_ |
Développeur | Votre clé, et la seule. Elle sert à gérer vos clients : les créer, les raccorder, lire leurs compteurs. Elle n'émet aucune facture, et elle ne porte aucun environnement. Les comptes ouverts avant septembre 2026 gardent leur clé sk_test_, toujours valide. |
ck_test_ |
Test | Clé d'un de vos clients. Signature réelle sur l'environnement sandbox, dépôt TTN de test, aucun crédit consommé. |
ck_live_ |
Production | Clé du même client, en production. Signature XAdES réelle, dépôt TTN effectif, 1 crédit déduit par signature. |
Comptes et clients
Un compte développeur peut gérer plusieurs clients (une intégration par entreprise finale). Vous créez et configurez vos clients depuis votre espace développeur, section « Mes clients ».
| Notion | Description |
|---|---|
| Clés par client | Chaque client possède sa propre paire de clés ck_test_ / ck_live_ (clé de client), distinctes de votre clé de développeur sk_dev_ qui sert à la gestion. Une requête d'émission est toujours rattachée au client dont vous utilisez la clé. La clé développeur ne peut pas émettre de factures, que vous ayez des clients rattachés ou non : /einvoices/prepare, /einvoices/process et /einvoices/{id}/resubmit-ttn répondent 403 DEVELOPER_KEY_NOT_ALLOWED. Utilisez la clé du client concerné. Pour émettre au nom de votre propre société, créez-vous un client (POST /api/v1/clients) et utilisez sa clé : votre matricule y est accepté même s'il est aussi celui de votre compte. |
| Portefeuille partagé | Les crédits sont achetés au niveau du compte développeur et partagés entre tous vos clients. |
| Plafond par client | Vous pouvez fixer un plafond de crédits par client. Le client se bloque à son plafond sans affecter les autres ni le reste du portefeuille. Vous suivez la consommation par client. |
| Canal de dépôt | Chaque client dépose à TTN par SOAP (synchrone) ou SFTP (asynchrone), selon la configuration de son compte TTN. En SFTP, la facture passe par le statut submitted_ttn, et vous lisez la validation par GET /einvoices/{id}. |
| Méthode de signature | Configurée par client : cachet serveur (automatique), clé USB (signature locale) ou signature à distance. Sans incidence sur le format des requêtes. |
seller.tax_id est donc facultatif : si vous l'omettez, nous posons celui du compte. Si vous l'envoyez, nous comparons uniquement les 8 premiers caractères, ceux qui identifient le contribuable. 1234567M et 1234567MAM000 sont donc équivalents, et la forme que vous envoyez n'a pas d'importance : c'est la forme complète du compte qui part dans le document. En cas de contribuable réellement différent, la requête est refusée en SELLER_MISMATCH avant toute signature, donc sans crédit consommé.- Même contenu, facture déjà produite : nous vous rendons le document existant, à l'identique, avec
"replayed": true. Aucun crédit n'est consommé et rien n'est redéposé chez TTN. - Contenu différent sur un numéro déjà accepté par TTN : refus en
INVOICE_NUMBER_ALREADY_ISSUED. Un numéro déposé ne peut pas porter deux documents. - Contenu différent après un refus de TTN : la signature repart normalement, et consomme un crédit. Le document précédent est nul, le numéro vous appartient toujours.
- Même contenu, signature en attente (DigiGo, clé USB, E-Houwiya) : nous vous rendons le lien déjà ouvert, avec
"resumed": trueet la mêmesigning_url. Vous avez perdu le lien ? Renvoyez simplement la même facture àPOST /einvoices/prepare, ou lisezGET /einvoices/{signing_id}, qui portesigning_urltant que la signature attend. Aucune seconde transaction n'est ouverte. - Contenu différent pendant qu'une signature attend : refus en
INVOICE_IN_PROGRESS.error.detailsporte lesigning_id, lasigning_urlde la signature ouverte pour la terminer, etcan_prepare_again_at: sans signature d'ici là, la tentative est abandonnée à cette heure (30 minutes après son ouverture) et le numéro se prépare de nouveau. Pour ne pas attendre, annulez-la :POST /einvoices/{signing_id}/cancel.
return_url, un identifiant de corrélation) ne comptent pas dans la comparaison : les changer ne fait pas de votre facture un document différent. Et l'ordre des lignes compte, lui, parce qu'il change le document déposé.
Le sandbox suit exactement la même règle que la production, et les deux environnements sont séparés : une facture d'essai ne bloque jamais un numéro réel.Test vs Production
Passer un client en production ne nécessite aucun changement de code : dans son ERP, remplacez simplement sa clé ck_test_xxx par sa clé ck_live_xxx. C'est le préfixe qui décide de l'environnement, il n'y a aucun réglage à changer ailleurs.
Vos clients sont indépendants les uns des autres : l'un peut être en production pendant qu'un autre reste en test. Il n'existe aucun interrupteur global, et votre clé développeur ne change jamais.
signing_url de /einvoices/prepare pointe vers la vraie page de signature (sandbox), où le signataire signe avec un certificat de test. Le compte de signature sandbox du client doit donc être configuré.| Critère | Test | Production |
|---|---|---|
| Signature | XAdES réelle (environnement sandbox) | XAdES réelle (production) |
| Dépôt TTN | Plateforme TTN de test | Plateforme TTN de production |
| Crédits consommés | Aucun | 1 par signature |
| Cachet/Certificat | Requis (sandbox) | Requis (production) |
Banc d'essai : intégrer avant d'avoir les accès TTN
ck_test_) signe et dépose réellement, sur les environnements de test du service de signature et de TTN : il faut pour cela un compte El Fatoora et un certificat. Le banc d'essai, lui, simule la signature et TTN. Il sert à une seule chose : vous permettre d'écrire et de mettre au point votre intégration pendant que votre client attend ses accès TTN et son certificat, au lieu d'attendre avec lui. Rien n'est signé, rien n'est déposé, et aucun document produit n'a de valeur légale.Obtenir un compte El Fatoora et un certificat prend souvent plusieurs semaines. Sans banc d'essai, aucun appel n'aboutit avant leur arrivée : vous ne pouvez ni tester vos écrans, ni lire nos réponses, ni préparer la gestion des erreurs. Avec le banc d'essai, vous livrez une intégration déjà rodée, et le premier appel réel réussit du premier coup.
| Critère | Banc d'essai | Mode test (ck_test_) | Production (ck_live_) |
|---|---|---|---|
| À quoi il sert | Écrire et mettre au point votre intégration | Recetter avec la vraie chaîne, sans valeur légale | Émettre les factures du client |
| Compte El Fatoora et certificat | Non requis | Requis (environnement de test) | Requis (production) |
| Signature | Simulée | Réelle (environnement de test) | Réelle |
| Dépôt TTN | Simulé, aucun envoi | Plateforme TTN de test | Plateforme TTN de production |
| Référence TTN | SIMULATION-AAAAMMJJ-XXXXXXXX | Référence TTN de test | Référence TTN officielle |
| Crédits consommés | Aucun | Aucun | 1 par signature |
| Valeur légale | Aucune | Aucune | Oui |
L'obtenir
Le banc d'essai s'ouvre sur demande, client par client : écrivez au support en indiquant le client concerné. Il s'applique uniquement aux appels faits avec la clé ck_test_ de ce client. Une clé ck_live_ n'est jamais simulée, quel que soit le réglage : une facture de production ne peut pas sortir d'une simulation.
Ce qui change dans votre code : rien
Mêmes adresses, mêmes corps de requête, mêmes champs de réponse, mêmes codes d'erreur : POST /einvoices/process, POST /einvoices/prepare, GET /einvoices/{signing_id}, POST /einvoices/{signing_id}/resubmit-ttn et POST /einvoices/{signing_id}/cancel. Le jour où les accès de votre client arrivent, nous fermons le banc d'essai : sa clé ck_test_ repart vers le vrai mode test, puis vous passez à sa clé ck_live_. Aucune ligne de votre code ne change.
Pour savoir qu'une réponse vient du banc d'essai, deux marques s'ajoutent, qu'un code existant ignore sans rien casser :
- le champ
"simulation": truedansdata; - l'en-tête HTTP
X-eFactureTN-Simulation: 1.
Ne faites dépendre aucune logique métier de ces marques : elles servent à afficher un bandeau « simulation » dans votre interface, pas à suivre un autre chemin. Sinon, votre code de production n'aurait jamais été éprouvé.
Ce que vous recevez
- Le même TEIF que la production : il sort de notre générateur réel. Une erreur dans votre corps de requête se découvre donc au banc d'essai, pas en production.
xml_base64: le TEIF avec un bloc de signatureId="SigFrs"construit comme le vrai, mais dont la valeur est le motSIMULATION. Aucune vérification cryptographique ne l'acceptera.pdf_base64: le PDF de la production, barré sur chaque page de la mention « SIMULATION - SANS VALEUR LÉGALE ».- Le cachet arrive plus tard, comme chez TTN. Le dépôt ne rend que la référence. Environ 20 secondes après,
GET /einvoices/{signing_id}rendttn.reference_confirmee: true,ttn.qr_png_base64(QR marqué SIMULATION) etxml_validettn_base64(le document avecRefTtnValpuis la signatureSigTTN, dans l'ordre de TTN). Si votre code lit le QR dans la réponse du dépôt, il ne le trouvera pas au banc d'essai, et il ne le trouverait pas non plus en production : c'est voulu.
La page de signature (DigiGo, clé USB, E-Houwiya)
POST /einvoices/prepare rend une signing_url vers une page de signature simulée. Redirigez-y votre utilisateur comme vous le ferez en production :
- le code PIN est toujours
0000, et aucun SMS n'est envoyé ; - un autre code affiche « Code PIN incorrect » ; au bout de 3 codes erronés, la signature est bloquée ;
- le bouton « Refuser de signer » simule le refus du signataire : le statut passe à
rejectedavec le codeSIGNATURE_REFUSED; - après la signature ou le refus, l'utilisateur revient sur votre
return_url, avecefacture_invoiceetsigning_id, comme en production. Vérifiez ensuite le statut parGET /einvoices/{signing_id}: la redirection ramène, elle n'annonce rien ; - le lien sert une seule fois et expire au bout de 30 minutes.
Éprouver les erreurs
Les erreurs de votre requête sont refusées comme en production, avec les mêmes codes : un corps invalide rend 422 VALIDATION_FAILED, un matricule mal formé est refusé comme TTN le refuserait. Utilisez aussi POST /einvoices/validate, qui est le même au banc d'essai et en production.
Les pannes que vous ne savez pas provoquer se demandent par l'en-tête X-Simulation-Scenario, sur /process, /prepare ou /resubmit-ttn. Il ne vaut que pour l'appel qui le porte : un renvoi sans l'en-tête suit le parcours normal, ce qui vous permet de dérouler le cas « TTN était indisponible, je renvoie ».
curl -X POST https://efacturetn.com/api/v1/einvoices/process \
-H "X-Api-Key: ck_test_xxxxxxxxxxxx" \
-H "X-Simulation-Scenario: ttn_indisponible" \
-H "Content-Type: application/json" \
-d @facture.json
| Valeur | Ce qui se passe | Réponse |
|---|---|---|
ttn_indisponible | TTN ne répond pas. Le document n'est pas en cause. | signed_ttn_rejected, next_action: resubmit : renvoyez-le par /resubmit-ttn. |
ttn_compte_desactive | TTN refuse : le compte El Fatoora du client est désactivé (SERV01). | signed_ttn_rejected, next_action: resubmit. |
ttn_rejet_contrl05 | TTN refuse : le matricule d'authentification n'est pas celui de l'émetteur (CONTRL05). Le document est en cause. | signed_ttn_rejected, next_action: resign : corrigez puis émettez à nouveau. |
ttn_rejet_contrl02 | TTN refuse : bloc de transformation de la signature non conforme (CONTRL02). | signed_ttn_rejected, next_action: resign. |
signature_echouee | La signature échoue : rien n'est signé ni déposé. | 503 SIGNING_FAILED sur /process. Sur la page de signature, le statut passe à rejected. |
Une valeur inconnue est refusée en 422 INVALID_SIMULATION_SCENARIO, avec la liste des valeurs acceptées : la taire vous ferait croire que vous éprouvez un cas alors que vous suivez le parcours normal. Sur une clé qui n'est pas au banc d'essai, l'en-tête est ignoré.
Limites
- Aucune valeur légale. Ne transmettez jamais un PDF ou un XML du banc d'essai à un client final : ce n'est pas une facture.
- 300 factures par période de 24 heures et par client, au-delà :
429 SANDBOX_DAILY_LIMIT. - Envoyer deux fois le même numéro crée deux factures au banc d'essai, alors qu'en production le second envoi vous rend la première (voir Renvoyer la même facture). Éprouvez ce cas en mode test ou en production.
- Les documents du banc d'essai ne se lisent qu'avec la clé
ck_test_du client, ou avec votre clé développeur.
Modes de signature
Quatre modes de signature selon votre cas d'usage : Entreprise ID (cachet serveur SEAL), DigiGo (PIN + OTP), clé USB (signature locale) et E-Houwiya (Mobile ID). Vous pouvez basculer à la volée via le champ method (seal, digigo, usb ou mobileid) dans la requête.
| Entreprise ID (SEAL) | DigiGo | Clé USB | E-Houwiya | |
|---|---|---|---|---|
| Synchrone ? | Oui (~5s) | Non (signature manuelle) | Non (signature locale) | Non (smartphone) |
| Interaction | Aucune | PIN + OTP utilisateur | Clé USB sur le poste du signataire | Validation sur le smartphone du signataire |
Valeur method | seal | digigo | usb | mobileid |
| Endpoint (POST) | /einvoices/ | /einvoices/ | /einvoices/ | /einvoices/ |
| Cas d'usage | ERP back-office, volumes élevés | Signature personnelle, petits volumes | Vous détenez déjà votre clé de certification | Aucun matériel : le smartphone suffit |
| Résultat | Direct dans la réponse | Polling GET /einvoices/ | Polling GET /einvoices/ | Polling GET /einvoices/ |
E-Houwiya (Mobile ID) : le signataire valide depuis son smartphone avec son identité numérique mobile. L'option s'active chez le service de signature pour chaque compte : demandez-la nous avant le premier essai, sans quoi la page de signature ne la propose pas.
Gestion des erreurs
L'API utilise les codes HTTP standards et retourne un payload JSON normalisé. Toutes les erreurs ont la même forme :
| Code | HTTP | Description |
|---|---|---|
AUTH_INVALID_KEY | 401 | Clé API invalide, inactive ou non autorisée |
VALIDATION_FAILED | 422 | Champs requis manquants : voir error.details |
INVALID_JSON | 400 | Corps de requête non JSON valide |
INSUFFICIENT_CREDITS | 402 | Portefeuille épuisé ou plafond du client atteint (live uniquement) |
SEAL_NOT_CONFIGURED | 412 | Cachet électronique SEAL non provisionné, ou passphrase du cachet manquante, pour ce client sur l'environnement de la clé (sandbox ou production). Renseignez seal_passphrase_test / seal_passphrase_prod via l'API ou le portail. |
METHOD_REQUIRES_PREPARE | 422 | method vaut digigo, usb ou mobileid (ou c'est le mode du client sans cachet prêt) sur POST /einvoices/process, qui ne signe que par cachet serveur. Envoyez la facture à POST /einvoices/prepare : elle rend une signing_url. |
SIGNER_NOT_CONFIGURED | 412 | Le compte de signature de ce client n'est pas encore créé dans l'environnement de la clé (DigiGo, clé USB, E-Houwiya). Créez-le depuis le portail développeur, ou par PATCH /api/v1/clients/{id} avec "provision": "sandbox" ou "production" |
SIGNER_EMAIL_MISSING | 412 | Signature à distance (digigo, mobileid) sans signer_email renseigné sur le client. Renseignez-le (PATCH /api/v1/clients/{id} ou portail) avec l'adresse du certificat du signataire |
DEVELOPER_KEY_NOT_ALLOWED | 403 | Clé développeur (sk_) utilisée sur /einvoices/prepare, /process, /resubmit-ttn ou /cancel : l'émission passe obligatoirement par la clé du client concerné (ck_), y compris pour votre propre société |
SELLER_MISMATCH | 422 | Le seller.tax_id envoyé désigne un autre contribuable que le titulaire de la clé. Refusé avant signature, donc sans consommer de crédit : une facture est signée sous le matricule du compte, et TTN refuserait un document dont l'émetteur ne correspond pas au signataire. Émettez avec la clé du client concerné, ou corrigez le matricule |
INVOICE_NUMBER_ALREADY_ISSUED | 409 | Ce numéro de facture porte déjà un document accepté par TTN, et le contenu que vous envoyez est différent. Un numéro déposé chez TTN ne peut pas porter deux documents : émettez un avoir, ou utilisez un nouveau numéro. Si vous vouliez seulement redéposer le document existant, appelez POST /einvoices/{id}/resubmit-ttn, qui ne coûte aucun crédit |
ALREADY_SIGNED / CANCEL_NOT_SUPPORTED / SIGNATURE_ALREADY_CLOSED | 409 | Réponses de POST /einvoices/{signing_id}/cancel : voir Annuler une signature |
CANCEL_UNCONFIRMED | 502 | Annulation non confirmée par le service de signature : rien n'est annulé ni débité, réessayez |
INVOICE_IN_PROGRESS | 409 | Une autre requête signe ce numéro en ce moment, ou une signature à la main est ouverte et pas encore terminée. Le signing_id concerné est rendu dans error.details : interrogez-le avec GET /einvoices/{id} plutôt que de réessayer, sinon vous paieriez deux fois la même facture. Pour une signature à la main, error.details.signing_url donne le lien ouvert, pour la terminer. Renvoyer la même facture à /einvoices/prepare ne produit pas cette erreur : le lien vous est rendu ("resumed": true) |
PERMISSION_DENIED | 403 | Le signing_id consulté appartient à un autre compte. Seuls le client propriétaire et son développeur parent peuvent le consulter |
WRONG_ENVIRONMENT | 403 | Chaque clé ne lit que son propre environnement. Le document vous appartient, mais il a été émis dans l'autre environnement : une clé ck_test_ ne consulte que des documents de test, une clé ck_live_ que des documents de production. Le message indique quelle clé l'ouvre. Votre clé développeur sk_dev_, elle, voit les deux |
NOT_SIGNED | 409 | Renvoi TTN demandé sur une facture sans document signé : rien à renvoyer |
AWAITING_TTN | 409 | Renvoi TTN demandé alors que TTN a déjà accepté le dépôt : la confirmation est relevée toutes les cinq minutes, renvoyer ferait un doublon |
SIGNATURE_EXPIRED | - | Porté par une facture rejected : la signature n'a pas été terminée dans les 7 jours. Aucun crédit débité. Arrêtez d'interroger ce signing_id et préparez à nouveau la facture. |
ALREADY_VALIDATED | 409 | Renvoi TTN demandé sur une facture déjà confirmée par TTN : le document est définitif |
DEPOSIT_DELEGATED | 409 | Le dépôt de ce client est assuré par le service de signature : il ne se rejoue pas par l'API |
RESIGN_REQUIRED | 409 | Renvoi TTN demandé alors que TTN a refusé le document lui-même (next_action = resign) : corrigez la facture et émettez-la à nouveau |
DOCUMENTS_PURGED | 409 | Renvoi TTN demandé sur une facture dont les documents ont été effacés au terme des 90 jours de conservation : émettez-la à nouveau |
TTN_DEPOSIT_FAILED | 502 | TTN a refusé ou n'a pas répondu au dépôt : le message de TTN est dans error.message, le document signé est conservé et se renvoie avec POST /einvoices/{id}/resubmit-ttn |
TTN_DEPOSIT_FAILED | - | Présent dans ttn.error de l'objet einvoice : la facture est signée mais le dépôt TTN a échoué (ex. identifiants TTN invalides). Corrigez la configuration TTN du client puis ré-émettez la facture |
TTN_CREDENTIALS_MISSING | 412 | Le canal de dépôt choisi (soap ou sftp) place le dépôt de notre côté, mais les identifiants TTN du client ne sont pas renseignés. Refusé avant signature : sans eux, la facture serait signée et facturée, puis n'irait nulle part. Renseignez ttn.sandbox.login / ttn.sandbox.password (ou ttn.prod.*). Si ce client n'a pas de compte TTN à lui, utilisez le canal delegated : le service de signature dépose alors sous son propre compte. Le canal delegated n'est jamais concerné par cette erreur. |
SIGNING_TOKEN_EXPIRED | 412 | Le jeton du service de signature a expiré et son renouvellement automatique a échoué. Réessayer redonnera la même erreur : reprovisionnez le compte de signature du client (PATCH /api/v1/clients/{id} avec "provision": "sandbox" ou "production"), puis ré-émettez. En mode test, error.details.refresh_error donne la raison de l'échec du renouvellement |
INVALID_METHOD | 422 | Méthode de signature inconnue : method vaut seal, digigo, usb ou mobileid |
SIGNER_UNAVAILABLE | 400 | Ce mode n'est pas proposé par le service de signature du client (par exemple E-Houwiya) : choisissez-en un autre ou contactez-nous |
SIGNER_CIRCUIT_OPEN | 503 | Service de signature en panne répétée : circuit breaker activé. Réessayer dans 30-60 sec |
RATE_LIMIT | 429 | Limite de requêtes par minute dépassée |
SIGNING_FAILED | 503 | Échec technique du service de signature (timeout, 5xx provider, etc.) |
USER_CANCELLED | - | (DigiGo) Le client final a annulé la signature. Présent dans error de l'objet einvoice au statut rejected |
Debug en mode test (clés ck_test_)
En mode test uniquement, les erreurs SIGNING_FAILED retournent un champ error.details.debug
contenant le message technique sous-jacent, et GET /einvoices/{id} inclut un bloc debug
(état de la signature, erreur de vérification éventuelle) tant que la facture est en cours.
Ces champs ne sont jamais présents en mode production (clés live), pour éviter toute fuite d'information.
{
"success": false,
"data": null,
"error": {
"code": "SIGNING_FAILED",
"message": "La signature électronique a échoué. Veuillez réessayer.",
"details": {
"debug": "HTTP 401 Unauthorized"
}
}
}
Tableau de résolution des erreurs SIGNING_FAILED
Si vous rencontrez un SIGNING_FAILED, consultez ci-dessous le contenu de error.details.debug pour identifier la cause :
Message dans debug | Cause probable | Action recommandée |
|---|---|---|
HTTP 401ou Unauthorized |
Le jeton de signature configuré pour votre compte est invalide ou expiré. | Contactez le support pour faire renouveler votre jeton de signature. |
HTTP 402ou Payment Required |
Compte de signature sans crédit ou plan expiré. | Contactez le support pour réactiver le service. |
HTTP 403ou Forbidden |
L'organisation associée à votre compte n'est pas autorisée pour cet endpoint. | Vérifier la method envoyée. Contacter le support si problème persistant. |
HTTP 500 ou HTTP 502Bad Gateway |
Le service de signature est temporairement indisponible ou en surcharge. | Réessayer dans 1 à 2 minutes. Implémentez un retry avec backoff exponentiel côté client. |
HTTP 504Gateway Timeout |
Le service de signature met trop de temps à répondre. | Réessayer immédiatement. Si récurrent, contacter le support. |
Connection timed outou Could not resolve host |
Problème réseau côté serveur (souvent une route bloquée par un pare-feu sortant). | Réessayer dans quelques minutes. Si persistant, alerter le support. |
Idle timeout reachedou timed out after |
Le service de signature met plus de 30 secondes à répondre (charge serveur). | Réessayer après 1 minute. Le mode seal est plus rapide que digigo pour les volumes élevés. |
Email signataire non configuré |
L'adresse du signataire n'est pas renseignée sur la fiche du client. | Renseignez signer_email (PATCH /api/v1/clients/{id} ou portail) avec l'adresse du certificat DigiGo ou E-Houwiya du signataire. |
Passphrase manquanteou required for seal |
Le mode seal exige une passphrase configurée pour l'environnement du client (sandbox ou production). |
Renseignez seal_passphrase_test / seal_passphrase_prod via l'API (PATCH /clients/{id}) ou le portail, ou basculez vers method: "digigo". |
Invoice not foundou Locked invoice 50010 |
Tentative de re-signer une facture déjà en cours de signature. | Récupérez l'état actuel via GET /einvoices/{id}. Si elle attend encore, renvoyez la même facture à /einvoices/prepare pour récupérer son lien, ou annulez-la (POST /einvoices/{id}/cancel). Si elle est rejected, émettez-la de nouveau : le même numéro est accepté. |
Invalid TEIF formatou erreur de validation XML |
Les données envoyées ne respectent pas le format TEIF v1.8.8. | Vérifiez vos données : matricule fiscal à 13 caractères pour l’émetteur (7 chiffres + lettre de contrôle + catégorie + nature + 000, ex. 1234567MAM000), forme courte à 8 caractères tolérée pour l’acheteur seulement, TVA dans [0, 7, 13, 19], champs requis. |
aucune transaction UUID retournée |
Réponse anormale du service de signature. | Réessayer. Si persistant, contacter le support avec votre invoice_number. |
| Autre message non listé | Erreur non répertoriée - possiblement nouvelle. | Contactez le support avec : debug complet, invoice_number, horodatage, et votre Client ID API. |
- Implémentez un retry avec backoff exponentiel pour les erreurs 5xx et timeouts (3 tentatives max : 1s, 3s, 10s)
- Ne retentez jamais automatiquement les erreurs 4xx (sauf 429 RATE_LIMIT, après le délai indiqué)
- Conservez l'en-tête
X-Request-Idde chaque réponse : citez-le au support, il retrouve votre appel exact
Valider un payload
Valide la structure du payload sans signer ni déposer chez TTN. Idéal pour debugger votre intégration. Aucun crédit consommé même en mode live.
Corps de la requête
Le même corps pour /einvoices/validate, /einvoices/process et /einvoices/prepare. Ci-dessous l'essentiel ; tous les champs facultatifs (remises, FODEC, devise, retenue, mentions du PDF…) sont décrits dans Objet Invoice, et un exemple avec tous les champs dans Exemple complet.
| Champ | Type | Description |
|---|---|---|
| invoice_numberrequis | string | Numéro de facture côté ERP. 30 caractères au plus, lettres, chiffres, ., - et _ uniquement, ni espace ni / : voir les règles de forme. Le format par défaut d'Odoo, INV/2026/09/0008, est refusé par TTN. |
| daterequis | date YYYY-MM-DD | Date d'émission, jamais dans le futur. |
| sellerrequis | object | name, address. tax_id facultatif : votre clé le porte déjà, et seuls ses 8 premiers caractères sont comparés si vous l'envoyez. |
| buyerrequis | object | name, et tax_id (matricule tunisien) ou id_number (CIN, numéro d'entreprise étranger, avec country). |
| itemsrequis | array | Au moins une ligne : description, quantity, unit_price (HT), vat_rate (0, 7, 13 ou 19). Voir Objet Invoice. |
| typeoptionnel | enum | facture par défaut. Pour un avoir : credit_note et original_invoice_number, voir Avoirs et notes de débit. |
| pdf_base64optionnel | string | Votre PDF de facture encodé en base64. Sans lui, nous le générons. Voir PDF de la facture. |
Réponse
La réponse porte deux listes. errors : ce qui empêcherait l'émission, et rend valid faux. warnings : des alertes non bloquantes, qui ne changent ni valid ni le comportement de /einvoices/process et /einvoices/prepare. Affichez-les à l'utilisateur, par exemple dans votre bouton « Vérifier la conformité ».
{
"success": true,
"data": {
"valid": true,
"errors": [],
"warnings": [
"suspension.authorization_number : le numéro de l'attestation d'achat en suspension de TVA manque. C'est la pièce qui justifie la suspension en cas de contrôle."
]
}
}
Avertissements rendus aujourd'hui, pour une vente en suspension de TVA :
| Situation | Pourquoi on vous prévient |
|---|---|
| Numéro d'attestation absent | C'est la pièce qui justifie la suspension en cas de contrôle. |
| Numéro de bon de commande absent | La mention du bon de commande accompagne la vente en suspension. |
Date illisible (date_from, date_to, order_date) | Une date hors du format AAAA-MM-JJ n'est ni transmise à TTN ni imprimée. |
| Période de validité inversée | La date de début est postérieure à la date de fin. |
| Facture datée hors de la période de validité | L'attestation ne couvre pas la date de la facture. |
| Aucune TVA à suspendre | Toutes les lignes sont à 0 % ou exonérées : envoyez le taux réel (vat_rate: 19), pas exonerated: true. |
Signer + déposer (mode SEAL)
Synchrone (~5 secondes). Signature automatique via cachet électronique serveur, dépôt immédiat chez TTN. La réponse contient directement le XML signé (base64), le PDF avec QR code (base64) et la référence TTN. Clé requise : celle du client concerné (ck_) - une clé développeur reçoit 403 DEVELOPER_KEY_NOT_ALLOWED.
Corps de la requête
Le même corps pour /einvoices/validate, /einvoices/process et /einvoices/prepare. Ci-dessous l'essentiel ; tous les champs facultatifs (remises, FODEC, devise, retenue, mentions du PDF…) sont décrits dans Objet Invoice, et un exemple avec tous les champs dans Exemple complet.
| Champ | Type | Description |
|---|---|---|
| invoice_numberrequis | string | Numéro de facture côté ERP. 30 caractères au plus, lettres, chiffres, ., - et _ uniquement, ni espace ni / : voir les règles de forme. Le format par défaut d'Odoo, INV/2026/09/0008, est refusé par TTN. |
| daterequis | date YYYY-MM-DD | Date d'émission, jamais dans le futur. |
| sellerrequis | object | name, address. tax_id facultatif : votre clé le porte déjà, et seuls ses 8 premiers caractères sont comparés si vous l'envoyez. |
| buyerrequis | object | name, et tax_id (matricule tunisien) ou id_number (CIN, numéro d'entreprise étranger, avec country). |
| itemsrequis | array | Au moins une ligne : description, quantity, unit_price (HT), vat_rate (0, 7, 13 ou 19). Voir Objet Invoice. |
| typeoptionnel | enum | facture par défaut. Pour un avoir : credit_note et original_invoice_number, voir Avoirs et notes de débit. |
| pdf_base64optionnel | string | Votre PDF de facture encodé en base64. Sans lui, nous le générons. Voir PDF de la facture. |
Codes de réponse
| HTTP | Signification |
|---|---|
| 200 | Facture signée et déposée. Réponse complète. |
| 412 | Cachet non provisionné, ou code PIN du cachet manquant, pour l'environnement de la clé. Voir SEAL_NOT_CONFIGURED. |
| 422 | METHOD_REQUIRES_PREPARE : DigiGo, clé USB ou E-Houwiya demandés ici. Utilisez POST /einvoices/prepare. |
| 402 | Crédits insuffisants. |
| 422 | Validation échouée. Voir error.details. |
Préparer une signature (DigiGo, clé USB, E-Houwiya)
C'est le point d'entrée de DigiGo, de la clé USB et d'E-Houwiya. POST /einvoices/process ne signe que par cachet serveur : y envoyer "method": "digigo" ou "usb" rend 422 METHOD_REQUIRES_PREPARE.
Asynchrone. Génère le TEIF, crée une transaction de signature et retourne une signing_url vers laquelle vous redirigez le client final (signature à distance ou clé USB locale). Celui-ci signe, puis est redirigé vers votre return_url. Clé requise : celle du client concerné (ck_) - une clé développeur reçoit 403 DEVELOPER_KEY_NOT_ALLOWED. Le signing_id retourné (format sg_...) sert au suivi via GET /einvoices/{signing_id}.
Récupérez le résultat final via :
- Polling :
GET /einvoices/{signing_id}jusqu'àstatus: validated
Corps de la requête
Le même corps pour /einvoices/validate, /einvoices/process et /einvoices/prepare. Ci-dessous l'essentiel ; tous les champs facultatifs (remises, FODEC, devise, retenue, mentions du PDF…) sont décrits dans Objet Invoice, et un exemple avec tous les champs dans Exemple complet.
| Champ | Type | Description |
|---|---|---|
| invoice_numberrequis | string | Numéro de facture côté ERP. 30 caractères au plus, lettres, chiffres, ., - et _ uniquement, ni espace ni / : voir les règles de forme. Le format par défaut d'Odoo, INV/2026/09/0008, est refusé par TTN. |
| daterequis | date YYYY-MM-DD | Date d'émission, jamais dans le futur. |
| sellerrequis | object | name, address. tax_id facultatif : votre clé le porte déjà, et seuls ses 8 premiers caractères sont comparés si vous l'envoyez. |
| buyerrequis | object | name, et tax_id (matricule tunisien) ou id_number (CIN, numéro d'entreprise étranger, avec country). |
| itemsrequis | array | Au moins une ligne : description, quantity, unit_price (HT), vat_rate (0, 7, 13 ou 19). Voir Objet Invoice. |
| typeoptionnel | enum | facture par défaut. Pour un avoir : credit_note et original_invoice_number, voir Avoirs et notes de débit. |
| pdf_base64optionnel | string | Votre PDF de facture encodé en base64. Sans lui, nous le générons. Voir PDF de la facture. |
En plus, pour une signature à la main (DigiGo, clé USB, E-Houwiya)
| Champ | Type | Description |
|---|---|---|
| methodoptionnel | enum | seal, digigo, usb, mobileid. Défaut : le mode configuré sur le client. seal est aussi accepté ici et signe aussitôt par cachet serveur, sans signing_url. mobileid = E-Houwiya (Mobile ID) : l'option s'active chez le service de signature pour chaque compte : demandez-la nous avant le premier essai, sans quoi la page de signature ne la propose pas. |
| return_urlrequis (digigo, usb, mobileid) | URL HTTPS | URL de votre ERP où le client est redirigé après signature. Obligatoire dès que la signature passe par un navigateur, c'est-à-dire pour digigo, usb et mobileid : sans elle, votre utilisateur termine sa signature et n'a aucun endroit où revenir. Son absence est refusée en 422 VALIDATION_FAILED. Seul seal s'en passe, personne n'étant devant l'écran. Doit être en HTTPS (les URL http:// sont rejetées). Vos propres paramètres de query sont préservés. |
return_url sert uniquement à ramener l'utilisateur dans votre interface. C'est une redirection navigateur : ne considérez jamais une facture comme signée parce que le navigateur est revenu sur cette URL - elle pourrait être appelée directement.
Pour confirmer le statut réel, utilisez toujours un canal authentifié serveur-à-serveur :
- Polling :
GET /einvoices/{signing_id}avec votre clé API (headerX-Api-Key).
?efacture_invoice={invoice_number}&signing_id={signing_id} à votre return_url. Le signing_id (format sg_...) est celui à utiliser avec GET /einvoices/{signing_id}. Utilisez ces paramètres seulement pour retrouver la facture concernée, jamais comme preuve de signature.
signing_url
C'est une adresse opaque : redirigez-y votre utilisateur sans l'analyser. Selon le service de signature qui traite le compte, elle mène soit directement à la page de signature, soit à une page d'attente hébergée sur efacturetn.com qui ouvre cette page puis ramène l'utilisateur sur votre return_url dès que la signature est acquise. Certains services de signature n'honorent aucune adresse de retour, et nous comblons cette absence à votre place.
- Le contrat ne change pas : vous recevez une
signing_url, votre utilisateur revient surreturn_url. Changer de service de signature ne demande aucune modification de votre côté. - Ne codez donc aucun domaine en dur, n'ouvrez pas cette page dans une iframe, et ne rejouez pas une
signing_urlexpirée : annulez la signature (POST /einvoices/{id}/cancel) puis redemandezPOST /einvoices/prepare, ou attendezcan_prepare_again_at. Tant qu'elle attend, la renvoyer à/preparevous rend le même lien, sans second crédit (voir Renvoyer la même facture). - Si votre utilisateur ferme la page, rien n'est perdu : la signature est récupérée de notre côté en tâche de fond, la facture poursuit son dépôt TTN comme si le navigateur était revenu.
GET /einvoices/{signing_id}reste la source de vérité. Le retour navigateur est un confort, jamais le mécanisme.
Récupérer une e-facture
Retourne le statut et les fichiers d'une e-facture. Utilisé principalement pour le polling après POST /einvoices/prepare. Seuls le client propriétaire de la facture et son développeur parent peuvent la consulter : toute autre clé reçoit 403 PERMISSION_DENIED.
Chaque clé ne lit que son propre environnement. Un document émis en test se consulte avec ck_test_, un document émis en production avec ck_live_ ; l'autre sens répond 403 WRONG_ENVIRONMENT, et il en va de même pour POST /einvoices/{id}/resubmit-ttn. Ce n'est pas une restriction de propriété, le document est bien le vôtre : c'est une séparation des environnements, parce qu'une clé de test se distribue plus largement qu'une clé de production et ne doit pas ouvrir vos documents fiscaux réels. Votre clé développeur sk_dev_ ne désigne aucun environnement et voit les deux, chez tous vos clients.
Cycle de vie
draft → teif_generated → pending_signature → signed → submitted_ttn → validated
↘ rejected
| Statut | Description |
|---|---|
pending_signature | En attente que le client signe sur signing_url |
signed | Signature reçue, dépôt TTN en cours |
submitted_ttn | Déposé, attente validation TTN |
validated | TTN a accepté. xml_base64 et pdf_base64 sont disponibles. Le QR officiel et la référence définitive arrivent quelques minutes plus tard : voir Documents finaux & QR. |
rejected | Annulé ou rejeté : voir les blocs signature et ttn pour savoir quel étage a échoué, et ttn.next_action pour savoir quoi faire (voir ci-dessous) |
Deux étages décrits séparément : signature et ttn
Le champ status est la synthèse globale du cycle. Un rejected peut signifier deux choses très différentes : signature refusée, ou facture signée mais dépôt TTN refusé. La réponse contient donc deux blocs dédiés :
{
"status": "rejected",
"signature": {
"status": "signed", // pending | signed | rejected
"signed_xml_available": true
},
"ttn": {
"status": "rejected", // not_reached | submitted | validated | rejected
"reference": null,
"error": { "code": "TTN_DEPOSIT_FAILED", "message": "Erreur TTN: ..." }
}
}
| Scénario | status | signature.status | ttn.status |
|---|---|---|---|
| En attente du signataire | pending_signature | pending | not_reached |
| Signature refusée / annulée | rejected | rejected | not_reached |
| Signée, dépôt TTN refusé | rejected | signed | rejected + error |
| Signée, déposée (SFTP), réponse TTN en attente | submitted_ttn | signed | submitted |
| Validée | validated | signed | validated + reference |
En cas de dépôt refusé (ttn.status: rejected) : le XML signé reste disponible, corrigez la cause (ex. identifiants TTN du client) puis ré-émettez la facture (nouveau /prepare ou /process). Un rejet est final pour ce signing_id.
status vaut rejected, ou validated avec reference_confirmee: true.
Une signature non terminée en 7 jours passe d'elle-même en rejected (SIGNATURE_EXPIRED).
Le service de signature n'est interrogé qu'une fois toutes les 30 secondes par facture : interroger plus souvent rend le même statut.Après un refus TTN : renvoyer ou re-signer
Quand status = rejected et qu'un document signé existe, l'API dit ce qu'il faut faire, dans ttn.next_action (GET), dans next_action (réponses de /process et /prepare en signed_ttn_rejected). Deux valeurs, pas plus : votre module n'a pas à lire le texte de TTN.
next_action | Ce que ça veut dire | Le bouton à montrer |
|---|---|---|
resubmit | Le document signé n'est pas en cause : TTN indisponible, délai dépassé, compte TTN du client inexistant ou désactivé, certificat non déclaré. Une fois la cause levée, le même fichier repart. | Renvoyer TTN : POST /einvoices/{id}/resubmit-ttn, sans nouvelle signature, sans crédit. |
resign | Le document est en cause : schéma violé (par exemple un numéro mal formé), numéro déjà déposé, émetteur différent du compte. Renvoyer le même fichier répondrait la même chose, et l'API le refuse (409 RESIGN_REQUIRED). | Corriger puis émettre à nouveau : nouvelle facture, nouvelle signature (un crédit en production). Vérifiez d'abord avec /einvoices/validate. |
resubmit. Un renvoi inutile ne coûte rien, TTN redit la même chose et le document est gardé. Une re-signature inutile coûte un crédit et un PIN au signataire. Une signature ne se recolle jamais sur un fichier corrigé : la moindre modification du document invalide la signature, c'est le principe même de la signature électronique.
Renvoyer chez TTN sans re-signer
Pour le bouton « Renvoyer TTN » de votre ERP. Quand le dépôt a échoué parce que TTN était indisponible, ou qu'il a été refusé pour une cause extérieure au document, cet appel redépose le document signé tel qu'il a été signé, sans nouvelle signature et sans consommer de crédit.
| Élément | Valeur |
|---|---|
{id} | Le signing_id rendu par /einvoices/prepare ou /einvoices/process à l'émission, le même que pour GET /einvoices/{id}. Un appel par facture : un client qui a plusieurs documents signés fait un appel par identifiant. |
| Authentification | La clé API du client émetteur, ck_test_ ou ck_live_. Jamais la clé développeur sk_ (403 DEVELOPER_KEY_NOT_ALLOWED). Un identifiant appartenant à un autre compte répond 403 PERMISSION_DENIED. |
| Corps | Vide. |
Quand l'utiliser
| État de la facture | Réponse |
|---|---|
rejected avec error.code = TTN_DEPOSIT_FAILED, TTN_REJECTED ou SFTP_DEPOSIT_FAILED | 200 : le fichier signé est redéposé. Le statut passe à validated (référence provisoire) ou submitted_ttn, puis la confirmation se lit comme d'habitude par GET /einvoices/{id}. |
pending_signature, aucun document signé | 409 NOT_SIGNED : rien à renvoyer, attendez la signature ou refaites un /prepare. |
validated ou submitted_ttn, reference_confirmee = false | 409 AWAITING_TTN : TTN a déjà accepté le dépôt, le renvoyer ferait un doublon. La confirmation est relevée toutes les cinq minutes. |
reference_confirmee = true | 409 ALREADY_VALIDATED : le document est définitif. |
| Client dont le dépôt est assuré par le service de signature | 409 DEPOSIT_DELEGATED : ce dépôt ne se rejoue pas par l'API, contactez le support avec le signing_id. |
rejected parce que TTN a refusé le document (next_action = resign) | 409 RESIGN_REQUIRED : renvoyer le même fichier répondrait la même chose. Corrigez la facture et émettez-la à nouveau. |
| TTN refuse ou ne répond pas | 502 TTN_DEPOSIT_FAILED avec le message de TTN. La facture reste rejected, vous pouvez réessayer plus tard. |
Réponse
{
"success": true,
"data": {
"signing_id": "sg_146c4253ea74656dbedc5b57",
"invoice_number": "FACT-2026-0012",
"status": "validated",
"ttn": { "status": "validated", "reference": "TTN-SOAP-1234567", "reference_confirmee": false },
"ttn_reference": "TTN-SOAP-1234567",
"resigned": false,
"credits_consumed": 0,
"message": "Document signé renvoyé chez TTN, sans nouvelle signature."
},
"error": null
}
/process ou un /prepare pour la même facture. Cela produirait un second document signé, un second crédit consommé, et deux dépôts pour un seul numéro. Le renvoi ne s'affiche que quand status = rejected et qu'un document signé existe (signature.signed_xml_available = true).
Annuler une signature en attente
Arrête une signature ouverte par POST /einvoices/prepare (DigiGo, clé USB, E-Houwiya) et pas encore faite. Clé requise : celle du client concerné, sur l'environnement de la facture. Aucun corps de requête.
Aucun crédit n'est consommé par une annulation, et une facture ne peut pas être débitée deux fois : le crédit n'est pris qu'au moment où une signature réelle revient. Si votre utilisateur signe à l'instant même où vous annulez, nous relisons le statut chez le service de signature avant de répondre : la facture est alors traitée comme signée, avec un seul crédit, et le document vous est conservé.
Réponses
| HTTP | Code | Signification |
|---|---|---|
| 200 | - | "status": "cancelled", "credits_consumed": 0. Rien n'a été signé. Le numéro de facture se prépare de nouveau aussitôt. |
| 409 | ALREADY_SIGNED | Trop tard : la facture était signée. Elle suit son cours normal, un crédit. Lisez-la avec GET /einvoices/{signing_id} ; pour l'annuler comptablement, émettez un avoir. |
| 409 | SIGNATURE_ALREADY_CLOSED | Rien à annuler : la tentative est déjà close (refusée, annulée ou abandonnée). Le numéro est libre. |
| 409 | CANCEL_NOT_SUPPORTED | Le service de signature de ce client ne permet pas l'annulation. Sans signature, la tentative s'abandonne d'elle-même à error.details.can_prepare_again_at (30 minutes après son ouverture). |
| 502 | CANCEL_UNCONFIRMED | L'annulation n'a pas pu être confirmée. Rien n'est tenu pour annulé, rien n'est débité : réessayez dans un instant. |
Avoirs et notes de débit
Un avoir s’envoie par les mêmes endpoints qu’une facture. Rien de séparé : seuls deux champs changent dans le corps.
| Champ | Valeur | Obligatoire |
|---|---|---|
typealias : document_type |
credit_note pour un avoir, debit_note pour une note de débit.Sont aussi acceptés : avoir, creditnote, credit-note, note_debit, debit-note. Absent = facture. |
oui |
original_invoice_numberalias : parent_invoice_number, facture_origine_numero |
Le numéro de la facture annulée ou corrigée. | l’un des deux |
original_ttn_referencealias : facture_origine_ttn |
La référence TTN de la facture d’origine, si vous l’avez. | l’un des deux |
type qui porte le sens du document, et le TEIF applique le signe lui-même. Des lignes négatives sur un credit_note produiraient un document qui s’annule deux fois.{
"type": "credit_note",
"original_invoice_number": "F-2026-0042",
"invoice_number": "AV-2026-0007",
"date": "2026-09-18",
"seller": { "name": "MA SOCIETE", "tax_id": "1234567ABM000", "address": "Rue X, Tunis" },
"buyer": { "name": "MON CLIENT", "tax_id": "7654321ABM000", "address": "Rue Y, Sfax" },
"items": [
{ "description": "Retour marchandise", "quantity": 2, "unit_price": 150.000, "vat_rate": 19 }
],
"stamp_duty": false
}
Timbre fiscal et retenue à la source sur un avoir
Ils ne sont jamais répliqués automatiquement depuis la facture d’origine : l’API ne connaît que le corps que vous envoyez. Si votre avoir doit les porter, envoyez-les explicitement (stamp_duty, champs de retenue). Sinon, laissez-les absents.
Documents finaux & QR code
Une e-facture produit quatre documents, et ils n'arrivent pas tous en même temps. C'est la source de confusion la plus fréquente : ne pas confondre ce que vous avez signé avec ce que TTN a validé.
| Champ | Contenu | Disponible | À archiver |
|---|---|---|---|
xml_base64 |
Le TEIF que vous avez signé et que nous avons déposé. | Dès la signature | non |
xml_validettn_base64 |
Le document final : le même, plus le bloc <RefTtnVal> ajouté par TTN (référence officielle, date de traitement, cachet). |
Après confirmation | oui |
ttn.qr_png_base64 |
Le QR officiel seul, image PNG. | Après confirmation | si vous composez votre propre document |
pdf_base64 |
Notre PDF, QR déjà apposé. | Dès la signature, mis à jour à la confirmation | au choix |
Le QR ne se fabrique pas
Le QR d'une facture El Fatoora est le Cachet Électronique Visible. C'est TTN qui le produit, sous forme d'image PNG, et il est vérifiable par les outils officiels. Un QR reconstruit de votre côté aura la même allure mais échouera à la vérification : l'empreinte et la clé qu'il contient sont calculées par TTN.
Vous n'avez donc rien à calculer. Soit vous utilisez notre pdf_base64, soit vous apposez ttn.qr_png_base64 telle quelle sur votre propre document, sans ré-encodage ni retouche.
Le QR arrive en différé
TTN remplit le bloc <RefTtnVal> après le dépôt : ce bloc est exclu de votre signature, c'est pourquoi TTN peut y écrire sans l'invalider. Nous ré-interrogeons TTN toutes les cinq minutes pour le récupérer.
"ttn": {
"status": "validated",
"reference": "073620200053562920196810312",
"reference_confirmee": true, // false = accusé de dépôt, pas encore la référence définitive
"confirmed_at": "2026-09-01T14:32:07+00:00",
"qr_png_base64": "iVBORw0KGgo..." // null tant que reference_confirmee vaut false
}
reference_confirmee vous dit si vous tenez le document définitif. Tant qu'il vaut false, la référence portée n'est qu'un accusé de dépôt et le QR n'est pas encore l'officiel : n'imprimez pas la facture pour votre client.Comment récupérer la confirmation
Rappelez GET /einvoices/{id} jusqu'à ce que reference_confirmee passe à true. Comptez quelques minutes ; un intervalle de 5 minutes suffit, plus court ne sert à rien.
Deux étapes de dépôt, pas une
| Ce que vous lisez | Ce que cela signifie |
|---|---|
status: submitted_ttn | TTN a accepté le dépôt mais ne nous a rendu aucun identifiant. La facture est déposée, pas encore confirmée. Continuez d'interroger. |
status: validated, reference_confirmee: false | TTN a rendu un identifiant au dépôt, la référence est provisoire. Continuez d'interroger. |
status: validated, reference_confirmee: true | La référence est définitive : allez chercher le QR et le XML validé. |
Combien de temps nous gardons ces documents
90 jours, et pas un de plus. Passé ce délai, les quatre documents et le corps de votre requête sont effacés de nos serveurs, ainsi que les journaux d'appels et de transactions correspondants. Nous conservons la ligne de la facture, c'est-à-dire son numéro, ses dates, son statut et sa référence TTN : un signing_id continue donc de répondre, en indiquant la date d'effacement dans documents_purged_at.
Pourquoi nous effaçons. Deux raisons, et aucune n'est une limitation de service :
- La confidentialité. Les factures de vos clients contiennent leurs montants, leurs partenaires et leurs adresses. Ce sont leurs affaires, pas les nôtres. Garder sans fin ce que nous n'avons plus de raison de garder serait une décision par défaut, et nous préférons ne pas la prendre à leur place.
- La performance. Une base qui ne fait que grossir finit par ralentir tout le monde, y compris vos appels. Effacer ce qui a servi garde nos temps de réponse là où ils sont.
| Conséquence | Ce qu'il faut faire |
|---|---|
GET /einvoices/{id} rend les champs XML et PDF à null, avec documents_purged_at renseigné. | Rien : ce sont vos archives qui font foi. |
POST /einvoices/{id}/resubmit-ttn répond 409 DOCUMENTS_PURGED. | Émettez la facture à nouveau depuis votre ERP. |
xml_validettn_base64 dès que reference_confirmee passe à true, et conservez-le avec le PDF dans votre système : c'est le document qui porte la référence et le cachet de TTN. Si vous ne les archivez pas de votre côté, vous ne les retrouverez pas chez nous après 90 jours. Le délai s'applique à toutes les factures, y compris celles que TTN n'a jamais confirmées. Nos 90 jours sont un filet de sécurité opérationnel, pas un archivage légal.
Si vous préférez que nous gardions vos archives
Archiver dix ans de factures signées, les mettre à l'abri et savoir les retrouver n'est pas le métier d'un intégrateur. Nous proposons de le faire à votre place, en option, pour chaque client que vous passez en production.
| Ce que vous obtenez | Détail |
|---|---|
| Conservation 10 ans | Le document signé, le document validé par TTN et le PDF, conservés chiffrés, au lieu des 90 jours. |
| Accès à tout moment | Une clé dédiée pour récupérer les archives de vos clients quand vous en avez besoin, sans passer par notre support. |
| Tarif | Par client et par an, en plus du raccordement. Le montant figure dans votre portail développeur, à la page des tarifs. |
L'option se commande depuis votre portail développeur, client par client, au moment de la mise en production ou à son renouvellement.
Une copie dans le Google Drive de votre client
Votre client veut garder lui-même un exemplaire de ses factures, dans un espace qui lui appartient. Nous déposons pour lui, dans un compte Google Drive, une archive par e-facture validée, sans que votre intégration n'ait rien à appeler.
| Question | Réponse |
|---|---|
| Que contient chaque archive ? | Un fichier ZIP par e-facture : le document signé, le document validé par TTN, le PDF et un fichier de métadonnées (numéro, dates, référence TTN). |
| Où est-elle rangée ? | Dans un dossier eFactureTN, puis un sous-dossier par client, par année et par mois. |
| Quand part-elle ? | Automatiquement, peu après la validation. En dépôt SOAP, après la confirmation de TTN, pour que l'archive porte le document définitif. |
| Quel compte Google ? | Celui que vous connectez depuis la fiche du client dans votre portail. Un même compte peut servir à plusieurs de vos clients : chacun reçoit son propre dossier. |
| Sous quelle forme ? | Déchiffrée, pour être lisible par son destinataire, et transmise par une connexion sécurisée (HTTPS). Chez nous, les documents restent chiffrés. |
| Et si l'accès Google est retiré ou expire ? | Rien n'est perdu : les archives attendent, vous êtes prévenu par e-mail, et elles repartent seules dès que le compte est reconnecté. |
| Et les factures déjà émises ? | Le portail propose, pour chaque client, le téléchargement des archives d'un mois entier. |
| Tarif | Par client et par an, même si plusieurs clients partagent le même compte Google. Un tarif groupé s'applique quand la copie est prise avec l'archivage 10 ans. Les montants figurent dans votre portail développeur. |
Solde de crédits
Retourne le solde courant de crédits, le total utilisé et le total acheté.
Statut de la plateforme
Retourne l'état global de la plateforme. Aucune authentification requise, aucun crédit consommé. Utilisez-le comme sonde depuis votre ERP ou votre supervision.
curl https://efacturetn.com/api/v1/status
Réponse
{
"success": true,
"data": {
"status": "operational",
"version": "1.0.0",
"teif_version": "1.8.8",
"all_operational": true,
"last_check": "17/07/2026 09:00"
},
"error": null
}
Champs
| Champ | Type | Description |
|---|---|---|
| status | string | État global. Toujours celui du service le plus dégradé : si un seul composant souffre, vous le voyez ici. Valeurs : operational, degraded, partial_outage, major_outage, maintenance. |
| all_operational | bool | Raccourci : true seulement si tous les services sont pleinement opérationnels. |
| last_check | string | Date de la dernière vérification automatique. |
| version | string | Version de l'API. |
| teif_version | string | Version TEIF supportée. |
Disponibilité par service
Détail service par service : statut, latence mesurée et taux de disponibilité. Nécessite une clé API. Aucun crédit consommé, en test comme en live.
Paramètres
| Champ | Type | Description |
|---|---|---|
| daysoptionnel | int | Fenêtre du taux de disponibilité, en jours. Entre 1 et 180. Défaut : 90. |
curl "https://efacturetn.com/api/v1/status/services?days=30" \ -H "X-Api-Key: ck_test_xxxxxxxxxxxx"
Réponse
{
"success": true,
"data": {
"status": "degraded",
"all_operational": false,
"last_check": "17/07/2026 09:00",
"uptime_window_days": 30,
"services": [
{
"slug": "signature",
"name": "Signature électronique",
"description": "Service de signature",
"status": "operational",
"status_label": "Operationnel",
"operational": true,
"latency_ms": 214,
"detail": null,
"uptime_percent": 99.86
},
{
"slug": "ttn_prod",
"name": "TTN production (El Fatoora)",
"description": "Envoi e-factures (production)",
"status": "partial_outage",
"status_label": "Panne partielle",
"operational": false,
"latency_ms": null,
"detail": "Perturbations en cours",
"uptime_percent": 98.40
},
{
"slug": "ttn_sandbox",
"name": "TTN sandbox (test)",
"description": "Environnement de test",
"status": "operational",
"status_label": "Operationnel",
"operational": true,
"latency_ms": 187,
"detail": null,
"uptime_percent": 99.90
}
]
},
"error": null
}
Champs d'un service
| Champ | Type | Description |
|---|---|---|
| slug | string | Identifiant stable du service. Utilisez-le dans votre code : le libellé peut changer, pas le slug. Valeurs : platform, signature, ttn_prod, ttn_sandbox, api, tej, email. |
| status | string | Mêmes valeurs que l'état global. |
| operational | bool | Raccourci pour status === "operational". |
| latency_ms | int / null | Latence mesurée. Renseignée uniquement pour les services sondés par le réseau, null sinon. |
| detail | string / null | Motif, renseigné uniquement quand le service n'est pas opérationnel. null le reste du temps. |
| uptime_percent | float / null | Taux de disponibilité sur la fenêtre demandée. null tant qu'aucun historique n'est disponible. |
Exemple : couper la file d'envoi quand TTN tombe
$res = json_decode(file_get_contents('https://efacturetn.com/api/v1/status'), true);
if ($res['data']['status'] !== 'operational') {
// Ne pas envoyer maintenant : reprendre le lot plus tard
$this->queue->pause('Plateforme e-facture indisponible');
}
Gestion des clients (API)
Un compte développeur peut gérer ses clients directement depuis son ERP, via l'API. Chaque client possède sa configuration TTN (sandbox et production), sa méthode de signature, son plafond de crédits et ses propres clés API.
Confidentialité des secrets
Les mots de passe TTN et SFTP ne sont jamais renvoyés en clair. En lecture, ils sont masqués par des astérisques dont le nombre correspond à la longueur enregistrée (ex. "password": "********"). Vous pouvez les écrire (création / modification) mais jamais les relire.
Champs d'un client
| Champ | Type | Description |
|---|---|---|
name | string | Nom du client (obligatoire à la création) |
matricule | string | Matricule fiscal (identifiant TTN) |
signer_method | string | seal (cachet serveur), usb (clé locale), digigo (à distance) ou mobileid (E-Houwiya, smartphone) |
signer_email | string | Email du signataire : l'adresse sous laquelle il détient son certificat DigiGo ou E-Houwiya, celle déclarée chez l'autorité de certification. Une autre adresse, même valide, donne sur la page de signature « certificat introuvable pour l'email fourni ». Obligatoire pour digigo et mobileid (sinon SIGNER_EMAIL_MISSING, HTTP 412), inutile pour seal et usb. Il se règle sur le client (ici, ou dans le portail développeur), pas dans la facture : il est lu et transmis au service de signature à chaque signature. |
seal_passphrase_test | string | Passphrase du cachet électronique sandbox, requise pour la méthode seal en test. Le cachet sandbox et le cachet production sont deux organisations distinctes, avec potentiellement des passphrases différentes. Masquée en lecture. |
seal_passphrase_prod | string | Passphrase du cachet électronique production, requise pour la méthode seal en production. Masquée en lecture. |
usb_signing_tool_url | string | Lecture seule. Lien de téléchargement direct de l'outil de signature USB (Windows) à installer sur le poste du signataire. Distribuez-le à vos clients depuis votre ERP. |
submission_channel | string | Qui dépose la facture au TTN, une fois signée. delegated (défaut) : le service de signature dépose lui-même, sous son propre compte TTN : votre client n'a pas besoin d'un compte El Fatoora à lui. soap : nous déposons via le web service TTN du client, la référence est connue immédiatement. sftp : nous déposons le XML signé dans le dossier in de son compte TTN, et la réponse est relevée plus tard dans out : la facture reste alors submitted jusqu'au relevé, sans référence. Toute autre valeur est refusée. |
credit_cap | int / null | Plafond de crédits du client. null = illimité (dans la limite du portefeuille) |
ttn.sandbox / ttn.prod | objet | login + password du compte TTN par environnement |
sftp.sandbox / sftp.prod | objet | host, port, username, password, in_dir, out_dir (canal SFTP) |
provision | string | Optionnel (création / modification). sandbox, production ou both : crée ou met à jour le compte de signature du client, quel que soit son mode de signature. L'opération est idempotente (création la première fois, mise à jour ensuite). Le résultat, y compris le message d'erreur éventuel du service, est renvoyé dans provisioning. Sans provision, tout PATCH met à jour automatiquement les environnements déjà connectés (raison sociale, matricule, accès TTN). L'email du signataire, lui, n'est pas transmis ici : il part avec chaque signature. |
En lecture, chaque client renvoie aussi son suivi : credits (cap, used, remaining) et stats (total, validated, rejected). De quoi afficher un mini tableau de bord par client dans votre ERP.
Créer un client
Crée un client et retourne ses clés API (ck_test_ / ck_live_). Ces clés restent récupérables à tout moment via GET /clients/{id}. Ajoutez "provision": "sandbox" (ou production / both) pour créer en même temps le compte de signature du client.
POST /api/v1/clients
X-Api-Key: sk_dev_VOTRE_CLE_DEVELOPPEUR
Content-Type: application/json
{
"name": "Société ABC",
"matricule": "1234567AAM000",
"signer_method": "seal",
"signer_email": "signataire@societe-abc.tn",
"seal_passphrase_test": "123456",
"seal_passphrase_prod": "987654",
"submission_channel": "soap",
"credit_cap": 500,
"ttn": {
"sandbox": { "login": "abc_test", "password": "secret1" },
"prod": { "login": "abc_prod", "password": "secret2" }
}
}
Réponse (201) :
{
"success": true,
"data": {
"id": "018f...-....",
"name": "Société ABC",
"matricule": "1234567AAM000",
"signer_method": "seal",
"signer_email": "signataire@societe-abc.tn",
"submission_channel": "soap",
"active": true,
"live_enabled": true,
"credits": { "cap": 500, "used": 0, "remaining": 500 },
"stats": { "total": 0, "validated": 0, "rejected": 0 },
"ttn": {
"sandbox": { "login": "abc_test", "password": "*******" },
"prod": { "login": "abc_prod", "password": "*******" }
},
"keys": { "test": "ck_test_...", "live": "ck_live_..." }
}
}
Si vous avez demandé un provision, la réponse contient un bloc provisioning par environnement :
"provisioning": {
"sandbox": { "success": true, "message": "Compte de signature créé (environnement test)." },
"production": { "success": false, "message": "L'organisation existe déjà chez le prestataire..." }
}
La création du client réussit même si le provisioning échoue : corrigez les accès puis relancez via PATCH avec provision. Utilisez ensuite la clé du client (keys.live / keys.test) pour émettre ses e-factures via /einvoices/process ou /einvoices/prepare.
Lister les clients
Retourne tous vos clients (secrets masqués) avec leurs crédits et statistiques, plus le solde de votre portefeuille partagé.
{
"success": true,
"data": {
"clients": [
{ "id": "...", "name": "Société ABC",
"credits": { "cap": 500, "used": 180, "remaining": 320 },
"stats": { "total": 200, "validated": 190, "rejected": 10 }, ... }
],
"wallet": { "balance": 4200, "total_purchased": 5000 }
}
}
Lire un client
Retourne un client : configuration, crédits, statistiques et ses clés API (keys.test / keys.live, préfixe ck_). Les mots de passe TTN/SFTP restent masqués, mais les clés API du client sont renvoyées en clair pour vous permettre de les récupérer à tout moment.
Modifier un client
Mise à jour partielle : seuls les champs envoyés sont modifiés. Envoyez uniquement un mot de passe TTN/SFTP pour le remplacer ; omettez-le pour le conserver. Ajoutez "provision" pour recréer ou mettre à jour le compte de signature.
PATCH /api/v1/clients/018f...
X-Api-Key: sk_dev_VOTRE_CLE_DEVELOPPEUR
{ "credit_cap": 800, "submission_channel": "sftp",
"sftp": { "prod": { "host": "sftp.ttn.tn", "port": 22, "username": "abc",
"password": "xxx", "in_dir": "/in", "out_dir": "/out" } } }
Supprimer un client • Régénérer les clés
Supprime le client.
Régénère les clés API du client (les anciennes cessent immédiatement de fonctionner). Les nouvelles clés sont retournées, et restent récupérables via GET /clients/{id}.
Rate limits
Par défaut : 60 requêtes par minute par clé API. Sliding window, comptage atomique. Configurable sur demande pour les volumes élevés.
En cas de dépassement : réponse 429 RATE_LIMIT. Implémentez un backoff exponentiel côté client.
Objet Invoice
| Champ | Type | Description |
|---|---|---|
| invoice_numberrequis | string | Numéro de facture côté ERP. 30 caractères au plus, lettres, chiffres, ., - et _ uniquement, ni espace ni / : voir les règles de forme. Le format par défaut d'Odoo, INV/2026/09/0008, est refusé par TTN. |
| daterequis | date | Date d'émission YYYY-MM-DD. |
| due_dateoptionnel | date | Date d'échéance. |
| typeoptionnel | enum | Nature du document. facture (défaut), avoir, note_debit, honoraire, proforma. Alias : document_type. Un avoir produit un TEIF de type I-12 et doit référencer sa facture d'origine (voir ci-dessous). |
| original_invoice_numberavoir | string | Numéro de la facture d'origine, requis sur un avoir ou une note de débit : sans lui TTN ne peut pas rattacher le document. Alias : facture_origine_numero, parent_invoice_number. Ajoutez original_ttn_reference si vous connaissez la référence TTN de l'origine. |
| delivery_dateoptionnel | date | Date de livraison YYYY-MM-DD, quand elle diffère de la date d'émission. |
| currencyoptionnel | string | Devise ISO 4217. Défaut TND (3 décimales). Pour les factures offshore/export : EUR ou USD (2 décimales). TTN ne reçoit que ces trois devises : toute autre (GBP, CHF…) est refusée en 422 VALIDATION_FAILED, avant signature et sans crédit consommé - conservées telles quelles, aucune conversion. En devise étrangère : ni TVA, ni timbre fiscal (régime export), le montant est en TTC = HT. |
| seller.namerequis | string | Raison sociale du vendeur. |
| seller.tax_idoptionnel | string | Matricule fiscal du vendeur. Facultatif : votre clé le porte déjà, et c'est celui du compte qui part dans le document, au format complet exigé par TTN. Si vous l'envoyez, seuls les 8 premiers caractères sont comparés : 1234567M et 1234567MAM000 sont équivalents. Un contribuable différent est refusé en SELLER_MISMATCH, avant signature. |
| seller.addressrequis | string | Adresse (rue). Aussi : address_line_2, postal_code, city, governorate, country, phone, email. |
| buyer.namerequis | string | Raison sociale / nom du client. |
| buyer.tax_idrequis* | string | Matricule fiscal tunisien du client. *Il est exigé ou buyer.id_number, jamais les deux. Ne l'envoyez pas pour un client étranger : ce champ n'accepte que la forme tunisienne, et un numéro d'un autre pays y est refusé en VALIDATION_FAILED. |
| buyer.id_numberrequis* | string | Numéro d'identification libre, à la place du matricule. C'est ici que vont la CIN d'un particulier et le numéro d'entreprise d'un client étranger (SIREN, SIRET, numéro de TVA intracommunautaire, NIF). Les espaces et la ponctuation sont retirés avant l'envoi. Alias : cin, piece_identite, passport. |
| buyer.addressoptionnel | string | Adresse (rue) du client. |
| buyer.address_line_2optionnel | string | Complément d'adresse. |
| buyer.postal_codeoptionnel | string | Code postal. |
| buyer.cityoptionnel | string | Ville. |
| buyer.governorateoptionnel | string | Gouvernorat. |
| buyer.countryoptionnel | string | Pays (code ISO, défaut TN). Ce champ décide du type d'identifiant envoyé à TTN, donc renseignez-le dès que le client n'est pas tunisien. Avec le pays par défaut, un id_number part comme une carte d'identité nationale, que le schéma officiel oblige à faire exactement 8 caractères : un SIREN de 9 chiffres serait alors refusé au dépôt, après signature. Avec un pays étranger, il part comme identifiant fiscal non tunisien, de forme libre. |
| buyer.phone / buyer.emailoptionnel | string | Téléphone / e-mail du client. |
| items[].descriptionrequis | string | Description de la ligne. |
| items[].referenceoptionnel | string | Référence de l'article, imprimée en colonne RÉFÉRENCE. Alias : code, article_code. |
| items[].unitoptionnel | string | Unité de la ligne (piece, kg, jour, heure…). Alias : unite. |
| items[].quantityrequis | number | Quantité. |
| items[].unit_pricerequis | number | Prix unitaire HT. |
| items[].vat_raterequis* | enum | 0, 7, 13, 19. Alias : tva_rate, tax_rate. *Requis sauf si default_vat_rate (facture) est défini ou si la ligne est exonerated. Aucune valeur n'est supposée par défaut : un taux manquant renvoie une erreur de validation. (Ignoré en devise étrangère : pas de TVA.) |
| items[].discount_percentoptionnel | number | Remise sur la ligne, en % (0–100). Le Total HT de la ligne est diminué d'autant, et la TVA est calculée sur le HT après remise. Alias : remise, remise_percent, discount. |
| items[].line_total_htoptionnel | number | Total HT de la ligne tel que VOUS l’avez calculé. Quand il est fourni, il remplace le produit quantité × prix unitaire, et la TVA comme le FODEC s’en déduisent. À utiliser si votre logiciel arrondit autrement que nous : vos totaux et les nôtres cessent alors de diverger de quelques millimes, sans avoir à multiplier les décimales du prix unitaire. Nous prenons votre montant : c’est votre facture. Seul un écart de plus de 1 % avec notre propre calcul est refusé en 422 : un TTC envoyé à la place d’un HT, ou une virgule déplacée. Le message nomme alors les trois montants en cause. Aucune différence d’arrondi ne vous bloque. Alias : total_ht, montant_ht, line_total. |
| items[].exoneratedoptionnel | boolean | Ligne exonérée de TVA (taux 0 %). Équivaut à vat_rate: 0. Alias : vat_exempt. |
| items[].fodecoptionnel | boolean | FODEC 1 % sur la ligne (produits assujettis). Calculé sur le HT après remise, et la TVA de la ligne est calculée sur HT + FODEC (règle tunisienne). Apparaît en ligne « FODEC (1%) » dans les totaux du PDF et en taxe I-1603 dans le TEIF. Alias : fodec_applicable, is_fodec. (Ignoré en devise étrangère.) |
| default_vat_rateoptionnel | enum | Taux TVA par défaut de la facture (0, 7, 13, 19), appliqué aux lignes sans vat_rate. Évite de répéter le taux sur chaque ligne. |
| global_discount_percentoptionnel | number | Remise sur toute la facture, en % (0 à 100), appliquée après les remises de ligne. Le Total HT est diminué d'autant et la TVA baisse proportionnellement. Le FODEC n'est pas réduit : il reste calculé sur le HT avant remise globale. Apparaît en ligne « Remise globale X% » dans les totaux du PDF. Alias : remise_globale_percent, remise_globale_pct. Un montant envoyé dans ce champ est refusé (422) : une valeur hors [0, 100] ne peut pas être un pourcentage. Utilisez alors global_discount_amount. |
| global_discount_amountoptionnel | number | Remise sur toute la facture, en MONTANT (dans la devise de la facture), pour les logiciels qui ne connaissent pas de pourcentage. Elle est rapportée au total HT des lignes, remises de ligne déduites, puis appliquée comme global_discount_percent : mêmes effets, FODEC non réduit compris. Alias : remise_globale, remise_globale_montant, remise_montant. Une remise supérieure au HT est refusée (422) plutôt que de produire un total négatif. N'envoyez qu'une seule des deux formes ; le pourcentage l'emporte si les deux sont présentes. |
| stamp_dutyoptionnel | boolean | number | Timbre fiscal. Présent par défaut (1,000 TND) - mettre false pour le désactiver, ou un montant numérique pour le personnaliser. Alias : timbre, fiscal_stamp. |
| withholding_tax_rateoptionnel | number | Retenue à la source (ex. 1.5, 3, 10, 15, 20). Base = TTC hors timbre (HT + TVA). Montant déduit du Net à payer. |
| suspensionoptionnel | object | Vente en suspension de TVA. Objet { "enabled": true, "authorization_number": "AUT-2026-777", "date_from": "2026-01-01", "date_to": "2026-12-31", "order_number": "BC-42", "order_date": "2026-01-15" }. L'opération reste taxable : la TVA est calculée mais non facturée, et le net à payer vaut HT + timbre. Dans le TEIF : la ligne garde son taux réel, la TVA due vaut 0 et le montant suspendu part en I-187. Le numéro d'autorisation part en référence I-85 avec sa période de validité, le bon de commande en I-83 avec sa date, et la facture porte la mention SpecialConditions « Vente en suspension de TVA ». Sur le PDF : la TVA suspendue s'affiche à part, sous les totaux, avec les mentions. N'envoyez pas exonerated: true : c'est une exonération, qui déclare un taux de 0 %. /einvoices/validate signale en warnings une attestation ou un bon de commande manquant, une date illisible ou hors validité. Alias : suspension_tva pour un simple booléen, num_autorisation, date_debut, date_fin, bon_commande. |
| brand_coloroptionnel | string | Couleur de marque du PDF généré (hex, ex. #1d4ed8). Défaut : #dc2626. Ignoré si vous fournissez pdf_base64. |
| payment_methodoptionnel | string | Mode de règlement affiché sur le PDF (texte libre, ex. Virement bancaire - RIB 12345678901234567890, Chèque, Espèces). Max 255 caractères. Rendu sur le PDF généré (mode B). |
| custom_noteoptionnel | object | Note libre positionnable (mentions légales, conditions, paragraphe…). Objet { "text": string (max 1000), "position": "top_left" | "bottom_left" | "bottom_center" | "bottom_right" }. Défaut position : bottom_left. Rendu sur le PDF généré (mode B). |
| invoice_objectoptionnel | string | Objet de la facture, affiché « Objet : … » (ex. Prestation de conseil - janvier 2026). Max 255 caractères. Rendu sur le PDF généré (mode B). |
| termsoptionnel | string | Conditions générales / conditions de paiement / mentions, affichées dans un bloc « Conditions générales » (ex. Paiement à 30 jours. Pénalité de 1,5%/mois en cas de retard.). Max 1000 caractères. Rendu sur le PDF généré (mode B). |
| referencesoptionnel | string | Référence libre affichée « Référence » (ex. Bon de commande N° 1234 du 05/01/2026). Max 100 caractères. Rendu sur le PDF généré (mode B). |
| bank_detailsoptionnel | string | Coordonnées bancaires affichées « Coordonnées bancaires : … » (ex. RIB 12345678901234567890 - Banque XYZ). Max 255 caractères. Rendu sur le PDF généré (mode B). |
| pdf_base64optionnel | string | Votre PDF de facture encodé en base64. Voir PDF de la facture. |
Règles de forme du document
Ces règles s'appliquent aux trois portes, POST /einvoices/validate, POST /einvoices/process et POST /einvoices/prepare, avec les mêmes messages. Elles refusent avant la signature ce que TTN refuserait après, quand le crédit est déjà consommé. Validez à blanc avec /einvoices/validate : l'appel est gratuit et rend la liste complète des erreurs dans data.errors.
| Champ | Règle | Pourquoi |
|---|---|---|
invoice_number | 1 à 30 caractères, commence par une lettre ou un chiffre, puis lettres, chiffres, ., -, _. Ni espace, ni /, ni :. Exemples acceptés : FACT-2026-0012, INV-2026-09-0008, F202600002. | C'est l'assertion du schéma appliqué par TTN, mot pour mot : ^[A-Za-z0-9][A-Za-z0-9._-]{0,29}$. Intégrateurs Odoo : le format par défaut INV/2026/09/0008 est signé puis refusé au dépôt. Envoyez INV-2026-09-0008 ou changez la séquence, et gardez la correspondance sur la facture. |
date | Date calendaire valide au format YYYY-MM-DD, pas dans le futur (heure de Tunisie). | Le document est signé et horodaté le jour de l'appel ; une date d'émission postérieure est incohérente. |
due_date | Optionnelle. Même format, jamais antérieure à date. | Une échéance avant l'émission est une erreur de saisie. |
type | Optionnel, défaut facture. Valeurs acceptées : facture, invoice, credit_note, avoir, debit_note. Toute autre valeur est refusée. | Une nature inconnue retombait en silence sur « facture » : un avoir mal orthographié partait comme une facture. |
original_invoice_number | Obligatoire pour credit_note et debit_note, mêmes règles de forme que invoice_number. | Un avoir corrige un document nommé. |
items[].quantity | Nombre strictement positif, 3 décimales au plus. | Le document conserve la quantité sur trois décimales : au-delà, la valeur serait tronquée entre votre ERP et le document signé. |
items[].unit_price | Nombre positif ou nul, 6 décimales au plus, 15 chiffres entiers au plus. | Le schéma TEIF borne un montant à 15 chiffres entiers ; le prix se conserve sur six décimales et s'imprime sur trois. |
items[].line_total_ht | Optionnel, nombre positif ou nul. Quand il est envoyé, il fait foi sur quantité × prix (voir la tolérance d'arrondi de l'objet Invoice). | C'est votre HT de ligne qui doit figurer sur le document, pas notre recalcul. |
items[].vat_rate | 0, 7, 13 ou 19, ou default_vat_rate au niveau du document, ou exonerated: true sur la ligne. | Un taux ne se devine pas. |
items[].discount_percent | Optionnel, de 0 à 100. | |
stamp_duty | true, false, ou un montant positif. | |
global_discount_percent / global_discount_amount | De 0 à 100 pour le pourcentage, montant positif pour le montant. | |
seller.tax_id | Facultatif : la clé porte le vendeur. S'il est envoyé, il doit désigner le même contribuable (8 premiers caractères), sinon SELLER_MISMATCH. | Le document est signé sous l'identité de la clé. |
buyer.tax_id | Matricule tunisien, forme longue (1234567ABM000) ou courte (1234567A). À défaut, buyer.id_number, de forme libre. | Un numéro étranger placé ici est refusé : ce champ n'accepte que la forme tunisienne. |
buyer.id_number | Libre. CIN d'un particulier, ou numéro d'entreprise d'un client étranger. | Envoyez-le seul, sans buyer.tax_id, et avec buyer.country. |
/validate est accepté par /process et /prepare ; un document refusé le serait aussi par TTN. Ne contournez pas un refus en modifiant le message côté ERP : corrigez la donnée.
Exemple complet (tous les champs)
Payload de référence regroupant l'ensemble des champs supportés : coordonnées structurées vendeur/client (MF, adresse, ville, code postal, gouvernorat, pays, téléphone, e-mail), TVA multi-taux par ligne, taux par défaut, ligne exonérée, timbre fiscal, retenue à la source et couleur du PDF. Ce payload passe la validation sans erreur.
Notes :
method:sealpour/einvoices/process(signature automatique),digigo,usboumobileidpour/einvoices/prepare(signature par le client).- Particulier : remplacez
buyer.tax_idparbuyer.id_number(CIN/passeport) → le PDF affiche « CIN » au lieu de « MF ». - Client étranger : même champ,
buyer.id_number, et surtoutbuyer.country. Voir l'exemple export ci-dessous. default_vat_rates'applique aux lignes sansvat_rate; chaque ligne peut le surcharger (TVA différente par ligne). Un récap TVA par taux est généré automatiquement.stamp_dutyest présent par défaut (1,000 TND) - mettezfalsepour le retirer.brand_coloret les coordonnées ne servent au rendu que pour le PDF généré (mode B). En mode A (pdf_base64), c'est votre PDF qui fait foi.- Validez d'abord via
POST /einvoices/validate(0 crédit) : tout champ manquant/invalide est retourné danserror.details. - Alias de champs acceptés (vendeur/client) :
name=nom/raison_sociale,tax_id=matricule_fiscal/mf,address=adresse/rue,postal_code=code_postal/cp,city=ville,governorate=gouvernorat,country=pays,phone=tel. L'addresspeut aussi être un objet{ street, city, postal_code }.
Récap TVA produit pour cet exemple :
| Taux | Base HT | Montant TVA |
|---|---|---|
| 0 % (exonéré) | 500,000 | 0,000 |
| 7 % | 100,000 | 7,000 |
| 13 % | 500,000 | 65,000 |
| 19 % | 1 000,000 | 190,000 |
| TOTAL | 2 100,000 | 262,000 |
Facture export : un client qui n'a pas de matricule tunisien
Un client étranger n'a pas de matricule fiscal tunisien, et son numéro d'entreprise
(SIREN, SIRET, numéro de TVA intracommunautaire, NIF) ne se met pas dans
buyer.tax_id : ce champ n'accepte que la forme tunisienne et le
refuse. Il se met dans buyer.id_number, seul, avec le pays
du client.
Ce que l'export change, et rien d'autre :
buyer.id_numberporte le numéro, etbuyer.tax_idest absent. Les espaces sont retirés avant l'envoi :552 100 554devient552100554.buyer.countryporte le code du pays du client. C'est lui qui fait accepter un numéro qui n'a pas la forme tunisienne.currencyen devise étrangère : 2 décimales, aucune conversion, les montants sont conservés tels quels.- Ni TVA, ni timbre fiscal :
vat_rate: 0sur les lignes, ouexonerated: true, etstamp_duty: false. - Le vendeur, lui, ne change pas : c'est toujours le titulaire de votre clé, et son matricule tunisien complet part dans le document.
Ce cas figure en entier dans votre portail, deuxième document du jeu de test TTN, prêt à être envoyé tel quel.
Retenue à la source (RS)
La retenue à la source est l'impôt que l'acheteur retient sur le paiement et reverse à l'État pour le compte du vendeur. Elle ne modifie ni le HT, ni la TVA, ni le Total TTC : elle réduit seulement le Net à payer.
- Optionnelle : envoyez
withholding_tax_rateuniquement quand il y a une retenue. Omettez le champ (ou mettez0) pour ne pas en appliquer - la ligne « Retenue à la source » n'apparaît alors pas et le Net à payer = Total TTC. Aucun autre indicateur n'est requis. - Base de calcul = TTC hors timbre (HT + TVA).
- Taux courants :
1.5(achats biens/services > 1000 DT TTC),3(honoraires régime réel),10(honoraires/loyers/commissions),15,20…
Exemple 1 - avec retenue à la source (1,5 %)
| Ligne | Montant | Calcul |
|---|---|---|
| Total HT | 125,000 | 1 × 125,000 |
| TVA (7 %) | 8,750 | 7 % du HT |
| Timbre fiscal | 1,000 | fixe |
| Total TTC | 134,750 | HT + TVA + timbre |
| Retenue à la source 1,5 % | − 2,006 | 1,5 % × (HT + TVA) = 1,5 % × 133,750 |
| NET À PAYER | 132,744 | TTC − RS |
Exemple 2 - sans retenue à la source
Il suffit de ne pas envoyer withholding_tax_rate (ou de mettre 0) :
| Ligne | Montant | Calcul |
|---|---|---|
| Total HT | 125,000 | 1 × 125,000 |
| TVA (7 %) | 8,750 | 7 % du HT |
| Timbre fiscal | 1,000 | fixe |
| NET À PAYER (= Total TTC) | 134,750 | aucune ligne RS affichée |
Vos totaux et les nôtres : line_total_ht
Par défaut, nous calculons le total de chaque ligne nous-mêmes : quantité × prix unitaire, remise déduite. Dans la très grande majorité des cas, c'est exactement ce que vous voulez et vous n'avez rien à faire.
Quand vous en avez besoin
Quand votre logiciel arrive à un total légèrement différent du nôtre. Cela arrive avec des prix unitaires à beaucoup de décimales, et pour deux raisons qui n'ont rien à voir avec un défaut :
-
La précision conservée. Nous gardons six décimales sur un prix
unitaire. Un prix comme
8.40336134454est donc stocké8.403361. Sur 7 006 unités, ces quelques millionièmes font environ 3 millimes. - L'ordre des arrondis. Beaucoup de logiciels arrondissent le total une seule fois, à la fin. Nous arrondissons chaque ligne puis nous sommons, parce qu'une ligne imprimée doit être un vrai montant et que le total doit être la somme de ce qui est imprimé. Cela vaut encore un millime ou deux, parfois dans l'autre sens.
Aucune précision supplémentaire ne supprime le second point : ce n'est pas une question de décimales, c'est une convention. D'où ce champ : envoyez-nous le montant que vous avez calculé, et nous imprimons le même.
Comment l'utiliser
Ajoutez line_total_ht sur la ligne, hors taxes,
remise déjà déduite. Il remplace le produit quantité × prix ;
la TVA et le FODEC s'en déduisent ensuite normalement.
La seule ligne ajoutée est line_total_ht. Tout le reste est
votre facture habituelle.
| Sans le champ | Avec le champ |
|---|---|
| Total ligne HT 58 873,947 (7 006 × 8,403361) |
Total ligne HT 58 873,950 (votre montant, repris tel quel) |
Pour que le total de la FACTURE tombe juste aussi
Le total HT du document est la somme des lignes que vous envoyez. Si votre logiciel arrondit le total une seule fois, la somme de vos lignes arrondies peut dépasser votre propre total d'un millime. Dans ce cas, reportez ce reliquat sur une ligne, comme votre logiciel le fait déjà pour imprimer votre propre document, et nos deux totaux seront identiques au millime.
Exemple complet, prêt à envoyer
Une facture de trois lignes, avec des prix unitaires à onze décimales -
exactement le cas qui fait diverger les totaux. Chaque ligne porte son
line_total_ht.
Ce que vous obtenez, et ce sont les montants que cet envoi produit réellement :
Avec line_total_ht | Sans | |
|---|---|---|
| Total HT | 61 006,723 | 61 006,720 |
| TVA 19 % | 11 591,277 | 11 591,277 |
| Timbre fiscal | 1,000 | 1,000 |
| Net à payer | 72 599,000 | 72 598,997 |
Avec le champ, le total est exactement celui de votre logiciel. Ici la TVA tombe juste des deux côtés : c’est le HT, et donc le net à payer, qui portent l’écart. Sans lui, trois millimes d'écart, assez pour qu'un rapprochement comptable automatique signale la facture.
Ici, la somme des trois lignes tombe pile sur votre total. Quand ce n'est pas le cas, reportez le reliquat sur une ligne, comme expliqué juste au-dessus.
Ce que nous contrôlons
Nous prenons votre montant : c'est votre facture, vous en êtes l'émetteur.
Nous refusons seulement un écart de plus de 1 % avec notre propre
calcul, parce qu'aucun arrondi ne produit cela et que la facture part
signée chez l'administration. Ce garde-fou attrape les trois
erreurs réelles : un TTC envoyé à la place d'un HT (+19 %), une
virgule déplacée (×10), une ligne mal appariée.
Le refus est un 422 qui nomme les trois montants en cause.
Deux pièges à éviter
- Envoyez un montant HORS TAXES. Le TTC est l'erreur la plus fréquente, et elle est refusée.
- Ne l'envoyez pas « au cas où ». Si vos totaux correspondent déjà aux nôtres, omettez le champ : le calcul normal reste la règle, et une ligne de moins est une ligne qui ne peut pas diverger.
PDF de la facture
Le PDF est le document visuel présenté au signataire et archivé après signature (le QR de signature y est apposé automatiquement). Deux modes sont possibles :
| Mode | Quand | Résultat |
|---|---|---|
| A - Vous fournissez le PDF recommandé | Vous envoyez le champ pdf_base64 (votre PDF déjà mis en page : logo, couleurs, mentions de votre ERP). |
Votre PDF est signé tel quel. C'est votre document qui fait foi. |
| B - Génération automatique | Vous omettez pdf_base64. |
Nous générons un PDF neutre mais valide à partir des données de la facture (vendeur, client, lignes, totaux). Sans votre logo. |
Recommandation : si votre ERP produit déjà des factures PDF, envoyez-les via
pdf_base64 pour conserver votre charte graphique. Contraintes :
- Encodage base64 du contenu binaire du PDF (préfixe
data:application/pdf;base64,toléré mais optionnel). - Doit être un PDF valide (commence par
%PDF). - Taille maximale : 10 Mo (avant encodage).
- Si le PDF fourni est invalide ou absent, le mode B (génération) prend automatiquement le relais - aucun échec de signature.
Objet Error
Format normalisé pour toutes les erreurs :
Une question technique ? Contactez l'équipe ou consultez la référence interactive Swagger.