API

Documentation de l'API

Connectez votre ERP ou logiciel métier : lisez les ventes, clients et factures de votre organisation, créez des ventes et recevez des notifications en temps réel. Testez chaque requête directement depuis cette page.

Cliquez « Autoriser » en haut pour saisir votre clé API, puis dépliez un endpoint et cliquez « Envoyer la requête ». Les données sont strictement limitées à l'organisation propriétaire de la clé.

URL de base : https://api.egicore.fr/api/v1/external

Authentification

Chaque requête porte la clé dans l'en-tête x-api-key. Un administrateur de votre société crée les clés dans Mon compte → API en choisissant leurs scopes. La clé complète (egk_…) n'est affichée qu'une seule fois, à la création ; Egicore n'en conserve qu'une empreinte.

Une clé est valable 365 jours par défaut (730 jours au maximum). Une clé révoquée ou expirée est refusée immédiatement (401).

curl https://api.egicore.fr/api/v1/external/orders \
  -H "x-api-key: egk_votre_cle"

Scopes

Chaque route exige un scope. Une clé sans le scope requis reçoit 403 INSUFFICIENT_SCOPE.

ScopeAutorise
orders:readLire les ventes (GET /orders, GET /orders/{id})
orders:writeCréer des ventes (POST /orders)
clients:readLire les clients (GET /clients, GET /clients/{id})
invoices:readLire les factures (GET /invoices, GET /invoices/{id})

Réponses & pagination

Les réponses sont en JSON. Une collection est enveloppée dans data et meta ; une ressource unique renvoie { "data": { … } }.

Paramètres de pagination : page (défaut 1) et limit (défaut 10, maximum 100 ; une valeur supérieure est ramenée à 100). Les montants sont en euros, les dates au format ISO 8601 (UTC) et les identifiants sont des UUID.

{ "data": [ … ], "meta": { "page": 1, "limit": 10, "total": 42 } }

Statuts de vente

Valeurs possibles du champ status des ventes et des factures, et du filtre status de GET /orders et GET /invoices :

ValeurSignification
DRAFTBrouillon
PENDING_PAYMENTEn attente de paiement
PAYMENT_RECEIVEDPaiement reçu
IN_ESCROWFonds cantonnés
DELIVEREDLivrée
CLOSEDClôturée (fonds libérés)
DISPUTEDLitige en cours
RESOLVEDLitige résolu
REFUNDEDRemboursée

Limites de débit

120 requêtes par minute et par clé (fenêtre fixe de 60 s). Au-delà, l'API répond 429 avec le code RATE_LIMIT_EXCEEDED jusqu'à la minute suivante.

Une protection générale de 100 requêtes par minute et par adresse IP s'applique aussi à toute l'API : si toutes vos requêtes partent de la même adresse, c'est cette limite qui est atteinte en premier. Elle renvoie également 429 avec le code RATE_LIMIT_EXCEEDED.

Chaque appel (clé, méthode, chemin, code de réponse) est journalisé pour audit.

Erreurs

Toute erreur renvoie le statut HTTP approprié et un corps au format :

{ "error": { "code": "INSUFFICIENT_SCOPE", "message": "Scope(s) manquant(s) : orders:write" } }

Erreur de validation (400) : message est un tableau.

{
  "error": {
    "code": "VALIDATION_ERROR",
    "message": ["clientId must be a UUID", "depositRate doit être 0 (sans acompte) ou compris entre 0.1 et 0.4"]
  }
}
HTTPCodeCas
400VALIDATION_ERRORCorps de POST /orders invalide ou champ inconnu. message est alors un tableau de messages de validation.
400INVALID_STATUS_FILTERFiltre status de GET /orders ou GET /invoices hors de la liste des statuts de vente.
400INVALID_PAGINATIONpage ou limit n’est pas un entier supérieur ou égal à 1.
401API_KEY_MISSINGEn-tête x-api-key absent.
401API_KEY_INVALIDClé inconnue ou révoquée.
401API_KEY_EXPIREDClé arrivée à expiration. Créez-en une nouvelle dans Mon compte → API.
403INSUFFICIENT_SCOPELa clé ne porte pas le scope requis par la route (le message liste les scopes manquants).
404ORDER_NOT_FOUNDVente introuvable dans votre organisation, ou identifiant qui n’est pas un UUID (GET /orders/{id}, GET /invoices/{id}).
404CLIENT_NOT_FOUNDClient introuvable dans votre organisation, ou identifiant qui n’est pas un UUID (GET /clients/{id}) ; clientId inconnu dans POST /orders.
404NOT_FOUNDRoute inconnue (chemin ou méthode).
404NO_SELLER_IN_ORGPOST /orders : aucun utilisateur de votre organisation auquel rattacher la vente.
429RATE_LIMIT_EXCEEDEDQuota de requêtes dépassé, par clé ou par adresse IP (voir Limites de débit).
500INTERNAL_ERRORErreur inattendue. Aucun détail technique n’est renvoyé ; contactez le support avec l’heure de l’appel.

Les messages sont destinés au débogage et peuvent évoluer : basez votre logique sur le statut HTTP et le champ code.

Ventes

Corps de POST /orders

Les champs non listés sont refusés (400 VALIDATION_ERROR). Le devis signé n'est pas transmis par l'API : le vendeur le joint dans Egicore avant de confirmer la vente.

