Flux et événements
Bruxel voit ce qui se passe à l’intérieur : la fiche, l’envoi du fournisseur, la description publiée. Ce qui se passe à l’extérieur —votre middleware a appelé SAP, l’entrepôt a créé l’article, la boutique a publié— il ne le sait que si votre système le signale.
Le scope
Signaler demande une clé avec le scope events:write. Lire le parcours d’un produit demande catalog:read. L’organisation vient toujours de la clé : le corps ne l’indique jamais.
Signaler des événements
Envoyez jusqu’à 100 événements par appel, pour les produits de votre choix. Chaque événement concerne un produit (par sku ou product_id) et indique le système, le type, le résultat (started, ok, error ou info) et le moment.
curl -X POST https://bruxel.ai/api/public/v1/events \
-H "Authorization: Bearer $BRUXEL_API_KEY" \
-H "Content-Type: application/json" \
-d '{
"events": [
{
"id": "mw-000123",
"sku": "BTL-1001",
"system": "sap",
"type": "material.created",
"status": "ok",
"occurred_at": "2026-10-05T18:04:11Z",
"duration_ms": 1840,
"summary": "Material creado: 4500012345",
"in_reply_to": "6f1c0e9a-7d2b-4c11-a4f0-91be2d07c3aa",
"request": { "method": "POST", "url": "https://sap.ejemplo/odata/A_Product", "body": { "Product": "BTL-1001" } },
"response": { "status": 201, "body": { "Product": "4500012345" } },
"link": "https://sap.ejemplo/material/4500012345",
"data": { "sap_code": "4500012345" }
}
]
}'La réponse contient une ligne par événement, dans le même ordre : accepted (enregistré), duplicate (vous l’aviez déjà : c’est le résultat d’origine) ou rejected (non enregistré, avec son motif).
{
"accepted": 1,
"duplicates": 0,
"rejected": 0,
"results": [
{
"index": 0,
"id": "mw-000123",
"outcome": "accepted",
"event_id": "…",
"product_id": "…",
"reason": null,
"detail": null,
"truncated": false
}
]
}Règles du contrat
- Idempotence : l’id vous appartient et est unique par système. Renvoyer le même événement est sans risque (duplicate) ; le même id avec un autre contenu revient rejected avec id_conflict.
- Chaque événement désigne son produit par sku ou product_id. Si vous envoyez les deux, ils doivent désigner le même produit. Un produit supprimé ou d’une autre organisation donne product_not_found.
- system s’écrit en minuscules (sap, wms, boutique). S’il manque, le système connecté auquel la clé est liée est utilisé (Compte → Général → Systèmes connectés) ou, si elle n’est pas liée, le nom de la clé en minuscules : « SAP middleware » devient sap_middleware, sauf si ce système a sa propre clé. Toute clé avec events:write peut signaler pour n’importe quel système qu’elle nomme : utilisez une clé par système.
- occurred_at est l’heure du fait, avec fuseau (ISO 8601), et c’est vous qui la fixez ; Bruxel enregistre à part l’heure de réception. Jusqu’à 5 minutes dans le futur et 30 jours dans le passé sont acceptés. Tout est trié par occurred_at.
- Ajout seulement : un événement ne se modifie ni ne se supprime. On corrige une erreur en envoyant un autre événement avec le nouveau résultat.
- Jusqu’à 16 Ko par corps (request.body et response.body), 4 Ko de data et 300 caractères de summary. Le surplus est coupé et l’événement est marqué truncated. Le link doit être en https.
- N’envoyez ni numéros de carte ni données personnelles. Ce qui ressemble à un identifiant secret (Authorization, token, password, api_key, un format de clé connu) est masqué avant l’enregistrement, mais c’est le dernier filet, pas le premier. Les corps sont conservés 90 jours ; l’événement, 13 mois.
Pourquoi un événement est rejeté
invalid— un champ obligatoire manque, un champ n’existe pas ou une valeur n’a pas la bonne forme (le message indique lequel).product_not_found— le produit n’est pas dans votre catalogue ou est dans la corbeille.id_conflict— vous avez déjà signalé un événement avec cet id et un autre contenu.occurred_at_future— occurred_at est à plus de 5 minutes dans le futur.occurred_at_too_old— occurred_at date de plus de 30 jours.system_required— system n’a pas été envoyé et la clé ne l’indique pas : elle n’est liée à aucun système connecté, et son nom ne peut pas servir de système ou est celui d’un système qui a sa propre clé.link_invalid— link n’est pas une adresse https.retry— il n’a pas pu être enregistré pour l’instant (un conflit passager dans la base) : renvoyez le MÊME événement.
Ce que cela fait avancer dans un flux
Une étape d’un flux peut être validée quand un système signale un certain événement. L’étape est validée par le dernier événement ok de ce système et de ce type :
"done_when": { "event": { "system": "sap", "type": "material.created" } }Si le dernier événement de ce type est error, l’étape passe en erreur et le parcours s’arrête jusqu’à l’arrivée d’un nouvel ok. Les événements started et info vont dans le journal mais ne valident ni n’arrêtent rien.
Le parcours d’un produit
GET /v1/products/:id/journey renvoie, pour chaque flux actif où se trouve le produit, l’état de chaque étape et depuis quand, le journal du parcours et les événements signalés par les systèmes. Il est recalculé à chaque demande.
curl https://bruxel.ai/api/public/v1/products/$PRODUCT_ID/journey \
-H "Authorization: Bearer $BRUXEL_API_KEY"
{
"product_id": "…",
"computed_at": "2026-10-05T18:05:02Z",
"runs": [
{
"flow_slug": "alta-producto",
"flow_name": "Alta de producto",
"status": "in_progress",
"steps": [
{ "key": "maestros", "name": "Datos maestros", "lane": "maestros", "state": "done", "since": "…", "late": false, "out_of_order": false },
{ "key": "sap_alta", "name": "Alta en SAP", "lane": "sap", "state": "done", "since": "2026-10-05T18:04:11Z", "late": false, "out_of_order": false },
{ "key": "tienda", "name": "Publicado", "lane": "tienda", "state": "active", "since": "2026-10-05T18:04:11Z", "late": false, "out_of_order": false }
],
"events": [ { "kind": "step", "step_key": "sap_alta", "to_state": "done", "cause": "event", "event_system": "sap", "event_type": "material.created", … } ],
"events_truncated": false
}
],
"reported": [ { "id": "mw-000123", "system": "sap", "type": "material.created", "status": "ok", … } ],
"reported_truncated": false
}Bruxel vous prévient : à vous, arrêté, terminé
Abonnez votre endpoint (Paramètres → Webhooks, ou votre système dans Systèmes connectés) à flow.step.ready, flow.run.blocked ou flow.run.completed. Ils arrivent regroupés : un par étape ou par flux chaque fois que le moteur fait avancer des produits, avec leur nombre et un échantillon de 20 au plus avec leur SKU. Tant qu’un avis n’est pas livré, ce qui arrive ensuite n’en crée pas un autre.
{
"event": "flow.step.ready",
"occurred_at": "2026-10-08T18:04:11Z",
"data": {
"flow": "alta-producto",
"step": "sap_alta",
"lane": "sap",
"system": "sap",
"count": 37,
"products": [ { "product_id": "…", "sku": "BTL-1001" }, … ]
}
}L’échantillon est un indice. À la réception de l’avis, lisez la liste complète : la file de l’étape (les produits qui y sont, les plus anciens d’abord) ou les parcours arrêtés ou terminés du flux, avec un curseur. Gardez le next_cursor et reprenez à partir de là la fois suivante.
curl "https://bruxel.ai/api/public/v1/flows/alta-producto/steps/sap_alta/products?limit=200" \
-H "Authorization: Bearer $BRUXEL_API_KEY"
curl "https://bruxel.ai/api/public/v1/flows/alta-producto/runs?status=completed&cursor=$CURSOR" \
-H "Authorization: Bearer $BRUXEL_API_KEY"