sailing Foudra MRP
Referencia chevron_rightapi/index

La API

JSON sobre HTTPS en el propio subdominio de la empresa, autenticado con una clave de API como Authorization: Bearer fdr_…. El alcance de la clave la limita: las claves read solo pueden hacer GET, las write pueden hacerlo todo salvo aprobar o enviar un pedido de compra, y las approve pueden hacer también eso. Los decimales (cantidades, dinero) son cadenas: los números de JSON son de coma flotante, y estos no lo son. Los errores son {"error": "…"} con un código de estado significativo.

Una aplicación que usan muchas empresas (Syncomatic, una app de móvil) usa OAuth 2.0 en lugar de una clave, para que cada usuario la autorice por sí mismo y pueda desconectarla desde Aplicaciones conectadas. Consulta OAuth al final de esta página; sus tokens de acceso (fdo_…) se envían igual que las claves y respetan los mismos alcances.

Lectura

Endpoint Devuelve
GET /api/parts · /api/parts/{id} Artículos, paginados (?page=&per=); ?category=504 filtra por una categoría, ?q= busca en referencia, nombre y descripción; cada artículo incluye on_hand
GET /api/parts/{id}/barcode.png La referencia como código de barras Code 128 en PNG, con la referencia impresa debajo; ?scale=1–10, ?height= (píxeles), ?text=0 solo para las barras. El JSON de cada artículo da esta ruta como barcode_url
GET /api/parts/{id}/bom Las líneas de la lista de materiales del artículo: artículo componente, cantidad por unidad, referencia, paso de la ruta
GET /api/parts/{id}/routing Los pasos de la ruta de fabricación del artículo, con totales
GET /api/parts/{id}/images Las imágenes del artículo: id, nombre de archivo, indicador de imagen principal
GET /api/suppliers Los proveedores
GET /api/parts/{id}/sourcing Quién vende el artículo, con qué referencia y a qué precio
GET /api/demand?status=open&channel= Pedidos de demanda con canal, referencia, cantidad y cantidad enviada
GET /api/channels Los canales
GET /api/channels/{code}/listings Todos los artículos publicados en el canal con su referencia en el canal; null significa que aún no se ha subido
GET /api/stock Una fila por artículo, ubicación y lote (lot_id, lot_code, expires_at; el stock sin trazabilidad no los tiene)
GET /api/lots?q= Lotes y números de serie, los más recientes primero: código, artículo, disponible, caducidad, lote del proveedor
GET /api/lots/{id}/barcode.png El código de un lote o número de serie como PNG Code 128, igual que el de un artículo; barcode_url en el JSON de cada lote
GET /api/sourcing?supplier=&supplier_part_number=&part_number= Qué artículo nuestro corresponde a una referencia de proveedor, o al revés. No distingue mayúsculas de minúsculas
GET /api/purchase-orders?status=&number=PO-00004 Pedidos con proveedor, subtotal y un enlace. number encuentra uno tal como lo nombra la gente
GET /api/purchase-orders/{id} Un pedido con sus líneas
GET /api/workstations · /api/workstations/{id} Puestos con su grupo y estado
GET /api/workstations/{id}/next El trabajo que este puesto debería hacer ahora, con sus materiales: null salvo que el puesto esté inactivo y haya un paso listo. ?material=<part id> limita a los trabajos que consumen ese artículo. La respuesta incluye current_material y requires_material_change del puesto
GET /api/work-orders?status=&number=WO-00023&external_ref= · /api/work-orders/{id} Órdenes; una orden individual incluye sus pasos y los materiales y la disponibilidad de cada paso. Cada orden abierta lleva la previsión de la programación hacia delante: expected_start, expected_finish, late (termina después de due_at) y delay_reasons; compara expected_finish con due_at para ver un retraso. Las órdenes terminadas y canceladas no tienen previsión (null)
GET /api/{parts,workstations,work-orders}/{id}/custom El JSON opaco, o null

Escritura