ChampTypeRequisRègle
clientIdstring (UUID)OuiIdentifiant d'un client de votre organisation (voir GET /clients).
totalHtnumberOuiMontant total HT en euros, supérieur ou égal à 0.
totalTtcnumberOuiMontant total TTC en euros, supérieur ou égal à 0.
depositRatenumberOuiTaux d'acompte : 0 (sans acompte) ou entre 0.1 et 0.4.
titlestringNonIntitulé de la vente. Facultatif ici, mais demandé au vendeur pour confirmer le brouillon.
balanceEscrowbooleanNonMet aussi le solde sous cantonnement jusqu’à la livraison. Défaut : true.
paymentMethodstringNonBANK_TRANSFER, PAY_BY_BANK, CARD ou SEPA_DIRECT_DEBIT.
deliveryDeadlinestring (date ISO)NonÉchéance de livraison : date future, au plus tard dans 365 jours. Facultative ici, mais demandée au vendeur pour confirmer le brouillon.
notesstringNonNotes internes, non visibles du payeur.

Clients

Factures

Webhooks sortants

Enregistrez une URL de réception dans Mon compte → API → Webhooks sortants et choisissez les événements à recevoir. L'URL doit être en https et publique (pas de localhost ni d'adresse IP privée). Un secret de signature whsec_… s'affiche une seule fois à la création : conservez-le. Depuis le même écran, vous envoyez un événement de test, consultez le journal des 20 dernières livraisons et relancez une livraison en échec.

Événements

ÉvénementÉmis quandChamps de data
order.createdUne vente est créée. Vente saisie dans Egicore : à sa confirmation (status PENDING_PAYMENT). Vente créée par POST /orders : immédiatement, au statut DRAFT.orderId, orderNumber, status, totalTtc, clientId
order.status_changedÀ chaque changement de statut d’une vente, quelle qu’en soit l’origine : action du vendeur ou du payeur, fonds reçus ou libérés par le prestataire de paiement, litige, remboursement, intervention de l’équipe Egicore.orderId, orderNumber, from, to
test.pingEnvoyé à la demande depuis Mon compte → API (« Envoyer un test ») vers un seul endpoint. Non abonnable.message, endpointId, sentAt

Requête envoyée

Egicore envoie un POST JSON à votre URL. Le corps contient le nom de l'événement, ses données et l'identifiant unique de la livraison :

{
  "event": "order.status_changed",
  "data": {
    "orderId": "3f2b8c1e-7a4d-4e9b-9c21-5d8e6f0a1b2c",
    "orderNumber": "EGI-202609-00042",
    "from": "IN_ESCROW",
    "to": "DELIVERED"
  },
  "deliveryId": "6a1f0c9e-2d3b-4e5f-8a7b-1c2d3e4f5a6b"
}
Autres exemples (order.created, test.ping)
{
  "event": "order.created",
  "data": {
    "orderId": "b7e1d2c3-4f5a-4b6c-8d7e-9f0a1b2c3d4e",
    "orderNumber": "EGI-202610-00007",
    "status": "DRAFT",
    "totalTtc": 14400,
    "clientId": "8d0c4f6a-2b1e-4c7d-9a3f-1e5b7c9d2f40"
  },
  "deliveryId": "0e9d8c7b-6a5f-4e3d-9c2b-1a0f9e8d7c6b"
}
{
  "event": "test.ping",
  "data": {
    "message": "Test event sent from Egicore.",
    "endpointId": "5c4b3a29-1807-4f6e-9d5c-4b3a2918f7e6",
    "sentAt": "2026-10-08T09:45:12.345Z"
  },
  "deliveryId": "9b8a7f6e-5d4c-4b3a-8f1e-0d9c8b7a6f5e"
}
En-têteValeur
Content-Typeapplication/json
X-Egicore-SignatureHMAC-SHA256 du corps brut, clé = votre secret whsec_… (utilisé tel quel), encodé en hexadécimal minuscule

Aucun horodatage n'est signé : protégez-vous du rejeu en dédoublonnant sur deliveryId, identique pour toutes les tentatives d'une même livraison, relance manuelle comprise.

Vérifier la signature

Recalculez le HMAC sur le corps brut reçu et comparez-le en temps constant à l'en-tête avant tout traitement :

import crypto from 'node:crypto'
import express from 'express'

const app = express()
const SECRET = process.env.EGICORE_WEBHOOK_SECRET // whsec_…

// Corps BRUT : la signature porte sur les octets reçus, pas sur un JSON re-sérialisé.
app.post('/egicore-webhook', express.raw({ type: 'application/json' }), (req, res) => {
  const received = req.get('X-Egicore-Signature') ?? ''
  const expected = crypto
    .createHmac('sha256', SECRET)
    .update(req.body)
    .digest('hex')

  const valid =
    received.length === expected.length &&
    crypto.timingSafeEqual(Buffer.from(received), Buffer.from(expected))
  if (!valid) return res.status(401).end()

  const { event, data, deliveryId } = JSON.parse(req.body.toString('utf8'))
  // Ignorez un deliveryId déjà traité, puis traitez l'événement en asynchrone.
  res.status(200).end()
})

Réponse attendue et nouvelles tentatives

Répondez par un code 2xx dès réception, puis traitez l'événement en asynchrone. Chaque tentative attend votre réponse 10 secondes au maximum. Un dépassement de ce délai, tout autre code, une erreur réseau ou une redirection (3xx, jamais suivie) compte comme un échec.

En cas d'échec, Egicore réessaie automatiquement : 5 tentatives au total, espacées de 15 s, 30 s, 1 min puis 2 min. Après la 5e tentative, la livraison passe en Échec ; vous pouvez alors la relancer depuis le journal (mêmes corps et deliveryId). Statuts du journal : En cours, Livrée, Échec. L'ordre d'arrivée des événements n'est pas garanti : appuyez-vous sur les champs from / to et sur l'état renvoyé par GET /orders/{id}.

Versioning & support

Le préfixe /api/v1/ identifie la version ; les ajouts rétrocompatibles (nouveaux champs, nouveaux événements) n'en changent pas. Ignorez les champs que vous ne connaissez pas. Support : support@egicore.fr.