Odoo est puissant, mais son API n’est pas "plug and play" quand on vise une intégration robuste en production. Les équipes qui appellent directement l’API finissent souvent avec du code dupliqué, des mappings implicites, des retries non maîtrisés et des comportements différents d’un service à l’autre.
Chez Dawap, notre choix a été de développer un SDK interne sous Symfony qui encapsule les appels Odoo, standardise les DTO, impose des policies de résilience et expose des méthodes métier lisibles. Ce SDK est utilisé sur plusieurs projets, ce qui nous permet de capitaliser sur la connaissance réelle de l’API Odoo: ses patterns efficaces, ses limites, ses pièges, et les variantes selon versions/modules.
Pour une vue globale de notre offre sur les projets API complexes, vous pouvez consulter notre page Intégration API.
Dans la plupart des implémentations, on interagit avec Odoo via JSON-RPC sur un endpoint unique. La logique métier se fait ensuite via la méthode `execute_kw` qui cible un modèle Odoo (`res.partner`, `sale.order`, `product.product`, `account.move`, etc.) et une opération (`search_read`, `create`, `write`...).
Dans la collection Postman exploitée pour cet article, on retrouve deux modes d’authentification réellement utilisés: `web/session/authenticate` pour créer une session HTTP, et `common.authenticate` dans JSON-RPC pour obtenir un `uid` réutilisable dans les appels `object.execute_kw`. Notre SDK supporte ces deux approches pour rester compatible avec les contextes legacy et les architectures plus récentes.
Exemple de requête d’authentification JSON-RPC:
{
"jsonrpc": "2.0",
"method": "call",
"params": {
"service": "common",
"method": "authenticate",
"args": ["odoo_db", "api_user@example.com", "secret_password", {}]
},
"id": 1
}
La réponse contient généralement un `uid` qui sera réutilisé dans les appels `object.execute_kw`. Dans notre SDK, ce mécanisme est masqué derrière un provider d’auth pour éviter de le réimplémenter partout.
Le point le plus important pour maîtriser Odoo n’est pas seulement le transport, mais la discipline sur les opérations `search_count`, `search_read`, `read`, `create`, `write`, `unlink` et les méthodes métier comme `action_confirm`. Ce sont ces opérations qui structurent les endpoints fonctionnels, quel que soit le modèle.
Notre SDK sépare clairement trois responsabilités: `AuthProvider`, `RpcClient` et `DomainAdapters`. L’`AuthProvider` gère la récupération et le renouvellement de session, le `RpcClient` applique les règles de transport (timeouts, retries bornés, logs), et les `DomainAdapters` exposent des méthodes métier explicites.
Côté Symfony, cela s’intègre via des services DI configurés par environnement (`dev`, `staging`, `prod`), avec secrets externalisés. Le code applicatif appelle des méthodes de haut niveau comme: `upsertPartner`, `createSaleOrder`, `syncStockLevels`, sans manipuler directement JSON-RPC.
Exemple de payload JSON-RPC exécuté par le SDK pour lire des partenaires:
{
"jsonrpc": "2.0",
"method": "call",
"params": {
"service": "object",
"method": "execute_kw",
"args": [
"odoo_db",
42,
"secret_password",
"res.partner",
"search_read",
[[["email", "=", "client@example.com"]]],
{"fields": ["id", "name", "email", "phone", "write_date"], "limit": 1}
]
},
"id": 2
}
Cette structure peut sembler simple, mais la robustesse se joue dans les détails: normalisation des erreurs Odoo, traitement des `fault_code`, gestion des écritures concurrentes et garde-fous métier.
<?php
declare(strict_types=1);
namespace App\Infrastructure\Odoo;
use Symfony\Contracts\HttpClient\HttpClientInterface;
final class OdooRpcClient
{
public function __construct(
private HttpClientInterface $httpClient,
private string $baseUrl,
private string $db,
private int $uid,
private string $password,
) {}
public function executeKw(string $model, string $method, array $args = [], array $kwargs = []): array
{
$payload = [
'jsonrpc' => '2.0',
'method' => 'call',
'params' => [
'service' => 'object',
'method' => 'execute_kw',
'args' => [$this->db, $this->uid, $this->password, $model, $method, $args, $kwargs],
],
'id' => random_int(1000, 9999),
];
$response = $this->httpClient->request('POST', rtrim($this->baseUrl, '/').'/jsonrpc', [
'json' => $payload,
'timeout' => 20.0,
])->toArray(false);
if (isset($response['error'])) {
throw new OdooApiException((string) ($response['error']['message'] ?? 'Unknown Odoo error'));
}
return (array) ($response['result'] ?? []);
}
}
Cette couche ne contient aucun métier: elle ne fait que fiabiliser le transport. Les règles métier vivent dans des adapters dédiés (`PartnerGateway`, `OrderGateway`, `InvoiceGateway`) pour garder une architecture maintenable.
Voici les opérations que nous retrouvons le plus souvent sur des projets e-commerce, B2B ou omnicanaux. Les exemples ci-dessous sont volontairement concrets et proches des flux réels.
La collection fournie couvre les domaines principaux suivants: `product.template` et `product.product` (catalogue), `res.partner` (clients), `sale.order` et `sale.order.line` (commande), `purchase.order` (achats), `stock.picking` (logistique), `account.move` (facturation), `ir.attachment` (documents), avec les méthodes `search_count`, `search_read`, `read`, `create` et des actions de workflow.
Concrètement, ce ne sont pas des endpoints REST multiples mais un endpoint JSON-RPC unique (`/jsonrpc`) et un couple (`model`, `method`) qui définit l’opération métier. C’est exactement ce que notre SDK rend explicite côté code.
Payload de création d’un partenaire:
{
"model": "res.partner",
"method": "create",
"args": [{
"name": "ACME France",
"email": "ops@acme.fr",
"phone": "+33 1 44 55 66 77",
"street": "12 rue de l'Industrie",
"zip": "75010",
"city": "Paris",
"country_id": 76,
"company_type": "company",
"vat": "FR12345678901"
}]
}
Dans le SDK, on applique un mapping strict de normalisation (trim, casse, format téléphone, validation TVA) avant envoi, puis on journalise l’`id` Odoo obtenu pour la traçabilité.
Payload de création de commande avec lignes:
{
"model": "sale.order",
"method": "create",
"args": [{
"partner_id": 1284,
"client_order_ref": "WEB-2026-000481",
"date_order": "2026-01-23 09:12:33",
"order_line": [
[0, 0, {
"product_id": 502,
"name": "Abonnement Premium 12 mois",
"product_uom_qty": 1,
"price_unit": 399.00,
"tax_id": [[6, 0, [3]]]
}],
[0, 0, {
"product_id": 881,
"name": "Frais de mise en service",
"product_uom_qty": 1,
"price_unit": 49.00,
"tax_id": [[6, 0, [3]]]
}]
]
}]
}
Le point clé est l’idempotence: `client_order_ref` et une clé métier interne évitent les doublons lors des retries. Une fois créée, la commande est relue pour vérifier état, montants et taxes calculés par Odoo.
Après `create`, on appelle `action_confirm` sur `sale.order`, puis les étapes de facturation selon workflow client. Notre SDK encapsule ces transitions avec contrôles d’état pour éviter les enchaînements invalides.
{
"model": "sale.order",
"method": "action_confirm",
"args": [[9412]]
}
Pour la facturation (`account.move`), nous vérifions systématiquement les journaux, comptes, taxes et arrondis avant validation finale afin d’éviter les incidents comptables en clôture.
Les flux stock sont sensibles à la volumétrie et à la concurrence. Nous privilégions des synchronisations incrémentales basées sur `write_date`, avec découpage par fenêtres et vérification de cohérence par entrepôt/emplacement.
{
"model": "stock.quant",
"method": "search_read",
"args": [[["write_date", ">=", "2026-01-23 00:00:00"]]],
"kwargs": {
"fields": ["id", "product_id", "location_id", "quantity", "reserved_quantity", "write_date"],
"limit": 500
}
}
La collection Postman montre aussi des opérations très utilisées en run: `sale.order.search_count`, `sale.order.read`, `sale.order.line.search_read`, `purchase.order.search_read`, `purchase.order.line.search_read`, `stock.picking.search_read`, `account.move.search_read`, `account.move.read`, `ir.attachment.search_read`. Cette couverture est essentielle pour superviser tout le cycle.
Exemple de lecture de lignes de commandes, utile pour contrôler quantités, prix et taxes après import:
{
"jsonrpc": "2.0",
"method": "call",
"params": {
"service": "object",
"method": "execute_kw",
"args": [
"odoo_db",
42,
"secret_password",
"sale.order.line",
"search_read",
[[["order_id", "=", 318784]]],
{
"fields": [
"id",
"order_id",
"product_template_id",
"product_uom_qty",
"price_unit",
"price_subtotal",
"price_tax",
"price_total"
],
"limit": 50
}
]
},
"id": 44
}
Dans un projet d’intégration Odoo, certains endpoints sont structurants: ils couvrent la majorité des besoins métier et doivent être maîtrisés dès le cadrage technique. Voici la shortlist que nos équipes utilisent systématiquement pour concevoir un SDK robuste.
`web/session/authenticate` et `common.authenticate` sont les deux points d’entrée indispensables pour gérer l’identité technique des appels et la récupération d’un `uid` exploitable en `execute_kw`.
`product.template.search_count`, `product.template.search_read`, `product.template.read` et `product.product.read` permettent de couvrir la base catalogue, les variantes, les libellés localisés et les données de pricing/stock utiles aux canaux web et marketplaces.
`res.partner.search_count` et `res.partner.search_read` sont centraux pour l’upsert client, la déduplication et le contrôle de qualité des données de facturation/livraison.
`sale.order.search_count`, `sale.order.search_read`, `sale.order.read`, `sale.order.create` (ou équivalent) et `sale.order.action_confirm` couvrent l’essentiel du cycle de vie commande. Côté lignes, `sale.order.line.search_read` et `sale.order.line.create` sont critiques pour contrôler montants, taxes et quantités.
`purchase.order.search_read` et `purchase.order.line.search_read` servent à superviser les réceptions attendues, sécuriser le planning supply et fiabiliser les projections de stock.
`stock.picking.search_read` est un endpoint clé pour suivre les statuts de préparation/livraison et recouper les événements logistiques côté transporteurs.
`account.move.search_count`, `account.move.search_read` et `account.move.read` sont prioritaires pour le pilotage financier, la réconciliation et les contrôles post-import.
`ir.attachment.search_read` complète le dispositif pour récupérer les documents de preuve (factures PDF, justificatifs, pièces métier) et conserver une traçabilité exploitable en run.
Dans nos projets, nous structurons les flux en étapes explicites: collecte source, mapping, écriture Odoo, relecture de contrôle, publication d’événement interne et mise à jour des marqueurs de reprise. Cette granularité simplifie le debug et sécurise la reprise après incident.
Exemple concret "commande web vers Odoo": 1. Validation du payload entrant + enrichissement référentiel. 2. Upsert client (`res.partner`). 3. Upsert produits/références si nécessaire. 4. Création `sale.order`. 5. Confirmation selon règles métier. 6. Création/validation de facture selon politique finance. 7. Ack applicatif uniquement après contrôles de cohérence.
Ce pipeline est orchestré avec Symfony Messenger dans les contextes asynchrones, ce qui permet de lisser la charge, isoler les erreurs et rejouer une étape sans rejouer tout le flux.
<?php
declare(strict_types=1);
namespace App\Application\Order\Command;
final class ExportOrderToOdooCommand
{
public function __construct(
public readonly string $orderId,
public readonly string $idempotencyKey,
) {}
}
namespace App\Application\Order\Handler;
use App\Application\Order\Command\ExportOrderToOdooCommand;
use App\Domain\Order\OrderRepository;
use App\Domain\Odoo\OrderGateway;
final class ExportOrderToOdooHandler
{
public function __construct(
private OrderRepository $orders,
private OrderGateway $odooGateway,
) {}
public function __invoke(ExportOrderToOdooCommand $command): void
{
$order = $this->orders->getById($command->orderId);
$payload = OdooOrderPayloadFactory::fromDomainOrder($order, $command->idempotencyKey);
$this->odooGateway->createOrUpdateOrder($payload);
}
}
Ici, la couche Application orchestre, la couche Domain décide, et la couche Infrastructure parle à Odoo. Cette séparation évite d’avoir des appels JSON-RPC disséminés dans les contrôleurs Symfony.
# config/packages/messenger.yaml
framework:
messenger:
transports:
odoo_async:
dsn: '%env(MESSENGER_TRANSPORT_DSN)%'
retry_strategy:
max_retries: 5
delay: 1000
multiplier: 2
max_delay: 30000
routing:
'App\Application\Order\Command\ExportOrderToOdooCommand': odoo_async
<?php
declare(strict_types=1);
namespace App\Infrastructure\Symfony\Messenger;
use Symfony\Component\Messenger\MessageBusInterface;
use App\Application\Order\Command\ExportOrderToOdooCommand;
final class OrderExportDispatcher
{
public function __construct(private MessageBusInterface $bus) {}
public function dispatch(string $orderId): void
{
$this->bus->dispatch(new ExportOrderToOdooCommand(
orderId: $orderId,
idempotencyKey: hash('sha256', 'odoo-'.$orderId),
));
}
}
Avec cette approche, les pics de charge sont absorbés par la queue, les retries sont homogènes, et les jobs peuvent être rejoués proprement sans dupliquer les écritures côté ERP.
Une intégration Odoo sérieuse vit dans les cas dégradés. Notre SDK applique des politiques de retries bornées (backoff + jitter), distingue les erreurs retryables des erreurs métier, et protège les écritures par clés d’idempotence métier.
Nous classons les erreurs en quatre catégories: 1. Erreurs techniques transitoires (timeout, réseau, 5xx). 2. Erreurs d’authentification/autorisation. 3. Erreurs de contrat (payload invalide, champ manquant, type incorrect). 4. Erreurs métier (référentiel absent, transition d’état impossible, règle comptable bloquante). Chaque catégorie a sa stratégie de traitement, sa journalisation et son niveau d’alerte.
Côté produit, ce cadre apporte un bénéfice immédiat: les incidents deviennent explicables, traçables et réparables sans bricolage en base ni patch urgent dans plusieurs services.
<?php
private function normalizeOdooError(array $error): string
{
$message = (string) ($error['message'] ?? 'Odoo error');
$data = (string) ($error['data']['message'] ?? '');
if (str_contains($data, 'AccessError')) {
return 'auth_or_permission_error';
}
if (str_contains($data, 'ValidationError')) {
return 'contract_or_business_validation_error';
}
if (str_contains($message, 'timeout') || str_contains($message, 'Connection')) {
return 'transient_network_error';
}
return 'unknown_odoo_error';
}
Nous couvrons le SDK Odoo avec une stratégie en couches: tests unitaires (mappers, validators, normalizers), tests d’intégration (RPC réel/simulé), tests de non-régression métier (parcours de bout en bout).
Les tests d’intégration vérifient les points critiques: auth et renouvellement, création/lecture d’entités, transitions d’état, gestion des erreurs Odoo, idempotence en retry, cohérence des montants et synchronisation incrémentale.
Pour la méthodologie détaillée, voir aussi: Tests API, stratégie et bonnes pratiques.
Dans ce contexte Odoo, nous priorisons les parcours critiques vus dans la collection: lecture catalogue, upsert partenaire, création commande, lecture lignes, vérification facture et récupération des pièces jointes. Tant que ces flux ne passent pas en nominal et en dégradé, la mise en production est bloquée.
Nous maintenons des jeux de données réalistes (B2B, B2C, multi-taxes, remises, avoirs, retours) et des mocks pour reproduire les situations les plus coûteuses: latence, payload partiel, réponse incohérente, erreur intermittente.
Postman est utilisé quotidiennement pour explorer, documenter et rejouer les scénarios API. Les collections sont versionnées et alignées avec les contrats techniques pour éviter les écarts entre documentation et implémentation.
Sur Odoo, nous nous appuyons explicitement sur des dossiers Postman couvrant Produits, Partenaires, Commandes, Achats, Stock, Factures et Pièces jointes. Ce découpage est repris dans la structure de notre SDK afin que chaque endpoint métier Postman corresponde à une méthode testable côté Symfony.
Article annexe utile sur l’outillage: Postman pour industrialiser vos tests API.
Chaque appel SDK est corrélé avec un trace id et des logs structurés (modèle, méthode, durée, statut, retry_count, erreur normalisée). On suit en production la latence p95/p99, le taux d’échec par endpoint, les doublons évités, et le backlog des flux asynchrones.
Nous maintenons des runbooks par type d’incident (auth expirée, quota, mapping invalide, conflit métier), ce qui réduit fortement le temps de diagnostic et de remédiation.
Complément recommandé: Observabilité et runbooks API.
Ce guide central présente notre logique de conception des connecteurs ERP sous Symfony, les choix d’architecture, la stratégie de tests et les règles de robustesse run appliquées de manière homogène.
Lire le guide des SDK API ERP Dawap
Approche orientée synchronisation clients, commandes et facturation pour les environnements Sage les plus courants.
Consulter l’article SDK API ERP Sage
Focus sur la fiabilité transactionnelle, la maîtrise des flux critiques et la gouvernance des erreurs d’intégration.
Consulter l’article SDK API ERP SAP
Implémentation dédiée aux scénarios multi-entités et à la synchronisation robuste entre outils métier et ERP.
Consulter l’article SDK API ERP Microsoft Dynamics 365
Retour d’expérience sur les patterns d’intégration Divalto pour les flux catalogue, commandes et données opérationnelles.
Consulter l’article SDK API ERP Divalto
Architecture de connecteur orientée fiabilité métier et observabilité pour les projets ERP internationaux.
Consulter l’article SDK API ERP Oracle NetSuite
Cas d’usage concrets autour des flux PME, avec un focus sur la simplicité d’exploitation et la maintenabilité.
Consulter l’article SDK API ERP Dolibarr
Connecteur pensé pour les besoins retail et omnicanaux, avec priorisation des performances et de la cohérence data.
Consulter l’article SDK API ERP Cegid
Implémentation ciblée sur les flux de gestion quotidienne et l’industrialisation des échanges ERP/applications web.
Consulter l’article SDK API ERP EBP
Patterns d’intégration flexibles pour les organisations qui recherchent personnalisation, modularité et évolutivité.
Consulter l’article SDK API ERP Axelor
Approche pragmatique pour relier CRM, commerce et finance autour d’un socle API cohérent et pilotable.
Consulter l’article SDK API ERP Sellsy
Connecteur conçu pour fluidifier les processus commerciaux et financiers avec un time-to-market maîtrisé.
Consulter l’article SDK API ERP Axonaut
Industrialisation des échanges Incwo avec des conventions communes de mapping, traçabilité et reprise sur incident.
Consulter l’article SDK API ERP Incwo
Stratégie d’intégration pour des environnements complexes où la gouvernance des flux et la conformité sont clés.
Consulter l’article SDK API ERP Oracle Fusion
Retour technique sur l’orchestration de flux industriels et supply avec contraintes de volumétrie et de robustesse.
Consulter l’article SDK API ERP Infor M3
Sur Odoo, la qualité d’une intégration ne se limite pas à la réussite d’un appel JSON-RPC. Elle se mesure à la capacité du système à rester cohérent dans le temps: gestion des statuts, prévention des doublons, reprise sur incident, et lisibilité des flux pour les équipes run.
Le cadrage initial doit couvrir quatre dimensions en parallèle: 1. périmètre fonctionnel priorisé (clients, commandes, factures, stocks), 2. contrat technique explicite (payloads, erreurs, timeouts, retries), 3. stratégie de test réaliste (nominaux, dégradés, non-régression), 4. modèle d’exploitation (monitoring, alerting, runbooks, ownership). C’est ce qui transforme un connecteur “fonctionnel” en socle fiable pour la croissance.
Dans nos projets, le SDK Symfony sert justement à standardiser ces exigences: mêmes conventions techniques, mêmes mécanismes de résilience, même qualité d’observabilité. Le résultat attendu est concret: moins d’incidents évitables, time-to-market plus court, et meilleure maîtrise des évolutions API sur le long terme.
Si vous voulez cadrer une intégration Odoo ou un périmètre ERP multi-systèmes, consultez aussi: Intégration API ERP Odoo et notre offre Intégration API.
Nous accompagnons les équipes produit et techniques dans la conception, l’intégration et l’industrialisation d’APIs. Notre mission : construire des architectures robustes, sécurisées et évolutives, alignées sur vos enjeux métier et votre croissance.
Vous préférez échanger ? Planifier un rendez-vous
SAP implique des contraintes élevées sur la volumétrie, la cohérence des données et la robustesse des workflows critiques. Nous y détaillons notre SDK Symfony pour orchestrer les flux logistiques et financiers avec contrôle d'état strict, résilience réseau et supervision orientée production.
Dynamics 365 nécessite des échanges API REST sécurisés et cohérents sur plusieurs domaines métier simultanément. Ce guide explique notre SDK Symfony pour synchroniser ventes, clients, stocks et finance, tout en conservant une observabilité fine et une gestion d'incidents pilotable.
Les projets Divalto demandent de concilier contraintes terrain, flux commerciaux et exigences logistiques. L'article présente notre SDK Symfony avec mappings versionnés, stratégie de retry adaptée et normalisation des échanges pour stabiliser les opérations au quotidien.
Les intégrations Sage demandent une forte rigueur sur les écritures, les référentiels et les statuts comptables. Cet article montre comment Dawap conçoit un SDK Symfony pour normaliser les flux, sécuriser la reprise sur incident et fournir une observabilité exploitable par les équipes run.
Nous accompagnons les équipes produit et techniques dans la conception, l’intégration et l’industrialisation d’APIs. Notre mission : construire des architectures robustes, sécurisées et évolutives, alignées sur vos enjeux métier et votre croissance.
Vous préférez échanger ? Planifier un rendez-vous