Endpoint Cuerpo Hace
POST /api/purchase-orders {supplier, currency?, exchange_rate?, expected_at?, external_ref?, notes?, lines:[{supplier_part_number | part_id, quantity, unit_price?}]} Crea un borrador; las líneas indicadas por referencia se resuelven mediante el aprovisionamiento. Una referencia desconocida hace fallar toda la petición, indicando la línea. Lo aprueba una persona
PUT /api/workstations/{id}/status {"status": "idle|busy|blocked|offline", "note"?} Refleja el estado de una máquina
POST /api/work-order-steps/{id}/start {"workstation_id"?} Toma un paso
POST /api/work-order-steps/{id}/complete {"quantity": "9", "consumed"?: [{"part_id", "quantity"}], "used"?: [{"part_id", "lots": ["R-2291"]}], "made"?: ["G0088"], "expires"?: "2027-03-31"} Registra las unidades fabricadas; descuenta automáticamente los materiales según la lista de materiales, o según el consumo real informado para cualquier artículo indicado en consumed; el último paso da entrada a la producción. Para artículos con trazabilidad: used indica los lotes o números de serie consumidos, made los números de serie de lo fabricado (uno por unidad, obligatorio) o su lote (por defecto, el número de la orden), y expires la caducidad de un lote de salida nuevo
POST /api/work-order-steps/{id}/scrap {"quantity": "9", "consumed"?: […], "used"?: […], "note"?} Una ejecución que no produjo nada: se consumen materiales, no hay producción y el recuento del paso no cambia
POST /api/work-order-steps/{id}/pause Un paso en marcha al que le queda cantidad vuelve a pendiente, conserva el avance y libera el puesto, para que el siguiente trabajo se elija de nuevo. El gestor de impresión lo hace después de cada cama
PUT /api/workstations/{id}/material {"part_id": 934} o {"part_id": null} Con qué está preparado el puesto: el filamento cargado en una impresora
PUT /api/tasks/{source} [{key, priority, message, href, actions?: [{key, label, tone?}]}] Sustituye en bloque el conjunto de tareas de este origen; todo lo que se omita se resuelve. Los nombres de origen son slugs en minúsculas. Una tarea puede ofrecer hasta seis acciones, botones que puede pulsar una persona; al pulsar uno, la tarea se resuelve con esa clave como su outcome, que el origen vuelve a leer. Una tarea con resultado nunca se reabre: usa una clave nueva para una nueva aparición
GET /api/tasks/{source} Las tareas abiertas de este origen y las resueltas recientemente con un outcome: así sabe una integración qué botón se pulsó
PUT /api/{kind}/{id}/custom El propio JSON Sustituye el bloque opaco (64 KB como máximo)
POST /api/parts {category_code, name, part_type, description?, notes?, unit_of_measure?, is_stocked?, minimum_stock?, lead_time_days?, tracking?, custom?} Crea un artículo; el número de stock se asigna dentro de la categoría. Devuelve el artículo
POST /api/parts/{id}/bom/lines {child_part_id, quantity, reference?, notes?, routing_step_id?} Añade una línea a la lista de materiales; se aplican las reglas contra ciclos
PUT /api/bom/lines/{id} {quantity?, reference?, notes?} Edita una línea
POST /api/parts/{id}/routing/steps {name, group_code?, seq?, setup_minutes?, run_minutes_per_unit?, batch_size?, instructions?} Añade un paso a la ruta de fabricación; si seq está vacío, toma la siguiente decena
POST /api/parts/{id}/images multipart, campo file Sube una imagen (12 MB como máximo); la primera pasa a ser la principal
PUT /api/parts/{id}/sourcing/{supplier} {supplier_part_number, unit_price?, currency?, product_url?, minimum_order_qty?, pack_size?, lead_time_days?, is_preferred?, notes?} Crea la fila del proveedor para el artículo (201) o la actualiza (200); los campos que no se envían conservan su valor. La primera fuente de un artículo pasa a ser la preferente
POST /api/demand {channel, reference, part_id | part_number | channel_part_number, quantity, due_at?, kind?, notes?} Demand received (demanda recibida). Idempotente según (channel, reference): una repetición mientras está abierta actualiza la cantidad y la fecha y responde 200; una primera llegada responde 201. Una referencia cerrada o cancelada no se toca
POST /api/demand/{id}/ship {quantity?, location_id? | location_code?, lots?} Demand shipped (demanda enviada). Da salida a esa cantidad (si está vacía, al resto) desde la ubicación, o desde el montón más grande del artículo, como un movimiento del registro que hace referencia a la demanda; cierra la demanda cuando se ha enviado todo. lots indica los lotes o números de serie enviados, estén donde estén: obligatorio para un producto con trazabilidad por número de serie. 409 si no hay suficiente disponible
POST /api/demand/{id}/cancel Demand cancelled (demanda cancelada)
PUT /api/channels/{code}/listings/{part_id} {channel_part_number?, is_listed?, notes?} Publica un artículo en el canal, o registra la referencia del canal para él

