API publique v1
L’API publique permet à un logiciel tiers de lire les données d’une société OMAG : Power BI, Excel, un site de vente en ligne, un ERP voisin, ou un développeur mandaté par le client.
Essayer tout de suite, sans compte
Section intitulée « Essayer tout de suite, sans compte »Une société de démonstration est ouverte à tous. Copiez la clé ci-dessous et appelez l’API : pas d’inscription, rien à installer.
curl "https://api.omag.ma:7979/api/v1/moi" \ -H "X-Omag-Cle: omk_live_1_FVhCzt_l2X10HJM5lvxRjUMZtuDddaO_"Elle ouvre toutes les portées en lecture sur un jeu de données fictif — 47 articles, 19 clients, 222 factures, 4 dépôts — et supporte tout ce qui est décrit dans cette page : pagination, synchronisation incrémentale, flux BI, export CSV.
Démarrage en trois minutes
Section intitulée « Démarrage en trois minutes »0. Vérifier que l’option est active. L’API fait partie du module « API & connecteurs IA », visible dans Mes applications. S’il n’est pas actif, les appels répondent 403 MODULE_NON_SOUSCRIT — il faut le demander à OMAG.
1. Obtenir une clé. L’administrateur du compte OMAG la crée lui-même dans Configuration → Clés API : un libellé, les portées à cocher, un quota. La clé s’affiche une seule fois — le serveur n’en garde que l’empreinte. Côté administrateur, la marche à suivre est décrite dans Connecter un autre logiciel.
2. Vérifier qu’elle fonctionne.
curl https://api.omag.ma:7979/api/v1/moi \ -H "X-Omag-Cle: omk_live_47_VOTRE_CLE"{ "cle": "omk_live_47_3JyZN7", "libelle": "Power BI direction", "societe": "omag_47_MA SOCIETE", "scopes": ["articles:read", "stock:read"], "scopes_disponibles": ["articles:read", "stock:read", "tiers:read", "documents:read", "reglements:read", "compta:read"]}3. Lire des données.
curl "https://api.omag.ma:7979/api/v1/articles?limite=50" \ -H "X-Omag-Cle: omk_live_47_VOTRE_CLE"Authentification
Section intitulée « Authentification »La clé se présente dans l’en-tête X-Omag-Cle. Par commodité, Authorization: Bearer omk_live_... est également accepté — c’est le réflexe de tout client HTTP, et le refuser ne protégerait de rien.
omk_live_<IdClient>_<aléa base64url>L’identifiant au milieu n’est pas un secret : c’est un routage. Chaque client OMAG possède sa propre base de configuration, et une clé nue ne dirait pas laquelle interroger. Le secret, c’est l’aléa (24 octets).
Une clé = une société
Section intitulée « Une clé = une société »Une clé porte une société et une seule. C’est elle qui fixe le périmètre : aucune route de l’API ne prend de nom de base, ni dans l’URL ni dans le corps. Un client multi-sociétés crée une clé par société.
Conséquence directe : vous ne pouvez pas vous tromper de dossier, et vous ne pouvez pas atteindre celui de quelqu’un d’autre.
Une clé n’ouvre que ce qui a été coché à sa création.
| Portée | Ouvre |
|---|---|
articles:read |
Articles |
stock:read |
Stock et disponibilités |
tiers:read |
Clients et fournisseurs |
documents:read |
Ventes : devis, commandes, BL, factures, avoirs |
achats:read |
Achats : commandes, réceptions, factures, avoirs, règlements fournisseurs |
reglements:read |
Règlements clients |
compta:read |
Écritures comptables, balance |
GET /v1/moi liste celles que porte votre clé ; GET /v1/flux indique en plus, pour chaque vue BI, si votre clé y a droit.
Plafond par clé et par minute (60 par défaut, réglable par l’administrateur).
Chaque réponse porte votre compteur — pas seulement les refus :
| En-tête | |
|---|---|
RateLimit-Limit |
votre plafond par minute |
RateLimit-Remaining |
appels restants dans la fenêtre en cours |
RateLimit-Reset |
secondes avant remise à zéro |
Au-delà du plafond : 429 avec, en plus, un Retry-After en secondes — toujours égal à RateLimit-Reset.
Lisez RateLimit-Remaining et ralentissez avant d’arriver à zéro : c’est plus simple que de gérer des 429, et ça évite de perdre des requêtes.
Depuis un navigateur, ces en-têtes sont explicitement exposés (Access-Control-Expose-Headers), donc lisibles en JavaScript.
Le quota est décompté même sur un refus de portée ou d’adresse : un appel mal formé coûte quand même une recherche de clé. Votre compteur reste donc exact quoi qu’il arrive.
Deux familles, deux publics
Section intitulée « Deux familles, deux publics »| Ressources REST | Flux à plat | |
|---|---|---|
| Chemin | /v1/<ressource> |
/v1/flux/<vue> |
| Pour qui | un développeur | Power BI, Excel, Looker Studio |
| Forme | objets, clés naturelles | lignes plates, déjà jointes et agrégées |
| Pagination | curseur opaque | numéro de page |
| Sortie | JSON | JSON ou CSV |
Ce n’est pas une incohérence : un outil de BI se débrouille mal avec un curseur, un développeur se débrouille mal avec un jeu pré-agrégé. Les deux familles partagent la même clé et les mêmes portées.
Ressources REST
Section intitulée « Ressources REST »| Route | Portée | Réponse |
|---|---|---|
GET /v1 |
ouverte | sonde { api, version, statut } |
GET /v1/moi |
clé valide | identité de la clé |
GET /v1/articles |
articles:read |
liste |
GET /v1/articles/{code} |
articles:read |
fiche |
GET /v1/stock |
stock:read |
une ligne par article et par dépôt |
GET /v1/tiers/clients |
tiers:read |
liste |
GET /v1/tiers/clients/{code} |
tiers:read |
fiche |
GET /v1/tiers/fournisseurs |
tiers:read |
liste |
GET /v1/tiers/fournisseurs/{code} |
tiers:read |
fiche |
GET /v1/documents/{type} |
documents:read |
en-têtes seulement |
GET /v1/documents/{type}/{numero} |
documents:read |
en-tête + lignes |
GET /v1/achats/documents/{type} |
achats:read |
en-têtes seulement |
GET /v1/achats/documents/{type}/{numéro} |
achats:read |
en-tête + lignes |
GET /v1/achats/reglements |
achats:read |
liste |
GET /v1/achats/reglements/{numéro} |
achats:read |
fiche |
GET /v1/reglements |
reglements:read |
liste |
GET /v1/reglements/{numero} |
reglements:read |
fiche |
GET /v1/compta/ecritures |
compta:read |
liste |
GET /v1/postman |
clé valide | collection Postman prête à importer |
{type} vaut devis, commandes, bl, factures ou avoirs — une seule forme de réponse pour les cinq. Un analyseur écrit pour les factures lit les devis sans une ligne de changement.
La liste de documents ne porte pas les lignes : les joindre ferait exploser une page de 200 documents alors que la plupart des intégrations n’en ont pas besoin. Qui veut le détail demande la fiche.
Côté achats, {type} vaut commandes, receptions, factures ou avoirs. La réponse a exactement la même forme que côté ventes, au nom du tiers près : fournisseur_code et fournisseur_nom remplacent client_code et client_nom. Un analyseur écrit pour l’un lit l’autre en changeant deux noms.
La sonde GET /v1 est volontairement anonyme : elle vous permet de distinguer « le service est injoignable » de « ma clé est refusée ».
Paramètres des listes
Section intitulée « Paramètres des listes »| Paramètre | Défaut | Effet |
|---|---|---|
limite |
200 (max 1000) | taille de page |
curseur |
— | page suivante ; valeur opaque, à renvoyer telle quelle |
modifie_depuis |
— | bascule en mode incrémental (date ISO) |
Chaque liste accepte des filtres nommés, combinés en ET, et compatibles avec la pagination comme avec le mode incrémental.
| Ressource | Filtres |
|---|---|
articles |
recherche, famille, sous_famille, code_barre, bloque |
stock |
article, depot |
tiers/clients |
recherche, ville, bloque, prospect |
tiers/fournisseurs |
recherche, ville |
documents/{type} |
client, du, au, impaye (sauf devis, qui n’a pas de règlement) |
achats/documents/{type} |
fournisseur, du, au, impaye (sauf commandes) |
achats/reglements |
fournisseur, du, au, mode |
reglements |
client, du, au, mode |
compta/ecritures |
compte, journal, exercice, du, au, lettre, non_lettre |
# Les factures impayées d'un client sur 2026GET /v1/documents/factures?client=000055&du=2026-01-01&au=2026-12-31&impaye=1
# Tout le compte clients non lettréGET /v1/compta/ecritures?compte=3421&non_lettre=1
# Le stock d'un dépôtGET /v1/stock?depot=1Conventions :
du/ausont inclusifs, au formatAAAA-MM-JJ.comptefiltre par préfixe :compte=34rend tous les comptes34xxx.rechercheporte sur plusieurs colonnes à la fois (code, libellé, référence, code-barre pour les articles ; nom, code, e-mail, téléphone, ICE pour les tiers).- Les booléens acceptent
1/0,true/false,oui/non.
{ "donnees": [ /* … */ ], "curseur_suivant": "ART0042", "supprimes": ["ART0007"]}curseur_suivant à null signifie fin du parcours. supprimes n’apparaît qu’en mode incrémental.
Synchronisation incrémentale
Section intitulée « Synchronisation incrémentale »Sans modifie_depuis, vous parcourez la table entière — le chargement initial. Avec, vous ne recevez que ce qui a bougé depuis. Une seule mécanique à coder, et le chargement de masse comme le rafraîchissement fréquent en sortent.
# 1) chargement complet, page par pageGET /v1/articles?limite=500GET /v1/articles?limite=500&curseur=<curseur_suivant>…jusqu'à curseur_suivant = null
# 2) puis, toutes les heuresGET /v1/articles?modifie_depuis=2026-09-13T08:00:00Les suppressions sont couvertes. C’est le trou classique de toute synchronisation incrémentale : une ligne effacée est invisible d’une requête sur la table. Ici elle sort dans supprimes. Traitez ce tableau comme une liste de clés à retirer de votre copie.
Seule la dernière action de chaque clé compte dans la fenêtre : une ligne créée puis supprimée ne remonte pas comme « modifiée », une ligne supprimée puis recréée ne remonte pas comme « supprimée ».
Exemple : parcourir toute une ressource
Section intitulée « Exemple : parcourir toute une ressource »import requests
BASE = "https://api.omag.ma:7979/api/v1"EN_TETES = {"X-Omag-Cle": "omk_live_47_VOTRE_CLE"}
def tout_lire(ressource, **params): curseur, lignes = None, [] while True: p = {"limite": 500, **params} if curseur: p["curseur"] = curseur r = requests.get(f"{BASE}/{ressource}", headers=EN_TETES, params=p, timeout=60) r.raise_for_status() page = r.json() lignes += page["donnees"] curseur = page.get("curseur_suivant") if not curseur: return lignes
articles = tout_lire("articles")print(len(articles), "articles")Flux à plat pour la BI
Section intitulée « Flux à plat pour la BI »| Vue | Portée | Contenu |
|---|---|---|
GET /v1/flux |
clé valide | catalogue : vues, paramètres, et ce que votre clé ouvre |
GET /v1/flux/ventes |
documents:read |
CA par année, mois, agence, commercial, client, ville, secteur, famille, article |
GET /v1/flux/balance |
compta:read |
balance comptable |
GET /v1/flux/stock |
stock:read |
stock par article et dépôt, valorisé au PMP |
GET /v1/flux/reglements |
reglements:read |
encaissements à plat |
| Paramètre | Défaut | |
|---|---|---|
page |
1 | numéro de page |
taille |
5000 (max 20000) | lignes par page |
format |
json |
csv pour Excel |
du / au |
— | période AAAA-MM-JJ, au inclus |
ventes accepte base_ca, avec le même sens que le sélecteur « Base du CA » de l’écran Analyse croisée :
| Valeur | Définition |
|---|---|
LIV (défaut) |
CA livré : BL au mois du BL + factures directes. Stable dans le temps. |
FAC |
CA facturé : factures et avoirs seuls. Rapprochable avec la comptabilité et la TVA. |
MIX |
Situation à date : factures + BL pas encore facturés. Un mois passé se recalcule à chaque facturation. |
Une valeur inconnue retombe sur LIV.
La balance n’est pas un simple débit/crédit
Section intitulée « La balance n’est pas un simple débit/crédit »Elle expose les à-nouveaux et les mouvements de la période séparément :
| Champ | |
|---|---|
debit_report, credit_report |
à-nouveaux |
debit_mouvement, credit_mouvement |
mouvements de la période |
solde_debit, solde_credit |
soldes |
solde |
signé : positif débiteur, négatif créditeur |
sens |
D ou C |
Un outil qui ne lirait qu’un couple « débit / crédit » produirait une balance fausse du montant des reports. Utilisez solde : il somme à zéro sur une balance équilibrée, ce qui vous donne un contrôle immédiat.
CSV pour Excel
Section intitulée « CSV pour Excel »?format=csv rend un fichier à séparateur ; avec un BOM UTF-8 — les deux conditions pour qu’Excel français l’ouvre correctement, accents compris.
curl "https://api.omag.ma:7979/api/v1/flux/ventes?du=2026-01-01&au=2026-12-31&format=csv" \ -H "X-Omag-Cle: omk_live_47_VOTRE_CLE" -o ventes-2026.csvRecette Power BI
Section intitulée « Recette Power BI »Power Query, Sources de données → Requête vide → Éditeur avancé :
let Cle = "omk_live_47_VOTRE_CLE", Base = "https://api.omag.ma:7979/api/v1",
Page = (n as number) as record => let Reponse = Json.Document(Web.Contents(Base, [ RelativePath = "flux/ventes", Query = [du = "2026-01-01", au = "2026-12-31", taille = "20000", page = Text.From(n)], Headers = [#"X-Omag-Cle" = Cle] ])), Lignes = Table.FromRecords(Reponse[donnees]), Total = Reponse[pages_total] in [Lignes = Lignes, Total = Total],
Premiere = Page(1), Suivantes = List.Transform({2..Premiere[Total]}, each Page(_)[Lignes]), Tout = Table.Combine(List.Combine({{Premiere[Lignes]}, Suivantes}))in ToutRecette Excel
Section intitulée « Recette Excel »Données → À partir du Web ne sait pas poser d’en-tête. Deux options :
- Le CSV : téléchargez-le avec
curlet ouvrez-le. C’est le chemin le plus court. - Power Query dans Excel : le même code M que ci-dessus — Excel embarque le même moteur que Power BI.
Spécification OpenAPI
Section intitulée « Spécification OpenAPI »GET https://api.omag.ma:7979/api/v1/openapi.jsonPublique, sans clé. C’est la description complète de l’API dans un format que les outils comprennent : toutes les routes, tous les paramètres avec leur type, tous les champs de chaque réponse.
Ce qu’elle vous fait gagner :
| Essayer sans rien installer | la page Essayer l’API la charge et la rend interactive |
| Générer un client | openapi-generator produit une bibliothèque typée en C#, Python, PHP, Java… en quelques secondes |
| Importer dans vos outils | Postman, Insomnia et Bruno avalent l’URL directement |
| Brancher une automatisation | n8n, Make et consorts savent en construire un connecteur |
| Configurer un assistant IA | un GPT personnalisé se configure en téléversant ce fichier |
# Un client Python en une commandenpx @openapitools/openapi-generator-cli generate -i https://api.omag.ma:7979/api/v1/openapi.json -g python -o ./client-omagCollection Postman
Section intitulée « Collection Postman »curl https://api.omag.ma:7979/api/v1/postman \ -H "X-Omag-Cle: omk_live_47_VOTRE_CLE" -o omag-v1.postman_collection.jsonImportez le fichier dans Postman, renseignez la variable cle dans l’onglet Variables de la collection, et toutes les requêtes fonctionnent. La collection est générée pour votre installation : l’adresse de base y est déjà correcte.
La clé n’est pas incluse dans le fichier, volontairement — un secret n’a rien à faire dans un fichier qu’on s’échange.
Dates : toujours AAAA-MM-JJ, en heure locale de la société. Elles ne sont jamais rendues en UTC — une date métier du 1er septembre ne doit pas s’afficher chez vous comme le 31 août.
Montants : toujours des nombres JSON, jamais des chaînes. Vous pouvez additionner sans conversion.
Ce qui sort :
- Données validées uniquement. Un devis en cours de saisie n’est pas publiable. Il n’existe aucun paramètre pour outrepasser cette règle.
- Les articles masqués et les tiers en sommeil sont exclus.
- Les coûts (
pmp_ht,dernier_prix_achat_ht) sortent dansarticles:read. Une clé donnée à un prestataire lui donne donc accès aux marges : cette portée ne s’accorde qu’à qui doit l’avoir. - Pas de cloisonnement par agence ni par site : une clé n’est pas un utilisateur, elle voit tout le périmètre de sa société.
Toujours la même forme, y compris les 404 :
{ "code": "SCOPE_MANQUANT", "message": "Cette clé n'a pas le scope « articles:read »." }Le code est stable : testez-le. Le message est destiné à l’humain qui lit le journal, et peut changer d’une version à l’autre.
| HTTP | code |
Cause | Que faire |
|---|---|---|---|
| 400 | PARAMETRE_INVALIDE |
date mal formée, numéro non entier | corriger l’appel |
| 401 | CLE_MANQUANTE |
en-tête absent | ajouter X-Omag-Cle |
| 401 | CLE_INVALIDE |
clé inconnue, révoquée ou mal formée | en demander une nouvelle |
| 403 | IP_REFUSEE |
adresse hors de la liste de la clé | faire ajouter votre IP |
| 403 | SCOPE_MANQUANT |
portée non accordée | faire cocher la portée |
| 403 | MODULE_NON_SOUSCRIT |
l’option API n’est pas active sur le compte | la demander à OMAG |
| 404 | INTROUVABLE |
l’enregistrement n’existe pas ou n’est pas validé | — |
| 404 | TYPE_INCONNU |
type de document inconnu | voir la liste des types |
| 404 | ROUTE_INCONNUE |
chemin inexistant | vérifier l’URL |
| 429 | QUOTA_DEPASSE |
plafond par minute atteint | respecter Retry-After |
| 500 | ERREUR_INTERNE |
anomalie serveur | signaler, avec l’heure et le chemin |
| 503 | INDISPONIBLE |
base injoignable | réessayer |
| 503 | SOURCE_INDISPONIBLE |
procédure absente (flux BI) | une maintenance la recréera |
L’administrateur du compte voit vos erreurs dans Configuration → Clés API → Activité : méthode, chemin, code, heure, adresse IP. C’est la première chose à regarder avant d’ouvrir un ticket.
Bonnes pratiques
Section intitulée « Bonnes pratiques »Chargez une fois, rafraîchissez ensuite. Un intégrateur qui retélécharge tout le catalogue toutes les cinq minutes sature son quota et n’apporte rien de plus que modifie_depuis.
Une clé par usage. Power BI, le site de vente en ligne et le prestataire de passage méritent trois clés différentes : on révoque l’une sans casser les autres, et le journal dit qui fait quoi.
Restreignez les IP quand l’appelant a une adresse fixe : c’est le réglage le plus rentable de l’écran.
Ne partagez jamais une clé par e-mail ou dans un dépôt Git. Elle n’est affichable qu’une fois pour cette raison ; si elle a fuité, elle se révoque — c’est instantané et sans effet sur les autres.
Contrat de version
Section intitulée « Contrat de version »On ajoute des champs, on n’en retire jamais, et on ne change pas le sens d’un champ existant. Un programme écrit contre la v1 continue de fonctionner.
Côté client, cela veut dire : ignorez les champs que vous ne connaissez pas plutôt que de rejeter la réponse. Une rupture nécessaire ouvrirait une /v2, annoncée, avec les deux versions servies en parallèle.
Toutes les évolutions sont datées dans le journal des versions. La sonde publique porte le repère du contrat en cours, donc une supervision peut détecter un changement sans lire cette page :
curl https://api.omag.ma:7979/api/v1# { "api": "omag", "version": "v1", "statut": "ok",# "contrat": "2026-09-13.2", "journal": "https://docs.omag.ma/developpeurs/changelog/" }
