ForTech Fabric API público Asistente IA 📊 Métricas ← Tablero

API público

Permite que otros sistemas (p. ej. uno de sugerencias o una mesa de ayuda) carguen actividades en la fábrica y sigan su avance: listar los proyectos, crear tareas y consultarlas, y sincronizar el catálogo de clientes al que se asignan las tareas. Las tareas creadas entran en la primera etapa activa del flujo (Backlog) y desde ahí las procesan los agentes como cualquier otra.

Dirección base
/public/v1
Formato
JSON (UTF-8), con nombres de campo en camelCase.
Fechas
Hora local de la fábrica, sin zona: 2026-10-15T12:51:03.

Autenticación

Cada sistema externo tiene su propia clave, que entrega el administrador de la fábrica. Va en el encabezado X-Api-Key de cada petición. Sin clave, o con una que no es válida, la respuesta es 401.

X-Api-Key: <clave del sistema>

La clave solo sirve para este API: no da acceso al resto de la fábrica. Guárdala como un secreto (nunca en el navegador ni en el código) y pide una nueva si se filtra: la anterior se revoca sin afectar a los demás sistemas.

Para el administrador: cómo dar de alta un sistema

En la configuración del API (variables de entorno del App Service), un par por sistema:

PublicApi__Clients__0__Name = Sugerencias
PublicApi__Clients__0__Key  = <clave larga y aleatoria>
PublicApi__Clients__1__Name = Mesa de ayuda
PublicApi__Clients__1__Key  = <otra clave>
PublicApi__TimeZone         = America/Lima

Name es el nombre de origen que se usa si el sistema no manda sourceName. PublicApi__TimeZone es la zona horaria de las PCs de la fábrica (para la fecha de creación); vacía = la del servidor. Para revocar un sistema, se borra su clave.

GET /public/v1/projects

Los proyectos (repositorios) de la fábrica, con los flujos de agentes que usa cada uno y sus etiquetas. Hace falta el id para crear una tarea y, si el proyecto usa más de un flujo, también el del flujo.

curl /public/v1/projects \
  -H "X-Api-Key: $CLAVE"

Respuesta 200

[
  {
    "id": 3, "name": "Portal de clientes", "color": "#4F46E5",
    "flows": [{ "id": 1, "name": "Flujo principal" }, { "id": 2, "name": "Correcciones" }],
    "labels": [{ "id": 7, "name": "bug", "color": "#DC2626" }, { "id": 8, "name": "reportes", "color": "#2563EB" }]
  },
  { "id": 5, "name": "Facturación", "color": "", "flows": [{ "id": 1, "name": "Flujo principal" }], "labels": [] }
]

POST /public/v1/tasks

Crea una tarea en el primer agente activo de su flujo, sin PC asignada: la toma la primera PC que la ejecute. Si el proyecto usa un solo flujo, es ese; si usa varios, hay que indicar cuál en flowId (sin él la respuesta es 400 con los flujos del proyecto).

CampoTipoDescripción
projectIdnúmeroobligatorioId del proyecto (ver proyectos).
flowIdnúmerosegún el proyectoId del flujo de agentes que recorre la tarea (ver proyectos). Obligatorio si el proyecto usa varios flujos.
titletexto ≤ 300obligatorioTítulo de la tarea.
descriptiontexto ≤ 100000opcionalEl requerimiento completo: es lo que reciben los agentes. Cuanto más claro (contexto, criterios de aceptación), mejor resultado.
baseBranchtexto ≤ 200opcionalRama de la que parte el desarrollo y contra la que se abre el PR. Vacía = la última usada en el proyecto.
sourceNametexto ≤ 100opcionalNombre del software de origen. Vacío = el nombre con el que se configuró el sistema.
sourceIdtexto ≤ 100opcionalId de la tarea en el software de origen (p. ej. Ticket 0677). Evita duplicados: ver abajo.
sourceUrlURL ≤ 1000opcionalLink http(s) a la tarea en el software de origen. La fábrica lo muestra en la tarjeta.
prioritytextoopcionalLow, Medium, High o Urgent (también en español: Baja, Media, Alta, Urgente). Vacía = Medium. Es informativa: el orden de ejecución lo define el tablero. Otra que Medium cuenta como elegida a mano y el Triage ya no la cambia.
labelslista de textos ≤ 50opcionalNombres de etiquetas del proyecto (sin importar mayúsculas). Las que el proyecto no tiene se crean con un color de la paleta. Hasta 20.
complexitytextoopcionalLow, Medium o High (también Baja, Media, Alta). Vacía = sin clasificar: la pueden sugerir el Triage o el Planificador. Elige el modelo de los agentes que tienen modelos por nivel; indicada cuenta como elegida a mano y los agentes ya no la cambian.
customerIdtexto (GUID)opcionalId de un cliente del catálogo (ver clientes): para quién es la tarea. Vacío = sin cliente; uno que no existe = 400.
dependsOnlista de númerosopcionalTareas del mismo proyecto de las que depende (hasta 20): no se ejecuta en un agente que ejecuta algo hasta que estén terminadas (ver relaciones). Una inexistente o de otro proyecto = 400.
curl -X POST /public/v1/tasks \
  -H "X-Api-Key: $CLAVE" \
  -H "Content-Type: application/json" \
  -d '{
    "projectId": 3,
    "flowId": 1,
    "title": "Permitir exportar el reporte de ventas a Excel",
    "description": "Los usuarios piden descargar el reporte...",
    "baseBranch": "develop",
    "sourceName": "Sugerencias",
    "sourceId": "Ticket 0677",
    "sourceUrl": "https://sugerencias.empresa.com/tickets/677",
    "priority": "High",
    "labels": ["reportes"],
    "complexity": "Medium",
    "customerId": "3f2504e0-4f89-11d3-9a0c-0305e82c3301"
  }'

