bruxel.ai

Fluxos e eventos

O Bruxel vê o que acontece dentro: a ficha, o envio do fornecedor, a descrição publicada. O que acontece fora —seu middleware chamou o SAP, o armazém cadastrou o material, a loja publicou— ele só sabe se o seu sistema informar.

O scope

Informar exige uma chave com o scope events:write. Ler o percurso de um produto exige catalog:read. A organização sempre vem da chave: o corpo nunca a informa.

Informar eventos

Envie até 100 eventos por chamada, dos produtos que quiser. Cada evento é de um produto (por sku ou product_id) e diz qual sistema, qual tipo, como foi (started, ok, error ou info) e quando aconteceu.

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" }
      }
    ]
  }'

A resposta traz uma linha por evento, na mesma ordem: accepted (gravado), duplicate (você já o tinha: é o resultado original) ou rejected (não gravado, com o motivo).

{
  "accepted": 1,
  "duplicates": 0,
  "rejected": 0,
  "results": [
    {
      "index": 0,
      "id": "mw-000123",
      "outcome": "accepted",
      "event_id": "…",
      "product_id": "…",
      "reason": null,
      "detail": null,
      "truncated": false
    }
  ]
}
Um evento malformado não derruba o lote: volta rejected na sua linha e os demais são gravados. A chamada responde 422 só se o corpo não for um objeto com events ou tiver mais de 100 eventos, e 413 se passar de 1 MB.

Regras do contrato

  • Idempotência: o id é seu e único por sistema. Repetir o mesmo evento é seguro (duplicate); o mesmo id com outro conteúdo volta rejected com id_conflict.
  • Cada evento indica o produto com sku ou product_id. Se enviar os dois, devem ser o mesmo produto. Um produto excluído ou de outra organização dá product_not_found.
  • system vai em minúsculas (sap, wms, loja). Se não for enviado, usa-se o sistema conectado ao qual a chave está ligada (Conta → Geral → Sistemas conectados) ou, se ela não estiver ligada, o nome da chave em minúsculas: “SAP middleware” vira sap_middleware, a menos que esse sistema tenha sua própria chave. Qualquer chave com events:write pode informar por qualquer sistema que nomear: use uma chave por sistema.
  • occurred_at é a hora do fato, com fuso (ISO 8601), e quem define é você; o Bruxel grava à parte a hora em que recebeu. Aceita-se até 5 minutos no futuro e 30 dias no passado. Tudo é ordenado por occurred_at.
  • Só se acrescenta: um evento não é editado nem excluído. Corrige-se um erro enviando outro evento com o novo resultado.
  • Até 16 KB por corpo (request.body e response.body), 4 KB de data e 300 caracteres de summary. O excedente é cortado e o evento fica marcado como truncated. O link deve ser https.
  • Não envie números de cartão nem dados pessoais. O que parecer uma credencial (Authorization, token, password, api_key, um formato de chave conhecido) é ocultado antes de gravar, mas essa é a última rede, não a primeira. Os corpos ficam 90 dias; o evento, 13 meses.

Por que um evento é rejeitado

  • invalid — falta um campo obrigatório, há um campo que não existe ou um valor tem outro formato (a mensagem diz qual).
  • product_not_found — o produto não está no seu catálogo ou está na lixeira.
  • id_conflict — você já informou um evento com esse id e outro conteúdo.
  • occurred_at_future — occurred_at está mais de 5 minutos no futuro.
  • occurred_at_too_old — occurred_at tem mais de 30 dias.
  • system_required — system não foi enviado e a chave não o indica: ela não está ligada a nenhum sistema conectado, e o nome dela não serve como sistema ou é o de um sistema que tem sua própria chave.
  • link_invalid — link não é um endereço https.
  • retry — não foi possível gravar agora (um conflito passageiro no banco): reenvie o MESMO evento.

O que isso move num fluxo

Uma etapa de um fluxo pode ser concluída quando um sistema informa certo evento. A etapa é concluída com o último evento ok desse sistema e tipo:

"done_when": { "event": { "system": "sap", "type": "material.created" } }

Se o último evento desse tipo for error, a etapa fica com erro e o percurso para até chegar outro ok. Os eventos started e info vão para o registro, mas não concluem nem param nada.

O percurso de um produto

GET /v1/products/:id/journey retorna, para cada fluxo ativo em que o produto está, o estado de cada etapa e desde quando, o registro do percurso e os eventos informados pelos sistemas. É recalculado a cada pedido.

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
}
Sem corpos nem pessoas: o que foi enviado e a resposta se veem dentro do Bruxel, e o registro diz o que aconteceu e por qual caminho, não quem. Com os fluxos desligados, responde 404.

O Bruxel avisa: sua vez, parou, terminou

Inscreva seu endpoint (Configurações → Webhooks, ou seu sistema em Sistemas conectados) em flow.step.ready, flow.run.blocked ou flow.run.completed. Chegam agrupados: um por etapa ou por fluxo cada vez que o motor move produtos, com quantos foram e uma amostra de até 20 com o SKU. Enquanto um não é entregue, o que chega depois não cria outro.

{
  "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" }, … ]
  }
}

A amostra é uma pista. Ao receber o aviso, leia a lista completa: a fila da etapa (os produtos que estão nela, os que esperam há mais tempo primeiro) ou os percursos parados ou terminados do fluxo, com cursor. Guarde o next_cursor e continue dali na próxima vez.

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"
Ativar um fluxo acorda você uma vez para cada etapa que tem produtos: é justamente quando seu sistema precisa ler a fila. O scope catalog:read basta para lê-la.
Fluxos e eventos — Bruxel