Núcleo digital · referencia de la API REST

Una sola API

El núcleo DWFW expone una API REST en JSON bajo /api. Es la misma que consumen el panel del curador, el admin y la web pública: no hay una API "interna" y otra "externa". Esta referencia se genera del esquema real desplegado — endpoints, campos, permisos y OpenAPI — así que siempre describe lo que hay.

URL base https://dwfw.trabemflex.com/apiOpenAPI 3.0Servidor MCP

01

Introducción

Cada colección del esquema es un recurso REST con las cinco operaciones de siempre (listar, leer, crear, actualizar, borrar) más un recuento. La API la genera Payload CMS a partir del esquema TypeScript, así que no se desincroniza del modelo ni de los permisos: lo que el admin deja hacer es exactamente lo que la API deja hacer.

Convenciones: JSON en UTF-8; ids numéricos; fechas en ISO 8601 (UTC); slugs y valores de select en castellano sin tildes (son identificadores, no textos); cabecera Content-Type: application/json en las escrituras. GraphQL está desactivado: todo es REST.

  • URL base: https://dwfw.trabemflex.com/api (en desarrollo, http://localhost:3000/api).
  • Versionado: la API sigue la versión del esquema del repositorio; los cambios que rompen se anuncian en el README y en este documento.
  • Sin métricas de popularidad: no hay likes, vistas ni ranking en ningún recurso, por doctrina (epojé algorítmica).

02

Inicio rápido

Tres llamadas para ver cómo funciona: lo público sin sesión, iniciar sesión y leer con sesión.

# 1 · Sin sesión: lo público (archivo), en inglés
curl "https://dwfw.trabemflex.com/api/obras?locale=en&depth=1&limit=20"

# 2 · Iniciar sesión (deja la cookie payload-token y devuelve un JWT)
TOKEN=$(curl -s -H "Content-Type: application/json" \
  -d '{"email":"media@dwfw.test","password":"dwfw-demo-2026"}' \
  "https://dwfw.trabemflex.com/api/usuarios/login" | jq -r .token)

# 3 · Con sesión: lo que permitan tus capacidades
curl -H "Authorization: JWT $TOKEN" "https://dwfw.trabemflex.com/api/obras?locale=en&depth=1"

03

Autenticación

La sesión es la de Payload. POST /api/usuarios/login con email y password devuelve el usuario, un JWT y su caducidad (exp), y deja la cookie HttpOnly payload-token. Vale cualquiera de las dos vías: la cookie (navegador, con credentials: include; protegida con CSRF, así que solo se acepta desde el propio origen) o la cabecera Authorization: JWT <token> (servidores, scripts, apps: la vía obligatoria fuera del navegador). El token caduca a las 2 horas; /api/usuarios/refresh-token lo renueva sin volver a pedir la contraseña.

GET /api/usuarios/me devuelve el usuario de la sesión (user: null si no hay). GET /api/access resume, para la sesión actual, qué operaciones y qué campos se permiten en cada colección: es la forma recomendada de construir interfaces que se adapten al rol.

# Iniciar sesión: devuelve el usuario, un JWT y deja la cookie payload-token (HttpOnly)
curl -H "Content-Type: application/json" \
  -d '{"email":"media@dwfw.test","password":"dwfw-demo-2026"}' \
  "https://dwfw.trabemflex.com/api/usuarios/login"
# → { "user": { … }, "token": "<jwt>", "exp": 1790000000 }   (+ cookie payload-token)

# Con la cabecera Authorization: JWT (scripts, servidores, apps)
curl -H "Authorization: JWT <token>" "https://dwfw.trabemflex.com/api/usuarios/me"

# En el navegador basta la cookie (mismo origen; con csrf activo la cookie se ignora sin cabecera Origin propia)
fetch('/api/usuarios/me', { credentials: 'include' })

# Renovar el token antes de que caduque (2 h por defecto)
curl -H "Authorization: JWT <token>" -X POST "https://dwfw.trabemflex.com/api/usuarios/refresh-token"

# Qué puede hacer esta sesión en cada colección y campo
curl -H "Authorization: JWT <token>" "https://dwfw.trabemflex.com/api/access"

# Cerrar sesión
curl -H "Authorization: JWT <token>" -X POST "https://dwfw.trabemflex.com/api/usuarios/logout"

04

Permisos: roles y capacidades

