L'API Goodstock

Lire et écrire vos données depuis vos propres outils.

Votre instance expose deux interfaces. L'une est dédiée aux articles et tient en deux appels ; l'autre ouvre le modèle complet.

L'interface articles répond à la question qu'on pose le plus souvent : que sait Goodstock de cette référence, et comment lui transmettre ce que j'en sais. Un appel pour lire, un pour écrire. Un jeton, du JSON.

L'accès au modèle se fait par clé d'API. Tout ce que vous voyez à l'écran s'y lit et s'y écrit, avec les droits de l'utilisateur dont la clé est employée : commandes fournisseurs, attendus de réception, historiques de statuts, coûts de revient.

Avant de commencer

Le jeton de l'interface articles vous est remis par nos soins. La clé d'API, elle, se crée depuis votre profil. Écrivez-nous à contact@very-goodstock.com en indiquant ce que vous comptez faire — cela nous évite de vous ouvrir plus de droits que nécessaire.

Interface articles

Deux appels, un jeton

Deux portes, selon ce que vous avez à faire

Interface articles · en-tête X-Auth-Key

Lire et écrire une fiche produit : coûts, fournisseur, points de mesure de stock, relations de remplacement. Deux appels et un jeton, en JSON. Le détail.

Accès au modèle · clé d'API

Tout le reste : commandes fournisseurs et leurs lignes, attendus de réception, historiques journaliers, coûts de revient, fermetures fournisseurs. Le détail.

La première est taillée pour le cas le plus fréquent, et ne demande rien d'autre qu'un jeton. La seconde ouvre le modèle entier, avec les droits de celui qui a créé la clé — ni plus, ni moins.

L'adresse de votre instance vous a été communiquée à l'ouverture du compte, sous la forme https://votre-instance.very-goodstock.com. Chaque requête porte deux en-têtes :

En-têteValeur
X-Auth-Keyle jeton fourni par Goodstock
Content-Typeapplication/json
Lecture

POST /api/v1/get_product

Renvoie ce que Goodstock sait d'une référence : ses coûts, son fournisseur principal, les autres fournisseurs connus, et les commandes d'achat planifiées qui la concernent.

POST /api/v1/get_product
X-Auth-Key: token_xxxxxxxxxxxxxxxxx
Content-Type: application/json

{ "product_code": "123456" }
{
  "id": 22112,
  "name": "Savon de Marseille 300 g",
  "real_cost": 4.15,
  "average_cost": 4.15,
  "main_supplier": {
    "id": 156,
    "name": "Savonnerie du Midi",
    "currency": "EUR",
    "purchase_price": 4.36,
    "currency_for_conversion": "EUR",
    "converted_purchase_price": 4.36,
    "lead_time": 45,
    "main_supplier_end_date": false
  },
  "suppliers": [ ... ],
  "purchases": [
    {
      "line_id": 88431,
      "order": { "id": 4148, "name": "4148", "supplier_name": "Savonnerie du Midi" },
      "date_planned": "2027-02-08",
      "quantity": 144,
      "quantity_received": 0,
      "supplier": { ... }
    }
  ]
}

Les champs de la réponse

ChampTypeDescription
idintIdentifiant de l'article dans Goodstock
namestringDésignation, informations de variante comprises
real_costfloatCoût de revient actuel
average_costfloatCoût moyen pondéré
main_supplierobjectFournisseur principal
…namestringNom du fournisseur
…currencystringDevise du fournisseur
…purchase_pricefloatPrix d'achat dans la devise du fournisseur
…currency_for_conversionstringDevise de la société utilisatrice
…converted_purchase_pricefloatPrix d'achat converti dans cette devise
…lead_timeintDélai d'approvisionnement, en jours
…main_supplier_end_datedateFin de validité du statut de fournisseur principal
suppliersarrayLes autres fournisseurs, mêmes champs que ci-dessus
purchasesarrayLes lignes de commande d'achat planifiées
…line_idintIdentifiant de la ligne
…orderobjectCommande porteuse : id, name, supplier_name
…date_plannedstringDate de réception prévue
…quantityintQuantité commandée
…quantity_receivedintQuantité déjà reçue
out_of_stock_datedateDate de rupture prévue ; si l'article est déjà en rupture, dernier jour où il restait du stock
out_of_stock_date_infiniteboolRupture trop lointaine pour être datée

Codes de retour

CodeSignification
401Jeton absent ou invalide
400Requête malformée — product_code manquant, ou paramètre du planning invalide, par exemple
403Article introuvable
500Erreur interne : signalez-la nous, elle est tracée de notre côté
import requests

URL = "https://votre-instance.very-goodstock.com"
ENTETES = {"X-Auth-Key": "token_xxxxxxxxxxxxxxxxx", "Content-Type": "application/json"}

