ERP integration guide
Three ways to connect Tauraco to business or accounting software, from the simplest to the most integrated:
- Files: CSV, XLSX or FEC exports, one-off or scheduled (see the exports guide), dropped on an SFTP server if needed.
- Public API: your software reads from and writes to Tauraco with a
client_credentialstoken (synchronisation sample below). - Connectors: Tauraco pushes documents to your software, with a declared accounting mapping and explained rejections.
Status on 2026-11-05: connectors are open in production since 2026-11-05; they were developed and tested against simulated servers only, never against a vendor's real environment. Communication rule: until a connector has been built and tested on a real vendor environment, Tauraco announces a "compatible format", never a "native integration".
Connector maturity
| Connector | Planned maturity | What it does |
|---|---|---|
| Generic CSV / XLSX | available | export profiles (columns, separators, date formats) |
| Generic REST | available | authenticated JSON push to an endpoint you designate, with the same protections as webhooks |
| Outbound SFTP | available | file drop on your server; known_hosts host key required, host and directory fixed |
| Odoo 17 / 18 / 19 | beta | connection test, pushing partners and invoices as drafts; no pull in v1; tested against a simulated server |
| Sage, QuickBooks, EBP, Pennylane (files) | configuration required | CSV profiles in the format the software expects, without an API |
| SAP, Dynamics 365, Oracle ERP, PeopleSoft, NetSuite, Cegid, Xero, Sage X3, Sage API (Accounting, Intacct, Active) | not supported | documented fact sheet, no exchange |
Per-vendor details (API, authentication, limits, SYSCOHADA points) are in the connector catalogue shipped with the platform documentation.
Accounting mapping
Tauraco never reclassifies an account: it applies the mapping you declare (accounts, taxes, currencies, journals, counterparties) and keeps the SYSCOHADA numbering. Every document that cannot be sent is rejected with a reason, without stopping the batch (list aligned on 2026-11-05 with the reasons of the mapping engine):
| Reason | Meaning |
|---|---|
missing_required | mandatory field missing |
unmapped_account | account without a mapping |
unmapped_tax | tax without a mapping |
currency_conversion_forbidden | different currency without an allowed conversion (never a silent conversion) |
invalid_date | unreadable date |
invalid_amount | invalid amount (not a decimal, not finite, or more precise than the currency) |
unknown_partner | counterparty without a mapping on the connection (to be declared) |
duplicate | document already sent unchanged, or present twice in the batch: it is counted skipped_duplicate (item status, not a rejection) |
remote_rejected | refused by the remote software (truncated message) |
invalid_value | unconvertible value (for example a currency code that is not a three-letter ISO 4217 code) |
A batch with rejections ends PARTIAL; a reconciliation report compares local and remote totals. A transport error stops the batch (FAILED). A simulation (dry_run) applies the mapping without sending anything.
Synchronise accounting software through the API
The sample reads the invoices modified since the previous run (updated_after, with a 5-minute overlap), deduplicates by identifier and writes a balanced sales journal (customer debit, sales and VAT credit). The accounts are those of your chart of accounts; amounts stay Decimal.
"""Synchronisation incrémentale des factures vers un logiciel comptable (journal des ventes CSV).
Incremental invoice sync to accounting software (sales journal as CSV).
Principe : on relit les factures modifiées depuis le dernier passage (filtre
``updated_after``), avec un recouvrement de 5 minutes, et on dédoublonne par
identifiant de facture : une facture déjà écrite n'est pas réécrite. La table de
correspondance des comptes est celle de VOTRE plan comptable : Tauraco ne reclasse
aucun compte. Les montants restent des chaînes décimales (``Decimal``), jamais des
flottants.
Usage : TAURACO_CLIENT_ID=… TAURACO_CLIENT_SECRET=… python3 synchro_comptable.py
"""
from __future__ import annotations
import csv
import json
import os
from datetime import datetime, timedelta, timezone
from decimal import Decimal
from pathlib import Path
from typing import Iterable, Iterator, List
from client_credentials import get_token, iterate
STATE_FILE = Path(os.environ.get("SYNC_STATE", "etat_synchro.json"))
OUTPUT_FILE = Path(os.environ.get("SYNC_OUTPUT", "journal_ventes.csv"))
OVERLAP = timedelta(minutes=5)
#: Correspondance déclarée par l'entreprise (exemple SYSCOHADA : 411 clients, 701 ventes).
ACCOUNT_CUSTOMERS = os.environ.get("ACCOUNT_CUSTOMERS", "411100")
ACCOUNT_SALES = os.environ.get("ACCOUNT_SALES", "701100")
ACCOUNT_VAT = os.environ.get("ACCOUNT_VAT", "443100")
SYNCED_STATUSES = {"SENT", "PARTIALLY_PAID", "PAID", "OVERDUE"}
def journal_lines(invoice: dict) -> List[List[str]]:
"""Trois lignes équilibrées d'une facture : débit client, crédit ventes HT, crédit TVA."""
total = Decimal(invoice["total"])
subtotal = Decimal(invoice.get("subtotal") or invoice["total"])
tax = Decimal(invoice.get("tax_total") or "0")
if subtotal + tax != total:
raise ValueError(f"facture {invoice['number']} : HT + taxes ≠ TTC, à vérifier")
label = f"{invoice['number']} {invoice.get('customer_name') or ''}".strip()
rows = [[invoice["issue_date"], "VT", invoice["number"], ACCOUNT_CUSTOMERS, label, str(total), "0"],
[invoice["issue_date"], "VT", invoice["number"], ACCOUNT_SALES, label, "0", str(subtotal)]]
if tax:
rows.append([invoice["issue_date"], "VT", invoice["number"], ACCOUNT_VAT, label, "0", str(tax)])
return rows
def new_invoices(invoices: Iterable[dict], already_synced: set) -> Iterator[dict]:
for invoice in invoices:
if invoice["status"] in SYNCED_STATUSES and invoice["id"] not in already_synced:
already_synced.add(invoice["id"])
yield invoice
def main() -> None:
state = json.loads(STATE_FILE.read_text()) if STATE_FILE.exists() else {"since": None, "synced": []}
started = datetime.now(timezone.utc)
params = {"limit": "200"}
if state["since"]:
since = datetime.fromisoformat(state["since"].replace("Z", "+00:00")) - OVERLAP
params["updated_after"] = since.isoformat().replace("+00:00", "Z")
token = get_token(os.environ["TAURACO_CLIENT_ID"], os.environ["TAURACO_CLIENT_SECRET"],
"invoices:read", tenant=os.environ.get("TAURACO_TENANT"))["access_token"]
synced = set(state["synced"])
with OUTPUT_FILE.open("a", newline="", encoding="utf-8") as handle:
writer = csv.writer(handle, delimiter=";")
for invoice in new_invoices(iterate("/invoices", token, params), synced):
writer.writerows(journal_lines(invoice))
state = {"since": started.isoformat().replace("+00:00", "Z"), "synced": sorted(synced)}
STATE_FILE.write_text(json.dumps(state))
if __name__ == "__main__":
main()
It relies on the client of the python/client_credentials.py sample (same directory).
Business formats
- FEC: available for a company under the French accounting regime (
fec), refused elsewhere. - Factur-X (CII EN 16931): developed, shipped "beta, not validated by Schematron". UBL deferred.
- ISO 20022:
camt.053(statements) developed, "experimental, not validated";pain.001(transfers) not supported in v1, since no beneficiary bank details are served; XSD validation only activates when the operator drops the official schemas.
No compliance is claimed for these formats.
Outbound connection security
Your server addresses (REST, Odoo, SFTP) are checked at creation and on every run: full DNS resolution, public addresses only, allowed ports (443/8443 for HTTPS, 22/2222 for SFTP), no redirect. Connection secrets are encrypted and never shown again. A connection test returns a generic message (unreachable, host_key_mismatch, auth_failed, timeout), never the remote server's banner.