El acceso lo decide un RBAC por capacidad, no por vistas. Un usuario acumula roles; cada rol es un atajo a un conjunto de capacidades; y las concesiones añaden capacidades sueltas, con fecha de caducidad opcional (se apagan solas). La matriz de abajo sale del código (src/access/capabilities.ts).

Dos recortes más: scopeEdiciones limita la lectura de un partner a sus ediciones, y obra:editar_propia permite al creador tocar solo obras de su ficha de autor. Una petición sin capacidad recibe 403; una lectura restringida devuelve solo lo permitido (Media, sin obra:leer, ve únicamente el archivo).

Capacidaddireccionfounding curatorcuradorinvestigadorcreadorpartnermediaadvisory
obra:leer●●●●●●·●
obra:crear●●●·●···
obra:editar●●●·····
obra:editar_propia●···●···
obra:borrar●·······
autor:leer●●●●●●●●
autor:gestionar●·······
autor:editar_taller●···●···
nodo:leer●●●●●··●
nodo:gestionar●●●·····
conexion:leer●●●●●··●
conexion:proponer●●●●····
conexion:confirmar●●●·····
significado:leer●●●●●··●
significado:asignar●●●·····
movimiento:avanzar●●●·····
index:leer●●●●···●
edicion:leer●●●●●●●●
edicion:gestionar●·······
capitulo:gestionar●·······
pieza:gestionar●·······
usuario:gestionar●·······

05

Consultas: parámetros, filtros y paginación

Todos los listados aceptan los mismos parámetros. Las respuestas de lista vienen paginadas con metadatos (totalDocs, totalPages, nextPage…).

ParámetroUso
localeIdioma del contenido: es (defecto), en, zh, ja. Vale para leer y para escribir. `all` devuelve todas las traducciones.
fallback-localeIdioma al que caer si falta la traducción (por defecto es). `none` para no caer.
depthNiveles de relaciones que se resuelven (0 = solo ids). Por defecto 2. Cuanto menor, más rápida la respuesta.
limit · pagePaginación: tamaño de página (defecto 10) y número de página (desde 1). `pagination=false` devuelve todo (úsalo con cuidado).
sortCampo de orden; con guion delante es descendente (sort=-createdAt). Varios campos separados por coma.
where[campo][op]=valorFiltros. Se anidan con where[and][n][campo][op] / where[or][n][campo][op]. Campos de relaciones con punto: autor.nombre, origen.value.
select[campo]=trueLimitar los campos devueltos. populate[coleccion][campo]=true elige qué campos se resuelven en las relaciones.
draft · joinsNo aplica en este esquema (no hay borradores ni joins). Se documenta para que no sorprenda en la respuesta de /api/access.

Operador

OperadorSignificado
equalsIgual. Con selects, ids de relación, booleanos y fechas ISO.
not_equalsDistinto.
in · not_inDentro / fuera de una lista separada por comas.
contains · likeTexto: contiene (sin distinguir mayúsculas) / patrón con todas las palabras.
greater_than · greater_than_equalMayor / mayor o igual: números y fechas.
less_than · less_than_equalMenor / menor o igual.
existsexists=true / false: el campo tiene valor o no.
near · within · intersectsSolo campos de tipo point (no hay en este esquema).
# Lista paginada y ordenada, contenido en japonés, relaciones resueltas dos niveles
curl "https://dwfw.trabemflex.com/api/obras?locale=ja&depth=2&limit=10&page=1&sort=-updatedAt"

# Filtro simple: obras del archivo (movimiento 7)
curl -g "https://dwfw.trabemflex.com/api/obras?where[estadoCuratorial][equals]=movimiento_7"

# Filtros combinados: and / in / exists
curl -g "https://dwfw.trabemflex.com/api/obras?where[and][0][tipo][in]=modelo_3d,avatar&where[and][1][edicion][exists]=true"

# Relación polimórfica: filtrar por value Y relationTo (los ids se repiten entre colecciones)
curl -g "https://dwfw.trabemflex.com/api/conexiones?where[or][0][origen.value][equals]=1&where[or][0][origen.relationTo][equals]=obras"

# Solo algunos campos (respuestas más ligeras)
curl -g "https://dwfw.trabemflex.com/api/obras?select[titulo]=true&select[estadoCuratorial]=true&depth=0"

