Integrating the TTN API into Your System: Developer Technical Guide

This guide is aimed at developers and technical architects responsible for integrating TTN electronic invoicing into an existing application. We cover the complete architecture, three integration strategies, and provide concrete Node.js/TypeScript code examples for each key step: TEIF XML generation, TUNTRUST signing, API submission, and response handling.
This tutorial is the seventh episode of our Electronic Invoicing in Tunisia series. It assumes you have already read Episode 5: TEIF Format and Technical Specifications and that your company is registered on the El Fatoora platform.
Overall System Architecture
Before writing a single line of code, it is essential to understand how your system interacts with TTN and DGI infrastructure.
┌─────────────────────────────────────────────────┐
│ YOUR SYSTEM │
│ ┌──────────┐ ┌──────────┐ ┌─────────────┐ │
│ │ ERP/CRM │──►│ TEIF │──►│ TTN Module │ │
│ │ (data) │ │Generator │ │ (signing │ │
│ └──────────┘ └──────────┘ │ + submit) │ │
│ └──────┬────────┘ │
└────────────────────────────────────────│───────────┘
│ HTTPS/REST
▼
┌──────────────────────────┐
│ TTN API (El Fatoora) │
│ api.elfatoora.tn │
│ ┌──────────────────────┐ │
│ │ TEIF Validation │ │
│ │ Signature Verification│ │
│ │ TTN Timestamping │ │
│ │ Archiving │ │
│ └──────────┬───────────┘ │
└─────────────│──────────────┘
│ Automatic transmission
▼
┌──────────────────────────┐
│ DGI (Tax Authority) │
│ VAT Declaration │
│ Tax monitoring │
└──────────────────────────┘
Key interaction points:
- Your system generates the TEIF XML from your business data
- Digital signing with the TUNTRUST certificate is performed client-side (your server)
- The TTN API validates the format, the signature, and archives the invoice
- TTN automatically transmits data to the DGI — you do not need to interact directly with the DGI for each invoice
Setup and Authentication
Technical Prerequisites
Before starting the integration, make sure you have:
- Node.js v18+ (v20 recommended)
- PKCS#12 certificate (
.p12) issued by TUNTRUST, with its password - TTN API credentials (client_id and client_secret) obtained after registering in EDI mode
- TTN sandbox access:
https://api-sandbox.elfatoora.tn
Installing Dependencies
npm install xmlbuilder2 node-forge axios dotenv qrcode
npm install --save-dev @types/node typescript tsxxmlbuilder2: XML document constructionnode-forge: PKCS#12 certificate handling and XML signingaxios: HTTP client for the TTN APIqrcode: QR code generation
Environment Variables
# .env
TTN_API_BASE_URL=https://api-sandbox.elfatoora.tn
TTN_CLIENT_ID=your_ttn_client_id
TTN_CLIENT_SECRET=your_ttn_client_secret
# TUNTRUST Certificate
TUNTRUST_CERT_PATH=/path/to/your-certificate.p12
TUNTRUST_CERT_PASSWORD=your_certificate_password
# Supplier info
SUPPLIER_TAX_ID=12345678A000000
SUPPLIER_NAME=Your Company SARLOption 1: Direct TTN API Integration
Direct integration is the most flexible but also the most complex approach to implement. You manage the complete invoice lifecycle: XML generation, signing, submission, and response handling.
Step 1: OAuth2 Authentication
The TTN API uses OAuth2 with the client_credentials flow:
// src/ttn/auth.ts
import axios from "axios";
interface TTNTokenResponse {
access_token: string;
token_type: string;
expires_in: number;
}
let cachedToken: { token: string; expiresAt: number } | null = null;
export async function getTTNAccessToken(): Promise<string> {
// Reuse the token if still valid (with a 60s margin)
if (cachedToken && Date.now() < cachedToken.expiresAt - 60000) {
return cachedToken.token;
}
const response = await axios.post<TTNTokenResponse>(
`${process.env.TTN_API_BASE_URL}/api/v1/auth/token`,
new URLSearchParams({
grant_type: "client_credentials",
client_id: process.env.TTN_CLIENT_ID!,
client_secret: process.env.TTN_CLIENT_SECRET!,
scope: "invoices:write invoices:read",
}),
{ headers: { "Content-Type": "application/x-www-form-urlencoded" } }
);
cachedToken = {
token: response.data.access_token,
expiresAt: Date.now() + response.data.expires_in * 1000,
};
return cachedToken.token;
}Step 2: TEIF XML Generation
// src/ttn/teif-generator.ts
import { create } from "xmlbuilder2";
export interface InvoiceLine {
id: number;
description: string;
quantity: number;
unitCode: string;
unitPrice: number; // excl. VAT
vatRate: number; // e.g. 19 for 19%
}
export interface InvoiceData {
invoiceNumber: string;
issueDate: string; // YYYY-MM-DD
dueDate: string;
supplierTaxId: string;
supplierName: string;
supplierAddress: string;
supplierCity: string;
supplierPostalCode: string;
customerTaxId: string;
customerName: string;
customerAddress: string;
customerCity: string;
customerPostalCode: string;
lines: InvoiceLine[];
notes?: string;
}
function formatAmount(amount: number): string {
return amount.toFixed(3);
}
export function generateTEIF(invoice: InvoiceData): string {
// Calculate totals
const lineCalculations = invoice.lines.map((line) => {
const lineTotal = line.quantity * line.unitPrice;
const vatAmount = (lineTotal * line.vatRate) / 100;
return { ...line, lineTotal, vatAmount };
});
const subtotalHT = lineCalculations.reduce((s, l) => s + l.lineTotal, 0);
const totalVAT = lineCalculations.reduce((s, l) => s + l.vatAmount, 0);
const totalTTC = subtotalHT + totalVAT;
// Build the XML document
const doc = create({ version: "1.0", encoding: "UTF-8" })
.ele("Invoice", {
xmlns: "urn:tn:gov:dgi:teif:1.8",
"xmlns:xsi": "http://www.w3.org/2001/XMLSchema-instance",
"xsi:schemaLocation":
"urn:tn:gov:dgi:teif:1.8 TEIF_v1.8.7.xsd",
});
// Header
const header = doc.ele("Header");
header.ele("InvoiceID").txt(invoice.invoiceNumber);
header.ele("IssueDate").txt(invoice.issueDate);
header.ele("IssueTime").txt(new Date().toTimeString().split(" ")[0]);
header.ele("InvoiceTypeCode").txt("380");
header.ele("DocumentCurrencyCode").txt("TND");
header.ele("TaxCurrencyCode").txt("TND");
header.ele("DueDate").txt(invoice.dueDate);
if (invoice.notes) {
header.ele("Note").txt(invoice.notes);
}
// Parties
const parties = doc.ele("Parties");
const supplier = parties.ele("Supplier");
supplier.ele("PartyIdentification").ele("ID", { schemeID: "TN_MF" })
.txt(invoice.supplierTaxId);
supplier.ele("PartyName").ele("Name").txt(invoice.supplierName);
const supplierAddr = supplier.ele("PostalAddress");
supplierAddr.ele("StreetName").txt(invoice.supplierAddress);
supplierAddr.ele("CityName").txt(invoice.supplierCity);
supplierAddr.ele("PostalZone").txt(invoice.supplierPostalCode);
supplierAddr.ele("Country").ele("IdentificationCode").txt("TN");
const customer = parties.ele("Customer");
customer.ele("PartyIdentification").ele("ID", { schemeID: "TN_MF" })
.txt(invoice.customerTaxId);
customer.ele("PartyName").ele("Name").txt(invoice.customerName);
const customerAddr = customer.ele("PostalAddress");
customerAddr.ele("StreetName").txt(invoice.customerAddress);
customerAddr.ele("CityName").txt(invoice.customerCity);
customerAddr.ele("PostalZone").txt(invoice.customerPostalCode);
customerAddr.ele("Country").ele("IdentificationCode").txt("TN");
// Invoice lines
const lines = doc.ele("InvoiceLines");
for (const line of lineCalculations) {
const invoiceLine = lines.ele("InvoiceLine");
invoiceLine.ele("ID").txt(String(line.id));
invoiceLine.ele("InvoicedQuantity", { unitCode: line.unitCode })
.txt(String(line.quantity));
invoiceLine.ele("LineExtensionAmount", { currencyID: "TND" })
.txt(formatAmount(line.lineTotal));
const item = invoiceLine.ele("Item");
item.ele("Name").txt(line.description);
const taxCat = item.ele("ClassifiedTaxCategory");
taxCat.ele("ID").txt("S");
taxCat.ele("Percent").txt(String(line.vatRate));
taxCat.ele("TaxScheme").ele("ID").txt("TVA");
const price = invoiceLine.ele("Price");
price.ele("PriceAmount", { currencyID: "TND" })
.txt(formatAmount(line.unitPrice));
price.ele("BaseQuantity", { unitCode: line.unitCode }).txt("1");
}
// VAT totals
const taxTotal = doc.ele("TaxTotal");
taxTotal.ele("TaxAmount", { currencyID: "TND" }).txt(formatAmount(totalVAT));
const taxSubtotal = taxTotal.ele("TaxSubtotal");
taxSubtotal.ele("TaxableAmount", { currencyID: "TND" })
.txt(formatAmount(subtotalHT));
taxSubtotal.ele("TaxAmount", { currencyID: "TND" })
.txt(formatAmount(totalVAT));
const taxCategory = taxSubtotal.ele("TaxCategory");
taxCategory.ele("ID").txt("S");
taxCategory.ele("Percent").txt("19");
taxCategory.ele("TaxScheme").ele("ID").txt("TVA");
// Monetary totals
const monetary = doc.ele("LegalMonetaryTotal");
monetary.ele("LineExtensionAmount", { currencyID: "TND" })
.txt(formatAmount(subtotalHT));
monetary.ele("TaxExclusiveAmount", { currencyID: "TND" })
.txt(formatAmount(subtotalHT));
monetary.ele("TaxInclusiveAmount", { currencyID: "TND" })
.txt(formatAmount(totalTTC));
monetary.ele("PayableAmount", { currencyID: "TND" })
.txt(formatAmount(totalTTC));
return doc.end({ prettyPrint: true });
}Step 3: Signing with the TUNTRUST Certificate
// src/ttn/signer.ts
import * as forge from "node-forge";
import * as fs from "fs";
import { create } from "xmlbuilder2";
interface SigningResult {
signedXml: string;
signatureValue: string;
certificateBase64: string;
}
export function signTEIF(xmlContent: string): SigningResult {
// Load the PKCS#12 certificate
const p12Buffer = fs.readFileSync(process.env.TUNTRUST_CERT_PATH!);
const p12Der = forge.util.createBuffer(p12Buffer.toString("binary"));
const p12Asn1 = forge.asn1.fromDer(p12Der);
const p12 = forge.pkcs12.pkcs12FromAsn1(
p12Asn1,
process.env.TUNTRUST_CERT_PASSWORD!
);
// Extract the private key and certificate
const bags = p12.getBags({
bagType: forge.pki.oids.pkcs8ShroudedKeyBag,
});
const keyBag = bags[forge.pki.oids.pkcs8ShroudedKeyBag]?.[0];
if (!keyBag?.key) throw new Error("Private key not found in PKCS#12");
const privateKey = keyBag.key;
const certBags = p12.getBags({ bagType: forge.pki.oids.certBag });
const certBag = certBags[forge.pki.oids.certBag]?.[0];
if (!certBag?.cert) throw new Error("Certificate not found in PKCS#12");
const certificate = certBag.cert;
// Canonicalize the XML (simplified C14N for this example)
const xmlBuffer = Buffer.from(xmlContent, "utf8");
// Compute the SHA-256 digest of the document
const md = forge.md.sha256.create();
md.update(xmlBuffer.toString("binary"));
const digestValue = Buffer.from(md.digest().bytes(), "binary").toString(
"base64"
);
// Sign the digest with the RSA private key
const signatureMd = forge.md.sha256.create();
signatureMd.update(xmlBuffer.toString("binary"));
const signatureBytes = privateKey.sign(signatureMd);
const signatureValue = Buffer.from(signatureBytes, "binary").toString(
"base64"
);
// Encode the certificate in Base64
const certDer = forge.asn1.toDer(forge.pki.certificateToAsn1(certificate));
const certificateBase64 = Buffer.from(certDer.bytes(), "binary").toString(
"base64"
);
// Build the XML Signature section and inject it into the document
const signatureXml = `
<Signature Id="TEIF-SIG-001" xmlns="http://www.w3.org/2000/09/xmldsig#">
<SignedInfo>
<CanonicalizationMethod Algorithm="http://www.w3.org/TR/2001/REC-xml-c14n-20010315"/>
<SignatureMethod Algorithm="http://www.w3.org/2001/04/xmldsig-more#rsa-sha256"/>
<Reference URI="">
<Transforms>
<Transform Algorithm="http://www.w3.org/2000/09/xmldsig#enveloped-signature"/>
</Transforms>
<DigestMethod Algorithm="http://www.w3.org/2001/04/xmlenc#sha256"/>
<DigestValue>${digestValue}</DigestValue>
</Reference>
</SignedInfo>
<SignatureValue>${signatureValue}</SignatureValue>
<KeyInfo>
<X509Data>
<X509Certificate>${certificateBase64}</X509Certificate>
</X509Data>
</KeyInfo>
</Signature>`;
// Insert the signature before the closing </Invoice> tag
const signedXml = xmlContent.replace(
"</Invoice>",
`${signatureXml}\n</Invoice>`
);
return { signedXml, signatureValue, certificateBase64 };
}Step 4: QR Code Generation
// src/ttn/qrcode-generator.ts
import QRCode from "qrcode";
import crypto from "crypto";
export async function generateInvoiceQRCode(params: {
invoiceId: string;
supplierTaxId: string;
issueDate: string;
totalAmount: number;
signatureValue: string;
}): Promise<string> {
// Generate a short hash for the sig parameter (first 8 characters of SHA256 hash)
const sigHash = crypto
.createHash("sha256")
.update(params.signatureValue)
.digest("hex")
.substring(0, 8);
// Generate the TTN invoice identifier (hash of invoice ID + tax number)
const iid = crypto
.createHash("sha256")
.update(`${params.invoiceId}:${params.supplierTaxId}`)
.digest("hex")
.substring(0, 12);
// Format the date for the QR code (YYYYMMDDHHMMSS)
const dt = params.issueDate.replace(/-/g, "") + "000000";
const verifyUrl = new URL("https://verify.elfatoora.tn/v");
verifyUrl.searchParams.set("iid", iid);
verifyUrl.searchParams.set("sid", params.supplierTaxId);
verifyUrl.searchParams.set("dt", dt);
verifyUrl.searchParams.set("amt", params.totalAmount.toFixed(3));
verifyUrl.searchParams.set("sig", sigHash);
// Generate the QR code in Base64 (for embedding in XML or PDF)
const qrCodeDataUrl = await QRCode.toDataURL(verifyUrl.toString(), {
errorCorrectionLevel: "M",
type: "image/png",
width: 200,
margin: 1,
});
// Return only the Base64 part (without the data:image/png;base64, prefix)
return qrCodeDataUrl.replace("data:image/png;base64,", "");
}Step 5: TTN API Submission and Response Handling
// src/ttn/client.ts
import axios, { AxiosError } from "axios";
import { getTTNAccessToken } from "./auth";
export interface TTNSubmitResponse {
invoiceId: string; // Internal TTN ID
status: "SUBMITTED" | "VALID" | "INVALID";
validationErrors?: Array<{
code: string;
field: string;
message: string;
}>;
timestamp: string;
acknowledgementUrl?: string;
}
export class TTNApiError extends Error {
constructor(
public readonly code: string,
public readonly details: string,
public readonly httpStatus: number
) {
super(`TTN API Error [${code}]: ${details}`);
}
}
export async function submitInvoice(
signedXml: string
): Promise<TTNSubmitResponse> {
const token = await getTTNAccessToken();
try {
const response = await axios.post<TTNSubmitResponse>(
`${process.env.TTN_API_BASE_URL}/api/v1/invoices`,
signedXml,
{
headers: {
Authorization: `Bearer ${token}`,
"Content-Type": "application/xml",
Accept: "application/json",
},
timeout: 30000, // 30 seconds
}
);
return response.data;
} catch (error) {
if (error instanceof AxiosError && error.response) {
const { status, data } = error.response;
// TEIF validation errors (400)
if (status === 400) {
throw new TTNApiError(
data.code || "VALIDATION_ERROR",
JSON.stringify(data.errors || data.message),
status
);
}
// Authentication error (401)
if (status === 401) {
// Invalidate the token cache and retry
throw new TTNApiError("AUTH_EXPIRED", "Token expired, please retry", 401);
}
// Rate limit exceeded (429)
if (status === 429) {
const retryAfter = error.response.headers["retry-after"] || "60";
throw new TTNApiError(
"RATE_LIMIT",
`API quota exceeded. Retry in ${retryAfter}s`,
429
);
}
throw new TTNApiError(
data.code || "API_ERROR",
data.message || "Unknown API error",
status
);
}
throw error;
}
}
export async function getInvoiceStatus(
ttnInvoiceId: string
): Promise<{ status: string; updatedAt: string }> {
const token = await getTTNAccessToken();
const response = await axios.get(
`${process.env.TTN_API_BASE_URL}/api/v1/invoices/${ttnInvoiceId}`,
{ headers: { Authorization: `Bearer ${token}` } }
);
return response.data;
}Orchestration: the Complete Service
// src/ttn/invoice-service.ts
import { generateTEIF, InvoiceData } from "./teif-generator";
import { signTEIF } from "./signer";
import { generateInvoiceQRCode } from "./qrcode-generator";
import { submitInvoice, TTNApiError } from "./client";
export interface InvoiceSubmissionResult {
ttnInvoiceId: string;
status: string;
submittedAt: string;
signatureValue: string;
qrCodeBase64: string;
}
export async function createAndSubmitInvoice(
invoiceData: InvoiceData
): Promise<InvoiceSubmissionResult> {
console.log(`[TTN] Generating invoice ${invoiceData.invoiceNumber}...`);
// 1. Generate the TEIF XML
const xmlContent = generateTEIF(invoiceData);
// 2. Sign the document
console.log("[TTN] Signing the XML document...");
const { signedXml, signatureValue } = signTEIF(xmlContent);
// 3. Calculate the total amount including tax for the QR code
const totalTTC = invoiceData.lines.reduce((sum, line) => {
const lineTotal = line.quantity * line.unitPrice;
return sum + lineTotal + (lineTotal * line.vatRate) / 100;
}, 0);
// 4. Generate the QR code
console.log("[TTN] Generating QR code...");
const qrCodeBase64 = await generateInvoiceQRCode({
invoiceId: invoiceData.invoiceNumber,
supplierTaxId: invoiceData.supplierTaxId,
issueDate: invoiceData.issueDate,
totalAmount: totalTTC,
signatureValue,
});
// 5. Submit to the TTN API with automatic retry
let lastError: Error | null = null;
for (let attempt = 1; attempt <= 3; attempt++) {
try {
console.log(`[TTN] Submitting to API (attempt ${attempt}/3)...`);
const response = await submitInvoice(signedXml);
console.log(
`[TTN] Invoice submitted successfully. TTN ID: ${response.invoiceId}`
);
return {
ttnInvoiceId: response.invoiceId,
status: response.status,
submittedAt: response.timestamp,
signatureValue,
qrCodeBase64,
};
} catch (error) {
if (error instanceof TTNApiError) {
// Non-recoverable errors — do not retry
if (error.httpStatus === 400 || error.httpStatus === 401) {
throw error;
}
// 429 or 5xx errors — wait and retry
lastError = error;
const delay = attempt * 2000; // 2s, 4s, 6s
console.warn(
`[TTN] Error ${error.code}, retrying in ${delay}ms...`
);
await new Promise((resolve) => setTimeout(resolve, delay));
} else {
throw error;
}
}
}
throw lastError || new Error("Submission failed after 3 attempts");
}Option 2: NGSign Middleware API
If you do not want to manage signing cryptography directly, TTN offers an intermediary service called NGSign API. This middleware handles the digital signature and significantly simplifies the integration.
Advantages:
- No local management of the PKCS#12 certificate
- Automatic updates when signing standards change
- Managed SaaS service by TTN
NGSign flow:
Your system
│ (unsigned TEIF XML)
▼
NGSign API (TTN)
│ (signing + timestamping)
▼
El Fatoora API (TTN)
│ (validation + archiving)
▼
Response to your system
// Submission via NGSign (without local signature management)
async function submitViaNGSign(xmlContent: string): Promise<string> {
const token = await getTTNAccessToken();
const response = await axios.post(
`${process.env.TTN_API_BASE_URL}/api/v1/ngsign/submit`,
{
xmlContent: Buffer.from(xmlContent).toString("base64"),
certificateAlias: process.env.TUNTRUST_CERT_ALIAS, // Alias pre-configured in NGSign
signingMode: "SERVER", // TTN signs with the certificate stored on their end
},
{
headers: {
Authorization: `Bearer ${token}`,
"Content-Type": "application/json",
},
}
);
return response.data.invoiceId;
}Note: NGSign requires depositing your TUNTRUST certificate with TTN, which raises security questions (the private key leaves your control). Evaluate this trade-off according to your security policy.
Option 3: Fully Custom Development
For large enterprises with specific requirements (multi-entity, complex ERP integration, very high volumes), a custom architecture may be necessary.
Typical architecture:
ERP / Source system
│ (invoice creation events)
▼
Message Queue (Redis / RabbitMQ)
│
▼
TTN Worker Pool (Node.js / microservice)
├── TEIF Generator
├── Signing Module (HSM or PKCS#12)
├── TTN API Client
└── Error Handler / Retry Manager
│
▼
Database (PostgreSQL)
├── Invoice statuses
├── TTN acknowledgements
└── Audit logs
This architecture enables processing thousands of invoices per day with maximum reliability, complete traceability, and automatic error recovery.
Testing with the TTN Sandbox
Before switching to production, you must validate your integration on the TTN test environment.
Sandbox Configuration
// src/config.ts
export const TTN_CONFIG = {
baseUrl:
process.env.NODE_ENV === "production"
? "https://api.elfatoora.tn"
: "https://api-sandbox.elfatoora.tn",
timeoutMs: 30000,
maxRetries: 3,
};Mandatory Test Scenarios
TTN requires validating at minimum the following scenarios in sandbox before production activation:
// src/tests/ttn-sandbox.test.ts
import { createAndSubmitInvoice } from "../ttn/invoice-service";
const baseInvoiceData = {
supplierTaxId: process.env.SUPPLIER_TAX_ID!,
supplierName: process.env.SUPPLIER_NAME!,
supplierAddress: "Test Address",
supplierCity: "Tunis",
supplierPostalCode: "1000",
};
// Test 1: Standard invoice with 19% VAT
async function testStandardInvoice() {
const result = await createAndSubmitInvoice({
...baseInvoiceData,
invoiceNumber: `TEST-${Date.now()}`,
issueDate: new Date().toISOString().split("T")[0],
dueDate: new Date(Date.now() + 30 * 86400000).toISOString().split("T")[0],
customerTaxId: "99999999A000TEST",
customerName: "Sandbox Test Customer",
customerAddress: "Test Customer Address",
customerCity: "Sfax",
customerPostalCode: "3000",
lines: [
{
id: 1,
description: "Test Service",
quantity: 1,
unitCode: "C62",
unitPrice: 100.0,
vatRate: 19,
},
],
});
console.assert(result.status === "VALID", "Test invoice must be VALID");
console.log("Test 1 passed:", result.ttnInvoiceId);
}
// Test 2: Credit note (avoir)
async function testCreditNote() {
// Similar but with InvoiceTypeCode = "381"
// ...
}
// Test 3: Multi-line invoice with mixed VAT rates
async function testMixedVATInvoice() {
// Lines with 19% and 7% VAT
// ...
}
// Run all tests
(async () => {
await testStandardInvoice();
await testCreditNote();
await testMixedVATInvoice();
console.log("All sandbox tests passed!");
})();Production Deployment Checklist
Before going live, validate every point on this list:
Security:
- The TUNTRUST certificate is stored in a secure vault (AWS Secrets Manager, HashiCorp Vault, or equivalent)
- The certificate password is never in plaintext in source code or logs
- TTN API credentials are in environment variables, outside the Git repository
- Access to the invoicing service is protected by internal authentication
Technical:
- All sandbox test scenarios passed with
VALIDstatus - Error handling covers codes 400, 401, 429, 500, 503
- Retry mechanism with exponential backoff is implemented
- Logs include the TTN ID for each submitted invoice
- The database retains the status and acknowledgement for each invoice
Legal and compliance:
- Production TTN access has been activated by TTN after test validation
- The DGI adherence declaration has been filed
- The archiving strategy (minimum 5 years) is in place
Operational:
- Alerts are configured for submission errors
- A monitoring dashboard for invoice tracking is available to the accounting department
- The remediation procedure in case of error is documented
Series Navigation
- Previous episode: Integration into your accounting software (coming soon)
- Next episode: Archiving and post-invoicing legal obligations (coming soon)
To revisit the technical fundamentals, see Episode 5: TEIF Format and Technical Specifications.
Does your company want to integrate TTN electronic invoicing without mobilizing your internal development resources? Noqta.tn offers a turnkey solution — from analyzing your existing system to production deployment, including training your teams. We have supported dozens of Tunisian companies through this transition.
Contact our technical team for a free diagnostic of your system and a project estimate.
Discuss Your Project with Us
We're here to help with your web development needs. Schedule a call to discuss your project and how we can assist you.
Let's find the best solutions for your needs.
Related Articles

TEIF Format and Technical Specifications for E-Invoicing in Tunisia
Technical guide to the TEIF (Tunisian Electronic Invoice Format): XML/XSD structure, mandatory fields, digital signature, QR code, and Web vs EDI integration modes.

Enrolling on El Fatoora: A Practical Step-by-Step Guide
Complete step-by-step guide to registering on Tunisia TradeNet's El Fatoora platform: prerequisites, required documents, enrollment portal, and technical validation.

AI SDK 4.0: New Features and Use Cases
Discover the new features and use cases of AI SDK 4.0, including PDF support, computer use, and more.