Flows and events
Bruxel sees what happens inside: the product record, the supplier submission, the published description. What happens outside —your middleware called SAP, the warehouse created the material, the store published— it only knows if your system reports it.
The scope
Reporting needs a key with the events:write scope. Reading a product's journey needs catalog:read. The organization always comes from the key: the body never says it.
Report events
Send up to 100 events per call, for any products. Each event belongs to one product (by sku or by product_id) and says which system, which type, how it went (started, ok, error or info) and when it happened.
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" }
}
]
}'The response has one row per event, in the same order: accepted (stored), duplicate (you already had it: this is the original result) or rejected (not stored, with its reason).
{
"accepted": 1,
"duplicates": 0,
"rejected": 0,
"results": [
{
"index": 0,
"id": "mw-000123",
"outcome": "accepted",
"event_id": "…",
"product_id": "…",
"reason": null,
"detail": null,
"truncated": false
}
]
}Contract rules
- Idempotency: the id is yours and unique per system. Repeating the same event is safe (duplicate); the same id with different content comes back rejected with id_conflict.
- Each event names its product with sku or product_id. If you send both, they must be the same product. A deleted product, or one from another organization, is product_not_found.
- system is lowercase (sap, wms, store). If you omit it, the connected system the key is linked to is used (Account → General → Connected systems) or, if it is not linked, the key name in lowercase: “SAP middleware” becomes sap_middleware, unless that system has its own key. Any key with events:write can report for any system it names: use one key per system.
- occurred_at is when it happened, with a time zone (ISO 8601), and you set it; Bruxel stores separately when it received it. Up to 5 minutes in the future and 30 days in the past are accepted. Everything is ordered by occurred_at.
- Append only: an event is never edited or deleted. Fix an error by sending another event with the new result.
- Up to 16 KB per body (request.body and response.body), 4 KB of data and 300 characters of summary. Anything larger is cut and the event is marked truncated. The link must be https.
- Do not send card numbers or personal data. Anything that looks like a credential (Authorization, token, password, api_key, a known key format) is masked before storing, but that is the last net, not the first. Bodies are kept 90 days; the event, 13 months.
Why an event is rejected
invalid— a required field is missing, it has a field that does not exist, or a value has the wrong shape (the message says which).product_not_found— the product is not in your catalog or is in the trash.id_conflict— you already reported an event with that id and different content.occurred_at_future— occurred_at is more than 5 minutes in the future.occurred_at_too_old— occurred_at is more than 30 days old.system_required— you did not send system and the key does not say it: it is not linked to any connected system, and its name cannot be used as one or belongs to a system that has its own key.link_invalid— link is not an https address.retry— it could not be stored right now (a passing clash in the database): retry the SAME event.
What it moves in a flow
A flow step can be marked done when a system reports a certain event. The step is done with the latest ok event of that system and type:
"done_when": { "event": { "system": "sap", "type": "material.created" } }If the latest event of that type is error, the step shows an error and the journey stops until another ok arrives. started and info events go into the log but do not complete or stop anything.
A product's journey
GET /v1/products/:id/journey returns, for each active flow the product is in, the state of each step and since when, the journey log and the events systems reported. It is recalculated on request.
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 lets you know: your turn, stopped, finished
Subscribe your endpoint (Settings → Webhooks, or your system in Connected systems) to flow.step.ready, flow.run.blocked or flow.run.completed. They arrive grouped: one per step or per flow each time the engine moves products, with how many and a sample of up to 20 with their SKU. While one is not delivered, what comes next doesn't create another.
{
"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" }, … ]
}
}The sample is a hint. When the notification arrives, read the full list: the step's queue (the products in it, longest waiting first) or the flow's stopped or finished journeys, with a cursor. Keep the next_cursor and pick up from there next time.
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"