# Recuento sin traer documentos
curl -g "https://dwfw.trabemflex.com/api/obras/count?where[estadoCuratorial][equals]=movimiento_7"
{
  "docs": [ { "id": 3, "titulo": "Metsä · Micelio v3", "estadoCuratorial": "movimiento_7", "…": "…" } ],
  "totalDocs": 1,
  "limit": 10,
  "totalPages": 1,
  "page": 1,
  "pagingCounter": 1,
  "hasPrevPage": false,
  "hasNextPage": false,
  "prevPage": null,
  "nextPage": null
}

06

Idiomas

El contenido (títulos, descripciones, notas, alt…) se guarda por idioma: español (defecto), inglés, chino simplificado y japonés. Se pide con ?locale=; si una traducción no existe se sirve el castellano, así que nada aparece vacío. ?locale=all devuelve todas las traducciones a la vez ({ es, en, zh, ja }). Los ids técnicos no se traducen: son datos.

Escribir en otro idioma es un PATCH con ?locale=xx enviando solo los campos localizados: las relaciones y los estados no cambian por idioma. En la referencia de cada recurso, la columna "Localizado" marca qué campos lo son.

07

Escritura

POST crea, PATCH actualiza parcialmente (solo lo enviado), DELETE borra. Las relaciones se envían por id; las polimórficas como { relationTo, value }. Los archivos van a Media por multipart/form-data.

  • Validación: los campos obligatorios y los valores de select se comprueban en el servidor; un 400 detalla campo y motivo.
  • Hooks de doctrina: procedencia=humano se fuerza en Significados; estadoCuratorial solo cambia con movimiento:avanzar; el creador no puede adjudicarse obras ajenas.
  • Idempotencia: PATCH y DELETE son idempotentes; POST no (cada llamada crea un documento).
# Crear (requiere obra:crear). Se guarda en la locale indicada; relaciones por id
curl -H "Authorization: JWT $TOKEN" -H "Content-Type: application/json" -X POST \
  -d '{"titulo":"Nueva obra","tipo":"modelo_3d","autor":1,"nodoOrigen":"laboratorio_ia"}' \
  "https://dwfw.trabemflex.com/api/obras?locale=es"
# → 201 { "message": "…", "doc": { "id": 4, … } }

# Actualización parcial en OTRA locale: solo los campos localizados; lo demás no cambia
curl -H "Authorization: JWT $TOKEN" -H "Content-Type: application/json" -X PATCH \
  -d '{"titulo":"New work","descripcionFormal":{"silueta":"…"}}' \
  "https://dwfw.trabemflex.com/api/obras/4?locale=en"

# Relación polimórfica al escribir: { relationTo, value }
curl -H "Authorization: JWT $TOKEN" -H "Content-Type: application/json" -X POST \
  -d '{"origen":{"relationTo":"obras","value":4},"destino":{"relationTo":"nodos","value":2},"tipoRelacion":"dialoga_con","procedencia":"humano","estado":"propuesta"}' \
  "https://dwfw.trabemflex.com/api/conexiones"

# Confirmar una conexión (requiere conexion:confirmar; si no, 403 con mensaje explícito)
curl -H "Authorization: JWT $TOKEN" -X PATCH -H "Content-Type: application/json" \
  -d '{"estado":"confirmada","procedencia":"humano"}' "https://dwfw.trabemflex.com/api/conexiones/1"

# Subir un archivo a Media: multipart con `file` y `_payload` (JSON con los demás campos)
curl -H "Authorization: JWT $TOKEN" -F "file=@look-03.png" -F '_payload={"alt":"Look 03"}' "https://dwfw.trabemflex.com/api/media"

# Borrar (requiere obra:borrar)
curl -H "Authorization: JWT $TOKEN" -X DELETE "https://dwfw.trabemflex.com/api/obras/4"

08

Referencia de recursos

Un bloque por colección: ruta, quién lee y quién escribe, campos con tipo y localización, y un ejemplo real tomado de la API pública en el momento de renderizar esta página.

RecursoRutaLecturaEscritura (capacidad)
Obras/api/obraspública solo el archivo (mov. 7); el resto según capacidadesobra:crear · obra:editar / obra:editar_propia · obra:borrar · movimiento:avanzar (estadoCuratorial)
Autores/api/autorespúblicaautor:gestionar
Nodos/api/nodospúblicanodo:gestionar
Conexiones/api/conexionespúblicaconexion:proponer · conexion:confirmar
Significados/api/significadospúblicasignificado:asignar
Movimientos curatoriales/api/movimientospúblicamovimiento:avanzar
Ediciones/api/edicionespúblicaedicion:gestionar
Capítulos de marca/api/capitulospúblicacapitulo:gestionar
Piezas audiovisuales/api/piezaspúblicapieza:gestionar
Activos técnicos/api/mediapública solo el archivo (mov. 7); el resto según capacidadesobra:crear (subir) · propio: editar / borrar si no publicado · obra:editar: todo y publicar
Usuarios/api/usuarioscon sesión (cada uno se ve a sí mismo; usuario:gestionar ve a todos)usuario:gestionar