reponse = requests.post(
    f"{URL}/api/v1/get_product",
    headers=ENTETES,
    json={"product_code": "123456"},
    timeout=30,
)
reponse.raise_for_status()
article = reponse.json()
print(article["real_cost"], article["main_supplier"]["lead_time"])
Sur votre site marchand

Afficher le planning prévisionnel du stock sur votre site marchand

Certains e-commerçants aiment afficher, sur la page produit, l'écoulement prévisionnel du stock et les réceptions à venir. Ajoutez "stock_planning": "html" à la requête : la réponse contient alors stock_planning_html, un bloc prêt à insérer dans la page, SVG et styles compris, sans JavaScript, adapté au mobile. Avec "data", vous recevez plutôt les points de la courbe et les réceptions, pour la dessiner vous-même.

POST /api/v1/get_product

{ "product_code": "HJKL_8956452", "stock_planning": "html" }
HJKL_8956452 Vis à tête fraisée de 12x56mm
Rupture dans 29 j
Stock
180 29 j de stock
Rupture
7 nov. dans 29 jours
Réappro
25 nov. +500 unités

Article fictif, tel qu'affichable sur votre page produit si vous le souhaitez, à vos couleurs.

L'appel part du serveur de votre site, jamais du navigateur de vos visiteurs : la clé d'API doit rester de votre côté. La projection évolue lentement, la réponse peut donc être gardée en cache quelques heures.

Écriture

PATCH /api/v1/product/<référence>

Met à jour le nom, le stock, la visibilité web, le fournisseur et ses conditions d'achat, le prix de vente, le statut « en soldes » et les relations de remplacement. Tous les champs sont optionnels : seuls ceux que vous envoyez sont modifiés. L'appel s'applique à l'article désigné et à toutes ses variantes.

Une modification du stock, de la visibilité web, du statut « en soldes » ou du groupe de prix de vente crée un point de mesure dans l'historique de l'article, exactement comme le ferait une synchronisation par connecteur.

PATCH /api/v1/product/123456
X-Auth-Key: token_xxxxxxxxxxxxxxxxx
Content-Type: application/json

{
  "stock": 150,
  "supplier_name": "Savonnerie du Midi",
  "supplier_price": 4.36,
  "supplier_packaging_qty": 100.0,
  "supplier_min_qty": 200.0,
  "sell_price": 8.50,
  "vat_rate": 0.20,
  "sell_price_incl_tax": 10.20,
  "discounted": true
}

Les champs du corps

ChampTypeDéfautDescription
namestring—Nouvelle désignation. Appliquée au seul article dont le code correspond exactement, pas aux variantes. Une chaîne vide est ignorée.
stockint—Nouvelle quantité en stock.
datestring—Bascule l'appel en mode historique : l'article n'est pas modifié, un point de mesure daté est ajouté. Voir plus bas.
web_activebool—Visibilité sur le site marchand.
supplier_namestring—Nom du fournisseur. Obligatoire dès qu'un autre champ fournisseur est renseigné.
supplier_product_codestring—Référence de l'article chez le fournisseur.
supplier_product_namestring—Désignation chez le fournisseur.
supplier_pricefloat—Prix d'achat unitaire, hors taxes sauf indication contraire.
supplier_unit_qty_factorfloat1.0Conversion entre l'unité fournisseur et la vôtre — 12 si le fournisseur vend par carton de douze.
supplier_packaging_qtyfloat—Quantité de conditionnement.
supplier_min_qtyfloat—Quantité minimale de commande.
sell_pricefloat—Prix de vente hors taxes.
vat_ratefloat—Taux de TVA — 0.20 pour 20 %.
sell_price_incl_taxfloat—Prix de vente toutes taxes comprises.
discountedbool—Statut « en soldes ». Il n'y a pas de prix remisé séparé : ce booléen indique que le sell_price en vigueur est un prix soldé.
replaced_skusarray—Références remplacées par cet article. [] supprime toutes les relations.
replaced_by_skustring—Référence qui remplace celui-ci. "" supprime la relation.
action_if_supplier_not_foundstringraiseFournisseur introuvable : raise lève une erreur, create le crée, skip ignore silencieusement.
create_unknown_productsboolfalseCrée l'article s'il est introuvable, au lieu de refuser l'appel.
multiply_price_by_unit_factorboolfalseMultiplie supplier_price par le facteur d'unité avant enregistrement.
prices_include_taxboolfalseConsidère supplier_price comme TTC et le ramène hors taxes.

Règles de validation

  • sell_price, vat_rate et sell_price_incl_tax vont ensemble : les trois, ou aucun. Une partie seulement est refusée.
  • Dès qu'un champ fournisseur est renseigné, supplier_name devient obligatoire.
  • replaced_skus doit être une liste de chaînes, replaced_by_sku une chaîne, discounted un booléen.
  • Un point de mesure est créé dès que stock, web_active, discounted ou le groupe de prix de vente change.
  • Les articles de type pack n'acceptent pas cet appel.

