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:
- Quién. Envía
X-On-Behalf-Of: someone@example.comy el cambio se hace en nombre de ese usuario, con su rol. Sin esa cabecera, quien actúa es el creador de la clave. Ambos deben tener el rol: una clave no puede prestar la autoridad de su creador a otra persona, ni tomar más de la que tiene su creador. La cabecera funciona también en los endpoints de lectura, así que el resumen es el de ese usuario. - Vista previa. Añade
?dry_run=1y la petición lo hace todo de verdad y luego lo deshace. La respuesta es exactamente lo que habría ocurrido, con"dry_run": true; no cambia nada. Úsalo antes de preguntar a una persona «¿lo hago?». - Una vez. Envía una cabecera
Idempotency-Key(cualquier cadena de hasta 200 caracteres, única para cada acción prevista). Una repetición en el plazo de una semana recibe la primera respuesta, con la cabeceraIdempotent-Replay: true, y nada se hace dos veces. La misma clave en una petición distinta se rechaza (422). - Registro. Cada cambio escribe una fila de auditoría que indica la clave y, si se ha dado, en nombre de quién actuó.
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.
- 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. - 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. POST https://foudra.com/oauth/token, codificado como formulario, con autenticación del cliente por HTTP Basic (un cliente público envíaclient_iden 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".
- 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. POST https://foudra.com/oauth/revokecontoken=…(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.