Recurso

Obras

/api/obras

Capa 1. La obra como documento cultural: tipo, autor, nodo de origen, estado curatorial (7 movimientos), descripción formal y activos técnicos.

Lectura
pública solo el archivo (mov. 7); el resto según capacidades
Escritura (capacidad)
obra:crear · obra:editar / obra:editar_propia · obra:borrar · movimiento:avanzar (estadoCuratorial)
Operaciones
GET /api/obras GET /api/obras/count GET /api/obras/:id POST /api/obras PATCH /api/obras/:id DELETE /api/obras/:id

Campos

CampoTipoObligatorioLocalizadoValores / destino
titulo Títulotextsísí—
tipo Tiposelectsí—modelo_3davatarcoleccion_digitalprenda_fisicaexperiencia_inmersivacodigo_generativo
autor Autorrelationshipsí—relación a autores
nodoOrigen Nodo de origen (procedencia)selectsí—academico_investigadorcomunidad_digital_emergentelaboratorio_iainstitucion_archivo
estadoCuratorial Estado curatorialselectsí—movimiento_1movimiento_2movimiento_3movimiento_4movimiento_5movimiento_6movimiento_7
descripcionFormal.silueta Siluetatextarea—sí—
descripcionFormal.material Materialtextarea—sí—
descripcionFormal.sintaxisVisual Sintaxis visualtextarea—sí—
activosTecnicos Activos técnicos (vértices, texturas, vídeo)join———
edicion Ediciónrelationship——relación a ediciones

Ejemplo real (GET, depth=0, vista pública)

GET https://dwfw.trabemflex.com/api/obras?depth=0&locale=es&limit=1

{
  "id": 3,
  "titulo": "Metsä · Micelio v3",
  "tipo": "codigo_generativo",
  "autor": 3,
  "nodoOrigen": "laboratorio_ia",
  "estadoCuratorial": "movimiento_7",
  "descripcionFormal": {
    "silueta": null,
    "material": "Crecimiento de micelio simulado como generador de patrón.",
    "sintaxisVisual": "La forma no se dibuja: se cultiva."
  },
  "activosTecnicos": {
    "docs": [],
    "hasNextPage": false
  },
  "edicion": null,
  "updatedAt": "2026-08-22T18:29:49.582Z",
  "createdAt": "2026-08-21T07:12:39.411Z"
}

Recurso

Autores

/api/autores

Capa 2. Diseñadores, colectivos, laboratorios e instituciones. El origen cultural es descriptivo, nunca criterio de orden.

Lectura
pública
Escritura (capacidad)
autor:gestionar
Operaciones
GET /api/autores GET /api/autores/count GET /api/autores/:id POST /api/autores PATCH /api/autores/:id DELETE /api/autores/:id

Campos

CampoTipoObligatorioLocalizadoValores / destino
nombre Nombretextsí——
tipo Tiposelectsí—diseñadorinvestigadorartistacolectivolaboratorioinstitucion
origenCultural Origen culturaltext—sí—
bio Biotextarea—sí—
trayectoria Trayectoriatextarea—sí—
nodos Nodosrelationship[]——relación a nodos
taller.capacidad Capacidad del tallertextarea—sí—
taller.materiales Materiales que trabajaselect[]——algodonlanalinosedacueropuntodenimfibras_sinteticasmateriales_biobasadostextil_tecnicosin_material_fisico
taller.tecnologias Tecnologíasselect[]——patronaje_digitalmodelado_3dsimulacion_textildiseno_generativoimpresion_3dcorte_laserbordado_digitalestampacion_digitaltejido_punto_industrialconfeccion_artesanaltenido_natural
taller.usaFibrasRecicladas ¿Fibras recicladas?checkbox———
taller.fibrasRecicladasDetalle Fibras recicladas · detalletextarea—sí—
taller.notasSostenibilidad Notas de sostenibilidadtextarea—sí—