Codes de retour

CodeSignification
200Succès — le corps de la réponse est vide
401Jeton absent ou invalide
422Données refusées : article ou fournisseur introuvable, groupe de prix incomplet, replaced_skus mal formé, article de type pack
400Erreur interne
Reprise de données

Injecter un historique de stock

Le champ date change la nature de l'appel. C'est le mode à employer pour reconstituer un historique connu par ailleurs — une reprise de données, ou une remontée quotidienne d'inventaires passés.

Le même appel, avec et sans date

Sans date

La quantité devient le stock d'aujourd'hui. C'est une correction d'inventaire : elle déplace le niveau courant, et le passé n'est pas touché.

Avec date

La quantité est enregistrée à cette date-là dans l'historique. Le stock d'aujourd'hui ne bouge pas, et la qualification des journées passées est recalculée.

Confondre les deux est l'erreur la plus coûteuse de la reprise : un historique injecté sans date écrase le stock courant référence après référence, et l'inventaire devient faux sans que rien ne le signale.
  • L'article n'est jamais modifié : son stock, son prix et sa visibilité courants restent intacts.
  • Un point de mesure est ajouté à la date demandée, sur l'article et ses variantes.
  • Ce point est marqué « mesure uniquement » : il alimente les statistiques et les calculs de rupture, sans compter comme une synchronisation complète.
  • Les champs non fournis reprennent la valeur actuelle de l'article.
  • Rejouer le même appel à la même date met à jour le point existant plutôt que d'en créer un second : l'import est rejouable sans risque de doublon.
  • Seuls stock, le groupe de prix de vente, web_active et discounted sont acceptés dans ce mode, et au moins l'un d'eux est requis. La date ne peut pas être dans le futur.

Formats de date acceptés

ÉcritureInterprétation
2026-07-1515 juillet 2026 à 00:00:00 UTC
2026-07-15 09:30:0015 juillet 2026 à 09:30:00 UTC
2026-07-15T09:30:0015 juillet 2026 à 09:30:00 UTC
2026-07-15T11:30:00+02:0015 juillet 2026 à 09:30:00 UTC — le décalage est converti
2026-07-15T09:30:00Z15 juillet 2026 à 09:30:00 UTC

Sans fuseau indiqué, la date est lue comme déjà exprimée en UTC, qui est le fuseau de stockage de Goodstock.

# Reprise de trois mois de niveaux de stock pour une référence.
import requests
from datetime import date, timedelta

URL = "https://votre-instance.very-goodstock.com"
ENTETES = {"X-Auth-Key": "token_xxxxxxxxxxxxxxxxx", "Content-Type": "application/json"}

jour = date(2026, 4, 1)
while jour <= date(2026, 6, 30):
    requests.patch(
        f"{URL}/api/v1/product/123456",
        headers=ENTETES,
        json={"date": jour.isoformat(), "stock": niveau_connu(jour)},
        timeout=30,
    ).raise_for_status()
    jour += timedelta(days=1)
Accès au modèle

Une clé d'API, et tout le reste s'ouvre

Goodstock est construit sur Odoo, dont l'API externe est publique et documentée. La clé d'API remplace le mot de passe : elle se crée depuis votre profil, dans les préférences du compte, onglet « Sécurité du compte ». Elle n'expose que ce que son porteur a le droit de voir, et se révoque sans toucher au mot de passe.

C'est la voie à prendre dès qu'on sort de la fiche article : commandes fournisseurs et leurs lignes, attendus de réception, historiques de statuts journaliers, coûts de revient, périodes de fermeture des fournisseurs.

# Tout passe par un point d'entrée unique, en JSON.
import requests

URL = "https://votre-instance.very-goodstock.com/jsonrpc"
BASE, LOGIN, CLE = "gs-votre-base", "vous@exemple.fr", "votre_cle_api"

def appel(modele, methode, args, kwargs=None):
    corps = {
        "jsonrpc": "2.0", "method": "call",
        "params": {
            "service": "object", "method": "execute_kw",
            "args": [BASE, UID, CLE, modele, methode, args, kwargs or {}],
        },
    }
    reponse = requests.post(URL, json=corps, timeout=60).json()
    if "error" in reponse:
        raise RuntimeError(reponse["error"]["data"]["message"])
    return reponse["result"]

# L'identification, une fois, donne l'identifiant d'utilisateur.
UID = requests.post(URL, json={
    "jsonrpc": "2.0", "method": "call",
    "params": {"service": "common", "method": "login", "args": [BASE, LOGIN, CLE]},
}, timeout=30).json()["result"]

# Les commandes en cours de réception, en un appel.
commandes = appel(
    "goodstock.purchase.order", "search_read",
    [[("state_id.name", "=", "En cours de récep.")]],
    {"fields": ["name", "partner_id", "date_order", "amount_total"], "limit": 50},
)