Respuesta 201, con la ruta de la tarea en Location

Location: /public/v1/tasks/42

{
  "id": 42,
  "projectId": 3,
  "projectName": "Portal de clientes",
  "flowId": 1,
  "stageId": 1,
  "stageTitle": "Backlog",
  "title": "Permitir exportar el reporte de ventas a Excel",
  "description": "Los usuarios piden descargar el reporte...",
  "baseBranch": "develop",
  "status": "NotStarted",
  "pullRequestUrl": "",
  "createdAt": "2026-10-15T12:51:03",
  "sourceName": "Sugerencias",
  "sourceId": "Ticket 0677",
  "sourceUrl": "https://sugerencias.empresa.com/tickets/677",
  "archivedAt": null,
  "pullRequestState": "Unknown",
  "pullRequestChecks": "None",
  "pullRequestHasConflicts": false,
  "pullRequestMergedAt": null,
  "priority": "High",
  "labels": [{ "id": 8, "name": "reportes", "color": "#2563EB" }],
  "complexity": "Medium",
  "previewUrl": "",
  "previewState": "None",
  "customer": { "id": "3f2504e0-4f89-11d3-9a0c-0305e82c3301", "name": "Banco del Sur" }
}

Sin duplicados

Un mismo sourceName + sourceId crea una sola tarea. Si se vuelve a enviar (p. ej. un reintento tras un corte de red), la respuesta es 409 con el id de la que ya existe en taskId y su ruta en Location: se puede tratar como éxito.

{
  "status": 409,
  "detail": "Ya existe la tarea 42 para Sugerencias Ticket 0677.",
  "taskId": 42
}

GET /public/v1/tasks

Tareas de la fábrica, las más nuevas primero. Todos los filtros son opcionales y se combinan.

ParámetroDescripción
projectIdSolo las de ese proyecto.
stageIdSolo las que están en esa etapa (agente).
statusSolo las que tienen ese estado.
sourceName, sourceIdSolo las de ese origen: p. ej. para encontrar la tarea de un ticket.
customerIdSolo las de ese cliente.
skipCuántas saltar (paginación). Por defecto 0.
takeCuántas devolver, de 1 a 200. Por defecto 50.
curl "/public/v1/tasks?sourceName=Sugerencias&take=20" \
  -H "X-Api-Key: $CLAVE"

Respuesta 200: total es la cantidad que cumple los filtros

{
  "total": 57,
  "items": [ { "id": 42, "title": "...", ... }, ... ]
}

GET /public/v1/tasks/{id}

Una tarea. 404 si no existe (p. ej. si se eliminó en la fábrica).

curl /public/v1/tasks/42 \
  -H "X-Api-Key: $CLAVE"

PATCH /public/v1/tasks/{id}

Cambia la prioridad, las etiquetas, la complejidad o el cliente de una tarea (p. ej. cuando cambian en el ticket). Lo que no se envía no se cambia; labels reemplaza todas las etiquetas de la tarea ([] = ninguna) y las que el proyecto no tiene se crean. Cambiar la prioridad o la complejidad la deja como elegida a mano; "complexity": "" la deja sin clasificar (y los agentes pueden volver a sugerirla). customerId es el id de un cliente del catálogo; "customerId": "" deja la tarea sin cliente. Responde 200 con la tarea, 400 si un campo no es válido o 404.