Ejemplo real (GET, depth=0, vista pública)

GET https://dwfw.trabemflex.com/api/autores?depth=0&locale=es&limit=1

{
  "id": 3,
  "nombre": "Lab Metsä",
  "tipo": "laboratorio",
  "origenCultural": "Helsinki, Finlandia",
  "bio": "Laboratorio de materiales generativos.",
  "trayectoria": "Spin-off académico centrado en biotextiles simulados.",
  "nodos": [],
  "taller": {
    "capacidad": null,
    "materiales": [],
    "tecnologias": [],
    "usaFibrasRecicladas": null,
    "fibrasRecicladasDetalle": null,
    "notasSostenibilidad": null,
    "procedencia": "humano"
  },
  "updatedAt": "2026-08-22T18:29:48.843Z",
  "createdAt": "2026-08-21T07:12:36.066Z"
}

Recurso

Nodos

/api/nodos

Capa 3. Vértices de la cartografía: corrientes filosóficas, desarrollos técnicos, comunidades, instituciones, tensiones culturales.

Lectura
pública
Escritura (capacidad)
nodo:gestionar
Operaciones
GET /api/nodos GET /api/nodos/count GET /api/nodos/:id POST /api/nodos PATCH /api/nodos/:id DELETE /api/nodos/:id

Campos

CampoTipoObligatorioLocalizadoValores / destino
nombre Nombretextsísí—
tipo Tiposelectsí—corriente_filosoficadesarrollo_tecnologico_previocomunidad_subculturainstituciontension_cultural
descripcion Descripcióntextarea—sí—

Ejemplo real (GET, depth=0, vista pública)

GET https://dwfw.trabemflex.com/api/nodos?depth=0&locale=es&limit=1

{
  "id": 4,
  "nombre": "Autoría en la era de los modelos generativos",
  "tipo": "tension_cultural",
  "descripcion": null,
  "updatedAt": "2026-08-22T18:29:49.224Z",
  "createdAt": "2026-08-21T07:12:36.120Z"
}

Recurso

Conexiones

/api/conexiones

Capa 3. Aristas tipadas del grafo. Las de procedencia máquina nacen como propuesta; solo un humano las confirma.

Lectura
pública
Escritura (capacidad)
conexion:proponer · conexion:confirmar
Operaciones
GET /api/conexiones GET /api/conexiones/count GET /api/conexiones/:id POST /api/conexiones PATCH /api/conexiones/:id DELETE /api/conexiones/:id

Campos

CampoTipoObligatorioLocalizadoValores / destino
origen Origenrelationshipsí—relación a obrasnodos
destino Destinorelationshipsí—relación a obrasnodos
tipoRelacion Tipo de relaciónselectsí—dialoga_conposibilitada_porconversacion_paralela_con
procedencia Procedenciaselectsí—maquinahumano
estado Estadoselectsí—propuestaconfirmadadescartada
confirmadaPor Confirmada porrelationship——relación a usuarios
confirmadaEn Confirmada endate———
nota Notatextarea—sí—

Ejemplo real (GET, depth=0, vista pública)

GET https://dwfw.trabemflex.com/api/conexiones?depth=0&locale=es&limit=1

{
  "id": 6,
  "origen": {
    "relationTo": "obras",
    "value": 2
  },
  "destino": {
    "relationTo": "obras",
    "value": 1
  },
  "tipoRelacion": "conversacion_paralela_con",
  "procedencia": "maquina",
  "estado": "propuesta",
  "confirmadaPor": null,
  "confirmadaEn": null,
  "nota": "Coincidencia en el campo silueta: ambas describen la silueta por lo que le falta de sostén estructural. Aguacero 01: \"Volumen inflado, sin punto de apoyo.\" Kata: \"Hombro desestructurado: la autoridad se retira del cuerpo.\" Vocabulario compartido: \"sin punto de apoyo\" / \"desestructurado\" / \"se retira\" — en las dos la forma se define por la ausencia de estructura que la sostenga, no por lo que añade. Divergen en tipo (coleccion_digital vs modelo_3d), nodo de origen (comunidad_digital_emergente vs academico_investigador) y material (plástico de baja poligonización vs lana simulada), y no tienen conexión entre sí en la cartografía. Solo se usa la descripción formal; no se usa el significado asignado a Kata ni el origen cultural de los autores. Evidencia sintáctica, pendiente de lectura curatorial.",
  "updatedAt": "2026-09-03T07:10:25.005Z",
  "createdAt": "2026-09-03T07:10:25.005Z"
}