Para que se te avise cuando ocurra algo en lugar de preguntar, consulta webhooks.

Para un asistente

Lo que necesita un agente de chat para ayudar a una persona a no perder el hilo: una llamada para «qué requiere mi atención», búsquedas por los nombres que usa la gente, y los botones. Cada elemento lleva una url en la que puede hacer clic una persona.

Endpoint Devuelve
GET /api/brief Todo lo que requiere atención ahora: tareas abiertas, estados de los puestos de trabajo, órdenes de fabricación en borrador y abiertas, pedidos de compra pendientes de aprobación, aprobados pero sin enviar, y con retraso, órdenes planificadas que toca confirmar, traslados necesarios, faltas y trabajos que, según la comprobación de capacidad, terminarán tarde. headline dice lo mismo en unas pocas frases, lo más urgente primero
GET /api/tasks Todas las tareas abiertas, la más urgente primero, con sus actions
GET /api/planning La página de planificación: órdenes planificadas proposed (propuestas) y firmed (confirmadas) (cada una indica because, el motivo), y la programación de cada grupo de puestos con sus trabajos retrasados
GET /api/demand/{id}/progress «¿Dónde está mi pedido?»: la demanda, el stock del artículo y el suministro entrante, las propuestas de la ejecución de la planificación para este pedido, las órdenes de fabricación que lo fabrican y cuáles de ellas están programadas con retraso. El suministro pertenece al artículo, no al pedido: dos pedidos de un mismo artículo ven el mismo stock

Hacer cosas

Endpoint Cuerpo Requiere Hace
POST /api/tasks/{id}/act {"action": "ready"} operario Pulsa uno de los botones de la tarea
POST /api/tasks/{id}/snooze {"days": 2} operario Oculta la tarea; una semana si no se indica número
POST /api/tasks/{id}/resolve operario Cierra la tarea a mano
POST /api/work-orders {part_number | part_id, location, quantity?, external_ref?, due_at?, priority?, notes?, output_location?, release?} planificador Crea una orden de fabricación: en borrador, o lanzada si release es true. location es el código del lugar de donde toma los artículos (y donde deja lo fabricado, salvo que output_location sea distinto). external_ref es tu nombre para aquello de lo que trata el trabajo. Envía una Idempotency-Key, o un reintento creará una segunda orden
POST /api/work-orders/{id}/issue {part_number | part_id, quantity?, note?, lots?} operario Saca un artículo del almacén para la orden, fuera de su lista de materiales; lots indica sus lotes o números de serie. 409 si no hay suficiente
POST /api/work-orders/{id}/release planificador De borrador a lanzada. Responde con la orden y la disponibilidad de cada paso
POST /api/work-orders/{id}/cancel planificador Cancela una orden sin terminar
POST /api/planning/run planificador Ejecuta la planificación en el momento. Responde con las propuestas
POST /api/planning/firm {"ids": [12, 13], "location": "BHILL"} planificador Convierte propuestas en órdenes de fabricación y pedidos de compra en borrador. Hace falta location (o location_id) cuando entre ellas hay algo que fabricar
POST /api/purchase-orders/{id}/cancel planificador Cancela el pedido
POST /api/purchase-orders/{id}/approve administrador, clave approve De borrador a aprobado
POST /api/purchase-orders/{id}/send planificador, clave approve Registra el pedido como enviado. Foudra no lo transmite al proveedor