curl -X PATCH /public/v1/tasks/42 \
  -H "X-Api-Key: $CLAVE" \
  -H "Content-Type: application/json" \
  -d '{ "priority": "Urgent", "labels": ["reportes", "cliente grande"], "complexity": "High",
        "customerId": "3f2504e0-4f89-11d3-9a0c-0305e82c3301" }'

PUT /public/v1/tasks/{id}/relations

Cambia la tarea madre y las dependencias de una tarea (las reemplaza). Una tarea con dependencias no se ejecuta mientras alguna no esté terminada: queda bloqueada (isBlocked) y se ejecuta sola cuando se desbloquea. Terminada = archivada, con su PR mezclado o, si no tiene PR, al final de su flujo. La madre y las dependencias tienen que ser del mismo proyecto y no pueden formar un ciclo. stackOnDependency (opcional, con una sola dependencia) hace que parta de la rama de esa tarea y que su PR vaya contra esa rama. Responde 200 con la tarea, 404 o 409 con el motivo si no se puede.

curl -X PUT /public/v1/tasks/42/relations \
  -H "X-Api-Key: $CLAVE" \
  -H "Content-Type: application/json" \
  -d '{ "parentTaskId": 40, "dependsOn": [41], "stackOnDependency": false }'

POST /public/v1/tasks/{id}/approvals

Algunas etapas son puntos de aprobación (p. ej. la Aprobación del plan): la tarea espera ahí hasta que una persona decide. El sistema externo también puede decidir: aprobarla (pasa a la etapa siguiente) o pedir cambios (vuelve a la etapa anterior con el comentario, que el agente recibe como instrucción).

CampoDescripción
decisionObligatorio: Approved o ChangesRequested.
commentOpcional al aprobar; obligatorio al pedir cambios (hasta 10.000 caracteres).
authorOpcional: quién decidió en el sistema externo (se guarda junto al nombre del sistema).
curl -X POST /public/v1/tasks/42/approvals   -H "X-Api-Key: $CLAVE" -H "Content-Type: application/json"   -d '{ "decision": "ChangesRequested", "comment": "Falta el caso sin permisos.", "author": "Ana" }'

Respuesta 201: la decisión

{
  "id": 7, "taskId": 42, "stageId": 4, "stageTitle": "Aprobación del plan",
  "decision": "ChangesRequested", "author": "Ana (Sugerencias)", "comment": "Falta el caso sin permisos.",
  "requestedAt": "2026-10-23T10:15:00", "decidedAt": "2026-10-23T11:02:41", "waitingSeconds": 2861,
  "toStageId": 3, "toStageTitle": "Planificador", "feedbackId": 15
}

409 si la tarea no está esperando aprobación (ya se decidió o se movió) o si la etapa no tiene adónde pasarla. GET /public/v1/tasks/{id}/approvals devuelve todas las decisiones de la tarea, de la más antigua a la más reciente.

Clientes

Catálogo de los clientes a los que se asignan las tareas (customerId al crearlas o cambiarlas). Cada cliente tiene solo un id (GUID) y un name (hasta 200 caracteres, sin repetir en la fábrica sin importar mayúsculas). Para sincronizarlo con el sistema externo conviene crear cada cliente con el mismo id que tiene allá.

Método y rutaQué hace
GET /customersLos clientes por nombre: { total, items }. search (opcional) filtra por una parte del nombre, sin importar mayúsculas; paginación con skip y take (1 a 200, 50 por defecto).
GET /customers/{id}Un cliente. 404 si no existe.
POST /customersCrea un cliente: { "name": "…", "id": "…" } (id opcional; sin él la fábrica genera uno). Responde 201 con su ruta en Location; 409 si el id ya existe (con su ruta en Location) o el nombre ya lo tiene otro.
PUT /customers/{id}Crea o renombra el cliente con ese id: { "name": "…" }. Responde 201 si lo creó y 200 si ya existía; se puede enviar cada cliente sin saber si ya está en la fábrica.
DELETE /customers/{id}Elimina el cliente: sus tareas quedan sin cliente. Responde 204, o 404 si no existe.
curl -X PUT /public/v1/customers/3f2504e0-4f89-11d3-9a0c-0305e82c3301 \
  -H "X-Api-Key: $CLAVE" \
  -H "Content-Type: application/json" \
  -d '{ "name": "Banco del Sur" }'

Respuesta 201 (o 200 si ya existía)

{ "id": "3f2504e0-4f89-11d3-9a0c-0305e82c3301", "name": "Banco del Sur" }
curl "/public/v1/customers?search=banco&take=20" \
  -H "X-Api-Key: $CLAVE"