Recurso

Significados

/api/significados

Capa 4. La pregunta hermenéutica fija y las lecturas. Siempre de procedencia humana (lo fuerza el servidor).

Lectura
pública
Escritura (capacidad)
significado:asignar
Operaciones
GET /api/significados GET /api/significados/count GET /api/significados/:id POST /api/significados PATCH /api/significados/:id DELETE /api/significados/:id

Campos

CampoTipoObligatorioLocalizadoValores / destino
obra Obrarelationshipsí—relación a obras
tensionSubyacente ¿A qué tensión de la sociedad contemporánea responde esta creación?textareasísí—
lectura Lecturasarray———
lectura[].tipo Tiposelect——antropologicaeticapoetica
lectura[].texto Textotextarea—sí—
textosTeoricos Aparato crítico asociadotextarea—sí—
curador Curadorrelationship——relación a usuarios
procedencia Procedencia · solo lecturatext———

Ejemplo real (GET, depth=0, vista pública)

GET https://dwfw.trabemflex.com/api/significados?depth=0&locale=es&limit=1

{
  "id": 2,
  "obra": 3,
  "tensionSubyacente": "jhksjhdkae",
  "lectura": [],
  "textosTeoricos": null,
  "curador": 1,
  "procedencia": "humano",
  "updatedAt": "2026-09-04T12:02:19.341Z",
  "createdAt": "2026-09-04T12:02:19.340Z"
}

Recurso

Movimientos curatoriales

/api/movimientos

Workflow curatorial: los siete movimientos por obra, con responsable y procedencia.

Lectura
pública
Escritura (capacidad)
movimiento:avanzar
Operaciones
GET /api/movimientos GET /api/movimientos/count GET /api/movimientos/:id POST /api/movimientos PATCH /api/movimientos/:id DELETE /api/movimientos/:id

Campos

CampoTipoObligatorioLocalizadoValores / destino
obra Obrarelationshipsí—relación a obras
movimiento Movimientoselectsí—movimiento_1movimiento_2movimiento_3movimiento_4movimiento_5movimiento_6movimiento_7
estado Estadoselectsí—pendienteen_cursocompletado
responsable Responsablerelationship——relación a usuarios
procedencia Procedenciaselectsí—maquinahumano
notas Notastextarea—sí—

Ejemplo real (GET, depth=0, vista pública)

GET https://dwfw.trabemflex.com/api/movimientos?depth=0&locale=es&limit=1

{
  "id": 21,
  "obra": 3,
  "movimiento": "movimiento_7",
  "estado": "en_curso",
  "responsable": 2,
  "procedencia": "humano",
  "updatedAt": "2026-08-21T07:12:40.179Z",
  "createdAt": "2026-08-21T07:12:40.179Z"
}

Recurso

Ediciones

/api/ediciones

Event Stack. Cada edición (fechas, ciudad anfitriona, país en foco).

Lectura
pública
Escritura (capacidad)
edicion:gestionar
Operaciones
GET /api/ediciones GET /api/ediciones/count GET /api/ediciones/:id POST /api/ediciones PATCH /api/ediciones/:id DELETE /api/ediciones/:id

Campos

CampoTipoObligatorioLocalizadoValores / destino
nombre Nombretextsísí—
fechaInicio Fecha de iniciodatesí——
fechaFin Fecha de findate———
ciudadAnfitriona Ciudad anfitriona (Country in Focus)text—sí—
paisEnFoco País en focotext—sí—

Ejemplo real (GET, depth=0, vista pública)

GET https://dwfw.trabemflex.com/api/ediciones?depth=0&locale=es&limit=1

{
  "id": 1,
  "nombre": "DWFW · Edición I",
  "fechaInicio": "2027-03-12T00:00:00.000Z",
  "fechaFin": "2027-03-15T00:00:00.000Z",
  "ciudadAnfitriona": "Madrid",
  "paisEnFoco": "España",
  "updatedAt": "2026-08-22T18:29:48.495Z",
  "createdAt": "2026-08-21T07:12:35.921Z",
  "fase": "antes",
  "cuentaAtras": 14386975
}

Recurso

Capítulos de marca

/api/capitulos

Event Stack. Capítulos de marca de una edición: sección, continente, duración, obras.

