bruxel.ai

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
    }
  ]
}
Un événement mal formé ne fait pas échouer le lot : il revient rejected dans sa ligne et les autres sont enregistrés. L’appel répond 422 seulement si le corps n’est pas un objet avec events ou contient plus de 100 événements, et 413 s’il dépasse 1 Mo.

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
}
Ni corps ni personnes : ce qui a été envoyé et la réponse se consultent dans Bruxel, et le journal dit ce qui s’est passé et par quel chemin, pas qui. Avec les flux désactivés, il répond 404.

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"
Activer un flux vous réveille une fois pour chaque étape qui a des produits : c’est justement le moment où votre système doit lire sa file. Le scope catalog:read suffit pour la lire.
Flux et événements — Bruxel