Cuatro reglas se aplican a todas ellas:

Cada respuesta tiene summary, una frase apta para repetírsela a una persona, data y acted_as. Rechazos: 403 por el alcance de una clave o el rol de un usuario, 404, 409 cuando el elemento no está en un estado que lo permita (el mensaje dice por qué), 422 para una acción incorrecta o una clave reutilizada.

El caso de la granja de impresión

Consulta /next?material=<loaded> mientras el puesto está inactivo: ¿hay algo listo con lo que está cargado? Si no, /next sin filtro dice qué necesita el primer trabajo; envía una tarea de cambiar filamento y pasa a bloqueado. La persona coloca la bobina, indica Loaded material (material cargado) en la página del puesto de trabajo y pulsa Ready. Después, start; imprime; complete con la cantidad y el filamento realmente usado; status de nuevo inactivo. Una impresión fallida es scrap, no una finalización con cero. El MRP no sabe nada de impresoras.

Las escrituras de la API se registran a nombre del usuario que creó la clave, salvo que X-On-Behalf-Of indique a otra persona (ver arriba).

OAuth

Para una aplicación que actúa en nombre de usuarios de cualquier empresa. Las aplicaciones las registra Foudra (pídelo a través del soporte): recibes un client_id, un secreto para una aplicación de servidor, y se registran tus URI de redirección exactas. El flujo es la concesión de código de autorización con PKCE (RFC 6749, RFC 7636), y se exige PKCE con S256 a todos los clientes.

  1. Envía el navegador a https://foudra.com/oauth/authorize?response_type=code&client_id=…&redirect_uri=…&scope=read%20write&state=…&code_challenge=…&code_challenge_method=S256. El usuario indica qué empresa, inicia sesión allí y ve lo que has pedido.
  2. Si pulsa Allow, el navegador vuelve a redirect_uri?code=…&state=…; si pulsa Deny, a ?error=access_denied. Un código dura 10 minutos y solo funciona una vez.
  3. POST https://foudra.com/oauth/token, codificado como formulario, con autenticación del cliente por HTTP Basic (un cliente público envía client_id en el formulario): grant_type=authorization_code&code=…&redirect_uri=…&code_verifier=…. La respuesta:
{"access_token": "fdo_…", "token_type": "Bearer", "expires_in": 3600,
 "refresh_token": "fdor_…", "scope": "read write",
 "company": {"slug": "acme", "name": "Acme Widgets", "api_base_url": "https://acme.foudra.com"}}

Llama a la API en api_base_url con Authorization: Bearer fdo_…: el token solo funciona allí. Un token caducado o revocado recibe 401 con WWW-Authenticate: Bearer error="invalid_token".

  1. Renuévalo antes o después de la hora con grant_type=refresh_token&refresh_token=fdor_…. Los tokens de renovación rotan: cada uso devuelve uno nuevo y gasta el anterior. Presentar un token de renovación ya gastado se considera un robo y revoca todos los tokens de ese inicio de sesión; el usuario tendrá que volver a autorizarte. Así que guarda el nuevo token de renovación antes de usar el nuevo token de acceso, y nunca reintentes una renovación con el anterior. Un token de renovación que no se usa en 90 días caduca.
  2. POST https://foudra.com/oauth/revoke con token=… (RFC 7009) cuando el usuario se desconecte en tu lado. Revocar un token de renovación revoca también sus tokens de acceso.

Los alcances son los mismos que los de las claves de API: read, write, approve, cada uno incluye el anterior. Lo que obtienes está limitado por tu registro y por el rol del usuario (lectores y operarios, read; planificadores, write; administradores y propietarios, approve), y el límite se vuelve a aplicar en cada petición, de modo que un usuario al que se le baja el rol o se le da de baja se lleva tu acceso con él. El scope de la respuesta del token indica lo que se ha concedido. Las escrituras se atribuyen al usuario, y las acciones informan "acted_as": {"user": …, "application": …}. X-On-Behalf-Of funciona igual que con las claves.

Los errores del endpoint de tokens son los de RFC 6749: {"error": "invalid_grant", "error_description": "…"} con estado 400, o invalid_client con 401.