Lectura
pública
Escritura (capacidad)
capitulo:gestionar
Operaciones
GET /api/capitulos GET /api/capitulos/count GET /api/capitulos/:id POST /api/capitulos PATCH /api/capitulos/:id DELETE /api/capitulos/:id

Campos

CampoTipoObligatorioLocalizadoValores / destino
titulo Títulotextsísí—
edicion Ediciónrelationshipsí—relación a ediciones
autor Autorrelationship——relación a autores
duracionMin Duración (min)number———
seccion Secciónselect——country_in_focusicons_of_designfuture_labconversaciones
continente Continenteselect——asiaoceaniaeuropaafricaamerica
obras Obrasrelationship[]——relación a obras

Ejemplo real (GET, depth=0, vista pública)

GET https://dwfw.trabemflex.com/api/capitulos?depth=0&locale=es&limit=1

{
  "id": 3,
  "titulo": "Conversaciones · Autoría y máquina",
  "edicion": 1,
  "autor": 2,
  "duracionMin": 60,
  "seccion": "conversaciones",
  "continente": "america",
  "obras": [
    2
  ],
  "updatedAt": "2026-08-22T19:21:09.427Z",
  "createdAt": "2026-08-22T19:21:09.303Z"
}

Recurso

Piezas audiovisuales

/api/piezas

Event Stack. Piezas audiovisuales (premiere, conversación, making of…) con referencia de streaming.

Lectura
pública
Escritura (capacidad)
pieza:gestionar
Operaciones
GET /api/piezas GET /api/piezas/count GET /api/piezas/:id POST /api/piezas PATCH /api/piezas/:id DELETE /api/piezas/:id

Campos

CampoTipoObligatorioLocalizadoValores / destino
titulo Títulotextsísí—
tipo Tiposelect——premiereconversacionmaking_ofhero_lookreemisionclip
capitulo Capítulorelationship——relación a capitulos
obra Obrarelationship——relación a obras
urlStreaming URL de streamingtext———

Ejemplo real (GET, depth=0, vista pública)

GET https://dwfw.trabemflex.com/api/piezas?depth=0&locale=es&limit=1

{
  "id": 3,
  "titulo": "Conversación con Colectivo Aguacero",
  "tipo": "conversacion",
  "capitulo": 3,
  "obra": 2,
  "urlStreaming": null,
  "updatedAt": "2026-08-22T19:21:09.874Z",
  "createdAt": "2026-08-22T19:21:09.764Z"
}

Recurso

Activos técnicos

/api/media

Activos técnicos: imágenes (≤50 MB), vídeo (≤2 GB), modelos 3D .glb/.gltf/.usdz/.fbx/.obj (≤500 MB) y PDF (≤20 MB). Privados por defecto (curación a ciegas): los ve quien los subió y curaduría; se publican al llegar la obra al movimiento 7. Almacenados en Cloudflare R2; vídeo y 3D se suben directos al bucket. Ficha: descripción, créditos, materiales, uso de IA, licencia, software.

Lectura
pública solo el archivo (mov. 7); el resto según capacidades
Escritura (capacidad)
obra:crear (subir) · propio: editar / borrar si no publicado · obra:editar: todo y publicar
Operaciones
GET /api/media GET /api/media/count GET /api/media/:id POST /api/media PATCH /api/media/:id DELETE /api/media/:id

Campos

CampoTipoObligatorioLocalizadoValores / destino
titulo Títulotext—sí—
alt Texto alternativotext—sí—
categoria Categoría · solo lecturaselect——imagenvideomodelo3ddocumento
obra Obrarelationship——relación a obras
publico Públicocheckbox———
subidoPor Subido por · solo lecturarelationship——relación a usuarios
ficha.descripcion Descripcióntextarea—sí—
ficha.creditos Créditos / autoríatext———
ficha.materiales Materiales / técnicatext—sí—
ficha.usoIA Uso de IAselect——ningunoasistidogenerativo
ficha.licencia Licenciaselect——todos_los_derechoscc_bycc_by_nccc_by_nc_nd
ficha.software Software / pipelinetext———

Ejemplo real (GET, depth=0, vista pública)

Sin documentos visibles sin sesión.

Recurso

Usuarios

/api/usuarios

Cuentas con roles y capacidades extra (concesiones con caducidad) y, opcionalmente, vinculadas a un autor.