Respuesta 200: total es la cantidad que cumple la búsqueda

{
  "total": 2,
  "items": [
    { "id": "3f2504e0-4f89-11d3-9a0c-0305e82c3301", "name": "Banco del Sur" },
    { "id": "9b1deb4d-3b7d-4bad-9bdd-2b0d7b3dcb6d", "name": "Banco Norte" }
  ]
}

La tarea

CampoDescripción
idId de la tarea en la fábrica.
projectId, projectNameProyecto.
flowIdFlujo de agentes que recorre.
stageId, stageTitleEtapa (agente) en la que está ahora: Backlog, Planificador, Desarrollador, revisores…
title, descriptionTítulo y requerimiento.
baseBranchRama base.
statusEstado de la ejecución en su etapa actual (ver abajo).
pullRequestUrlLink al pull request, cuando ya se creó (vacío si no).
createdAtFecha de creación (hora local de la fábrica).
sourceName, sourceId, sourceUrlOrigen. Vacíos en las tareas creadas en la fábrica.
archivedAtCuándo se archivó (hora local de la fábrica): ya no está en el tablero, pero sigue contando en las métricas. null si no está archivada.
priorityLow, Medium, High o Urgent (informativa).
labelsEtiquetas de la tarea: id, name y color.
complexityLow, Medium, High o null (sin clasificar): elige el modelo de los agentes que tienen modelos por nivel.
customerCliente para el que es la tarea: id y name (ver clientes); null si no tiene.
pullRequestStateEstado del pull request en su plataforma: Unknown (sin PR o todavía no consultado), Open, Merged o Declined (rechazado o cerrado sin mezclar). Lo actualiza cada pocos minutos la PC de la tarea.
pullRequestChecksResultado del CI del PR: None, Pending, Passed o Failed.
pullRequestHasConflictsSi el PR tiene conflictos con la rama base.
pullRequestMergedAtCuándo se mezcló el PR (hora local de la fábrica); null si no está mezclado.
parentTaskIdTarea madre; null si no es subtarea.
isBlockedLlegó a un agente que ejecuta algo pero alguna dependencia no está terminada: no se ejecuta hasta que se desbloquee.
dependenciesTareas de las que depende: id, title e isDone (ya no la bloquea).
dependentsTareas que dependen de esta, con isDone.
subtasksSubtareas, con isDone: el avance de la madre.
previewUrlLink al entorno de prueba publicado de la tarea, para que QA o el cliente la prueben sin instalar nada (vacío si no tiene uno publicado). Lo despliega el pipeline del proyecto.
previewStateEstado del entorno de prueba: None (nunca tuvo), Deploying, Deployed, DeployFailed, Removing, Removed (se eliminó al terminar la tarea) o RemoveFailed.

Estados

NotStartedEsperando a que el agente de su etapa la ejecute. Vuelve a este estado cada vez que la tarea cambia de etapa.
InProgressEl agente la está ejecutando.
CompletedEl agente terminó bien.
RejectedEl agente la rechazó (p. ej. un revisor): según el flujo, vuelve a una etapa anterior.
ErrorFalló la ejecución: alguien de la fábrica debe revisarla.

Errores

Los errores vienen en formato Problem Details.

400Datos inválidos. errors trae el detalle por campo.
401Falta la clave o no es válida.
404La tarea o el cliente no existe.
409La tarea ya existe para ese origen (ver sin duplicados), no se puede decidir sobre ella (ver aprobaciones) o ya hay un cliente con ese id o ese nombre (ver clientes). detail trae el motivo.
500Error del servidor: se puede reintentar más tarde.
{
  "title": "One or more validation errors occurred.",
  "status": 400,
  "errors": {
    "title": [ "El título es obligatorio." ],
    "projectId": [ "El proyecto no existe." ]
  }
}

Integración recomendada

  1. Pedir los proyectos y guardar a qué proyecto va cada tipo de actividad.
  2. Si las tareas son de clientes, sincronizar el catálogo con PUT /customers/{id} (con el id del sistema externo) cuando se crea o cambia un cliente, y DELETE cuando se elimina; luego crear las tareas con su customerId.
  3. Crear la tarea siempre con sourceId y sourceUrl: así se puede reintentar sin duplicar y desde la fábrica se llega al ticket.
  4. Guardar el id que devuelve la fábrica (o el que viene en el 409).
  5. Consultar el avance de vez en cuando (no más de una vez por minuto) con GET /tasks/{id}, o todas las propias con GET /tasks?sourceName=…, y mostrar stageTitle, status, el pullRequestUrl y el pullRequestState en el sistema de origen (Merged = el cambio ya está en la rama base).