Le même point d'entrée sert à lire (search_read), à écrire (write, create) et à appeler les méthodes métier du modèle.

Les modèles que l'on interroge le plus

ModèleCe qu'il porte
goodstock.productLes articles, leurs coûts, leurs niveaux de rotation
goodstock.purchase.orderLes commandes fournisseurs et leur étape
goodstock.purchase.order.lineLes lignes, avec la quantité proposée, la trace du calcul et les avertissements
goodstock.awaitingLes attendus de réception
goodstock.reception.lineLes lignes de réception, quantité par quantité
goodstock.product.supplierinfoLes conditions d'achat : prix, délai, franco, conditionnement
goodstock.partner.leaveLes périodes de fermeture des fournisseurs
Intelligence artificielle

Ce qu'un assistant peut faire dans Goodstock

Pour le brancher, il faut une clé d'API liée à un utilisateur Goodstock. C'est tout.

Goodstock est construit sur Odoo, dont l'interface de programmation est publique et documentée depuis des années. Un modèle la connaît déjà : il n'y a rien à lui apprendre sur la façon de s'y brancher, et peu à lui expliquer sur les modèles décrits plus haut.

Et il ne se limite pas à consulter. Un assistant peut déclencher à distance ce que l'utilisateur dont il porte la clé ferait à l'écran : créer une fiche, corriger une condition d'achat, relancer un calcul. Ses droits sont exactement les siens, et seuls quelques traitements vraiment très sensibles restent réservés à l'interface.

Trois choses lui réussissent particulièrement. Lire une donnée qui n'est pas rangée — un courriel, un tableau reçu, un PDF. Rapprocher ce que deux écrans ne montrent jamais ensemble, et qu'un œil humain ne croise qu'en ouvrant deux onglets. Et saisir, c'est-à-dire porter dans Goodstock ce qu'il vient de lire.

Un exemple

Vous consultez un fournisseur avant de commander. Il répond par un PDF : des références, des quantités, des prix. L'assistant le lit et porte les lignes dans votre commande, puis s'arrête là — le reste est le travail habituel de Goodstock, qui compare ces prix à ceux qu'il attendait et signale ce qui a bougé. Quand une hausse déclenche une alerte

# Un fournisseur annonce par courriel que son délai passe à 28 jours.
# L'assistant retrouve sa fiche, compare, et corrige si besoin.
fiche = appel(
    "res.partner", "search_read",
    [[("name", "ilike", "Savonnerie du Midi")]],
    {"fields": ["lead_time"], "limit": 1},
)[0]

if fiche["lead_time"] != 28:
    appel("res.partner", "write", [[fiche["id"]], {"lead_time": 28}])

À partir de là, les propositions de commande de ce fournisseur partent sur vingt-huit jours, sans que personne ait eu à ouvrir sa fiche. Le délai qu'un fournisseur tient vraiment

Par où commencer

Créez une clé sur un utilisateur limité en lecture, donnez-la à l'assistant que vous utilisez déjà avec les quelques modèles qui vous intéressent, et posez vos questions. C'est l'affaire d'une après-midi, et rien n'est modifié dans votre instance.

Si vous avez un usage en tête, parlons-en. L'interface est déjà là ; le reste est du cadrage. Prendre rendez-vous

Les bonnes pratiques

Quelques bonnes pratiques

Groupez vos appels. Un search_read sur cinq cents articles coûte moins cher que cinq cents appels sur un article. La même règle vaut pour l'écriture : un seul write sur une liste d'identifiants plutôt qu'un par fiche.

Espacez ce qui peut l'être. Une instance sert d'abord ses utilisateurs ; une reprise d'historique lancée en pleine journée ralentit tout le monde, y compris vous. Les traitements de masse se font la nuit, ou par lots avec une pause entre deux.

Prévenez-nous des gros chantiers. Une reprise de plusieurs centaines de milliers de points de mesure se prépare — nous savons dans quel ordre les charger et ce qu'il faut recalculer ensuite.

Ces chantiers tombent presque toujours pendant une mise en route ou un projet, c'est-à-dire à un moment où nous sommes déjà en lien. Il n'y a donc pas de démarche particulière à engager : la question se pose au fil des échanges, et le support s'efforce de répondre vite quand elle bloque.

Voir Goodstock en fonctionnement

Une heure : l'outil tourne devant vous sur un catalogue de démonstration, ruptures et saisonnalité comprises, puis nous le confrontons à votre situation.

Pas sûr d'en avoir besoin ? Trente questions, trois minutes, et un score sur 90 vous le dit.

Faire le diagnostic

Suivre Goodstock sur LinkedIn

Voir une démonstration Votre stock est-il sous contrôle ? 30 questions, 3 minutes