Lectura
con sesión (cada uno se ve a sí mismo; usuario:gestionar ve a todos)
Escritura (capacidad)
usuario:gestionar
Operaciones
GET /api/usuarios GET /api/usuarios/count GET /api/usuarios/:id POST /api/usuarios PATCH /api/usuarios/:id DELETE /api/usuarios/:id POST …/login POST …/logout GET …/me POST …/refresh-token

Campos

CampoTipoObligatorioLocalizadoValores / destino
nombre Nombretextsí——
roles Rolesselect[]sí—direccionfounding_curatorcuradorinvestigadorcreadorpartnermediaadvisory
concesiones Capacidades extra (temporales o permanentes)array———
concesiones[].capacidad Capacidadselectsí—obra:leerobra:crearobra:editarobra:editar_propiaobra:borrarautor:leerautor:gestionarautor:editar_tallernodo:leernodo:gestionarconexion:leerconexion:proponerconexion:confirmarsignificado:leersignificado:asignarmovimiento:avanzarindex:leeredicion:leeredicion:gestionarcapitulo:gestionarpieza:gestionarusuario:gestionar
concesiones[].caduca Caducadate———
concesiones[].motivo Motivotext———
scopeEdiciones Acotado a estas edicionesrelationship[]——relación a ediciones
autor Autorrelationship——relación a autores
enableAPIKey checkbox———
apiKey text———
email emailsí——

Ejemplo real (GET, depth=0, vista pública)

Sin documentos visibles sin sesión.

09

Errores

Los errores llegan como JSON con el código HTTP correspondiente y un array errors. Los hooks propios usan APIError con código explícito y mensaje en el idioma de la petición (Accept-Language).

CódigoCuándo
200 · 201OK. POST devuelve 201 con { message, doc }; PATCH y DELETE, 200.
400Validación o petición mal formada. `errors[0].data.errors` lista campo y motivo.
401Sin sesión (o token caducado) en una operación que la exige.
403Sesión sin la capacidad necesaria; también las reglas de doctrina (confirmar conexión sin conexion:confirmar).
404El documento no existe — o no es visible para esta sesión (el RBAC no revela existencia).
413 · 415Subida demasiado grande o tipo de archivo no admitido (Media).
500Error interno. Payload devuelve "Something went wrong"; los hooks propios usan APIError con código explícito para no llegar aquí.
HTTP/1.1 403 Forbidden
{ "errors": [ { "message": "Confirmar una conexión es un acto semántico: requiere la capacidad conexion:confirmar." } ] }

HTTP/1.1 400 Bad Request
{ "errors": [ { "name": "ValidationError", "message": "The following field is invalid: titulo",
                "data": { "errors": [ { "message": "This field is required.", "path": "titulo" } ] } } ] }

10

OpenAPI, tipos e integración

La especificación OpenAPI 3.0 se genera del mismo config: impórtala en Postman, Insomnia, Stoplight o genera un cliente con openapi-generator. Los tipos TypeScript del esquema están en src/payload-types.ts (se regeneran con pnpm generate:types).

Descargar openapi.jsonpayload-types.ts · GitHub

// Cliente mínimo en JavaScript (navegador o Node 18+)
const API = 'https://dwfw.trabemflex.com/api'

const res = await fetch(`${API}/usuarios/login`, {
  method: 'POST',
  headers: { 'Content-Type': 'application/json' },
  body: JSON.stringify({ email, password }),
  credentials: 'include', // en el navegador basta la cookie; en servidor usa la cabecera
})
const { token } = await res.json()

// Lectura con el JWT en cabecera
const obras = await fetch(`${API}/obras?locale=ja&depth=1&limit=20`, {
  headers: { Authorization: `JWT ${token}` },
}).then((r) => r.json())

// Tipos TypeScript del esquema (src/payload-types.ts)
import type { Obra, Autor } from './payload-types'

11

Buenas prácticas

  • Pide solo lo que necesitas: depth=0 o 1 y select[...] reducen mucho el tamaño de respuesta.
  • Pagina siempre en listados públicos (limit ≤ 100) y ordena por un campo estable.
  • Filtra relaciones polimórficas por value y relationTo a la vez.
  • Para interfaces por rol, consulta /api/access una vez por sesión en lugar de codificar los roles.
  • Trata los valores de select y las capacidades como identificadores estables; traduce en cliente con las etiquetas, no renombres valores.
  • En servidor usa la cabecera Authorization: JWT y renueva con refresh-token; en navegador deja la cookie.
Procedencia en cada capa · Epojé algorítmica · Los siete movimientos