Digital core · REST API reference

One API

The DWFW core exposes a JSON REST API under /api. It is the same one used by the curator's panel, the admin and the public website: there is no "internal" API and an "external" one. This reference is generated from the deployed schema — endpoints, fields, permissions and OpenAPI — so it always describes what is actually there.

Base URL https://dwfw.trabemflex.com/apiOpenAPI 3.0MCP server

01

Introduction

Every collection in the schema is a REST resource with the usual five operations (list, read, create, update, delete) plus a count. Payload CMS generates the API from the TypeScript schema, so it never drifts from the model or the permissions: what the admin allows is exactly what the API allows.

Conventions: UTF-8 JSON; numeric ids; ISO 8601 dates (UTC); slugs and select values in unaccented Spanish (they are identifiers, not texts); Content-Type: application/json on writes. GraphQL is disabled: everything is REST.

  • Base URL: https://dwfw.trabemflex.com/api (in development, http://localhost:3000/api).
  • Versioning: the API follows the schema version of the repository; breaking changes are announced in the README and in this document.
  • No popularity metrics: no likes, views or rankings on any resource, by doctrine (algorithmic epoché).

02

Quick start

Three calls to see how it works: public data without a session, sign in, and read with a session.

# 1 · No session: public data (archive), in English
curl "https://dwfw.trabemflex.com/api/obras?locale=en&depth=1&limit=20"

# 2 · Sign in (sets the payload-token cookie and returns a 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 · With a session: whatever your capabilities allow
curl -H "Authorization: JWT $TOKEN" "https://dwfw.trabemflex.com/api/obras?locale=en&depth=1"

03

Authentication

The session is Payload's. POST /api/usuarios/login with email and password returns the user, a JWT and its expiry (exp), and sets the HttpOnly payload-token cookie. Either channel works: the cookie (browser, with credentials: include; CSRF-protected, so it is only accepted from the site's own origin) or the Authorization: JWT <token> header (servers, scripts, apps: mandatory outside the browser). The token expires after 2 hours; /api/usuarios/refresh-token renews it without asking for the password again.

GET /api/usuarios/me returns the session user (user: null if none). GET /api/access summarises, for the current session, which operations and fields are allowed in each collection: the recommended way to build role-aware interfaces.

# Sign in: returns the user, a JWT and sets the payload-token cookie (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)

# With the Authorization: JWT header (scripts, servers, apps)
curl -H "Authorization: JWT <token>" "https://dwfw.trabemflex.com/api/usuarios/me"

# In the browser the cookie is enough (same origin; with csrf on, the cookie is ignored without a matching Origin header)
fetch('/api/usuarios/me', { credentials: 'include' })

# Refresh the token before it expires (2 h by default)
curl -H "Authorization: JWT <token>" -X POST "https://dwfw.trabemflex.com/api/usuarios/refresh-token"

# What this session can do in each collection and field
curl -H "Authorization: JWT <token>" "https://dwfw.trabemflex.com/api/access"

# Sign out
curl -H "Authorization: JWT <token>" -X POST "https://dwfw.trabemflex.com/api/usuarios/logout"

04

Permissions: roles and capabilities

Access is decided by capability-based RBAC, not by views. A user holds several roles; each role is a shortcut to a set of capabilities; and grants add individual capabilities with an optional expiry date (they switch themselves off). The matrix below comes from the code (src/access/capabilities.ts).

Two more restrictions: scopeEdiciones limits a partner's reads to its editions, and obra:editar_propia lets a creator touch only works linked to their author record. A request without the capability gets 403; a restricted read returns only what is allowed (Media, without obra:leer, sees only the archive).

Capabilitydireccionfounding 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

Queries: parameters, filters and pagination

All listings accept the same parameters. List responses are paginated with metadata (totalDocs, totalPages, nextPage…).

ParameterUsage
localeContent language: es (default), en, zh, ja. Applies to reads and writes. `all` returns every translation.
fallback-localeLanguage to fall back to when a translation is missing (default es). `none` disables fallback.
depthHow many levels of relationships are resolved (0 = ids only). Default 2. Lower is faster.
limit · pagePagination: page size (default 10) and page number (from 1). `pagination=false` returns everything (use with care).
sortSort field; a leading dash means descending (sort=-createdAt). Several fields separated by commas.
where[campo][op]=valorFilters. Nest with where[and][n][field][op] / where[or][n][field][op]. Relationship fields with a dot: autor.nombre, origen.value.
select[campo]=trueLimit the returned fields. populate[collection][field]=true chooses which fields are resolved in relationships.
draft · joinsNot applicable in this schema (no drafts or joins). Documented so they do not surprise you in /api/access.

Operator

OperatorMeaning
equalsEqual. Works with selects, relationship ids, booleans and ISO dates.
not_equalsNot equal.
in · not_inIn / not in a comma-separated list.
contains · likeText: contains (case-insensitive) / pattern with all words.
greater_than · greater_than_equalGreater / greater or equal: numbers and dates.
less_than · less_than_equalLess / less or equal.
existsexists=true / false: the field has a value or not.
near · within · intersectsPoint fields only (none in this schema).
# Paginated, sorted list, Japanese content, relationships resolved two levels deep
curl "https://dwfw.trabemflex.com/api/obras?locale=ja&depth=2&limit=10&page=1&sort=-updatedAt"

# Simple filter: works in the archive (movement 7)
curl -g "https://dwfw.trabemflex.com/api/obras?where[estadoCuratorial][equals]=movimiento_7"

# Combined filters: 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"

# Polymorphic relationship: filter by value AND relationTo (ids repeat across collections)
curl -g "https://dwfw.trabemflex.com/api/conexiones?where[or][0][origen.value][equals]=1&where[or][0][origen.relationTo][equals]=obras"

# Only some fields (lighter responses)
curl -g "https://dwfw.trabemflex.com/api/obras?select[titulo]=true&select[estadoCuratorial]=true&depth=0"

# Count without fetching documents
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

Languages

Content (titles, descriptions, notes, alt…) is stored per language: Spanish (default), English, Simplified Chinese and Japanese. Request it with ?locale=; if a translation does not exist, Spanish is served, so nothing shows up empty. ?locale=all returns every translation at once ({ es, en, zh, ja }). Technical ids are not translated: they are data.

Writing in another language is a PATCH with ?locale=xx sending only the localised fields: relationships and statuses do not change per language. In each resource reference, the "Localised" column marks which fields are.

07

Writing

POST creates, PATCH updates partially (only what you send), DELETE removes. Relationships are sent by id; polymorphic ones as { relationTo, value }. Files go to Media via multipart/form-data.

  • Validation: required fields and select values are checked on the server; a 400 details field and reason.
  • Doctrine hooks: procedencia=humano is enforced on Significados; estadoCuratorial only changes with movimiento:avanzar; a creator cannot claim other people's works.
  • Idempotency: PATCH and DELETE are idempotent; POST is not (each call creates a document).
# Create (requires obra:crear). Stored in the given locale; relationships by 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, … } }

# Partial update in ANOTHER locale: localised fields only; everything else unchanged
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"

# Polymorphic relationship on write: { 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"

# Confirm a connection (requires conexion:confirmar; otherwise 403 with an explicit message)
curl -H "Authorization: JWT $TOKEN" -X PATCH -H "Content-Type: application/json" \
  -d '{"estado":"confirmada","procedencia":"humano"}' "https://dwfw.trabemflex.com/api/conexiones/1"

# Upload a file to Media: multipart with `file` and `_payload` (JSON with the other fields)
curl -H "Authorization: JWT $TOKEN" -F "file=@look-03.png" -F '_payload={"alt":"Look 03"}' "https://dwfw.trabemflex.com/api/media"

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

08

Resource reference

One block per collection: path, who reads and who writes, fields with type and localisation, and a live example taken from the public API when this page is rendered.

ResourcePathReadWrite (capability)
Works/api/obraspublic for the archive only (mov. 7); the rest by capabilityobra:crear · obra:editar / obra:editar_propia · obra:borrar · movimiento:avanzar (estadoCuratorial)
Authors/api/autorespublicautor:gestionar
Nodes/api/nodospublicnodo:gestionar
Connections/api/conexionespublicconexion:proponer · conexion:confirmar
Meanings/api/significadospublicsignificado:asignar
Curatorial movements/api/movimientospublicmovimiento:avanzar
Editions/api/edicionespublicedicion:gestionar
Brand chapters/api/capitulospubliccapitulo:gestionar
Audiovisual pieces/api/piezaspublicpieza:gestionar
Technical assets/api/mediapublic for the archive only (mov. 7); the rest by capabilityobra:crear (subir) · propio: editar / borrar si no publicado · obra:editar: todo y publicar
Users/api/usuariossigned in (each user sees themself; usuario:gestionar sees everyone)usuario:gestionar

Resource

Works

/api/obras

Layer 1. The work as a cultural document: type, author, origin node, curatorial status (7 movements), formal description and technical assets.

Read
public for the archive only (mov. 7); the rest by capability
Write (capability)
obra:crear · obra:editar / obra:editar_propia · obra:borrar · movimiento:avanzar (estadoCuratorial)
Operations
GET /api/obras GET /api/obras/count GET /api/obras/:id POST /api/obras PATCH /api/obras/:id DELETE /api/obras/:id

Fields

FieldTypeRequiredLocalisedValues / target
titulo Titletextyesyes—
tipo Typeselectyes—modelo_3davatarcoleccion_digitalprenda_fisicaexperiencia_inmersivacodigo_generativo
autor Authorrelationshipyes—relation to autores
nodoOrigen Origin node (provenance)selectyes—academico_investigadorcomunidad_digital_emergentelaboratorio_iainstitucion_archivo
estadoCuratorial Curatorial statusselectyes—movimiento_1movimiento_2movimiento_3movimiento_4movimiento_5movimiento_6movimiento_7
descripcionFormal.silueta Silhouettetextarea—yes—
descripcionFormal.material Materialtextarea—yes—
descripcionFormal.sintaxisVisual Visual syntaxtextarea—yes—
activosTecnicos Technical assets (vertices, textures, video)join———
edicion Editionrelationship——relation to ediciones

Live example (GET, depth=0, public view)

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

{
  "id": 3,
  "titulo": "Metsä · Mycelium v3",
  "tipo": "codigo_generativo",
  "autor": 3,
  "nodoOrigen": "laboratorio_ia",
  "estadoCuratorial": "movimiento_7",
  "descripcionFormal": {
    "silueta": null,
    "material": "Simulated mycelium growth as a pattern generator.",
    "sintaxisVisual": "The form is not drawn: it is grown."
  },
  "activosTecnicos": {
    "docs": [],
    "hasNextPage": false
  },
  "edicion": null,
  "updatedAt": "2026-08-22T18:29:49.582Z",
  "createdAt": "2026-08-21T07:12:39.411Z"
}

Resource

Authors

/api/autores

Layer 2. Designers, collectives, laboratories and institutions. Cultural origin is descriptive, never an ordering criterion.

Read
public
Write (capability)
autor:gestionar
Operations
GET /api/autores GET /api/autores/count GET /api/autores/:id POST /api/autores PATCH /api/autores/:id DELETE /api/autores/:id

Fields

FieldTypeRequiredLocalisedValues / target
nombre Nametextyes——
tipo Typeselectyes—diseñadorinvestigadorartistacolectivolaboratorioinstitucion
origenCultural Cultural origintext—yes—
bio Biotextarea—yes—
trayectoria Backgroundtextarea—yes—
nodos Nodesrelationship[]——relation to nodos
taller.capacidad Workshop capacitytextarea—yes—
taller.materiales Materials workedselect[]——algodonlanalinosedacueropuntodenimfibras_sinteticasmateriales_biobasadostextil_tecnicosin_material_fisico
taller.tecnologias Technologiesselect[]——patronaje_digitalmodelado_3dsimulacion_textildiseno_generativoimpresion_3dcorte_laserbordado_digitalestampacion_digitaltejido_punto_industrialconfeccion_artesanaltenido_natural
taller.usaFibrasRecicladas Recycled fibres?checkbox———
taller.fibrasRecicladasDetalle Recycled fibres · detailtextarea—yes—
taller.notasSostenibilidad Sustainability notestextarea—yes—

Live example (GET, depth=0, public view)

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

{
  "id": 3,
  "nombre": "Lab Metsä",
  "tipo": "laboratorio",
  "origenCultural": "Helsinki, Finland",
  "bio": "Generative materials laboratory.",
  "trayectoria": "Academic spin-off focused on simulated biotextiles.",
  "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"
}

Resource

Nodes

/api/nodos

Layer 3. Vertices of the cartography: philosophical currents, technical developments, communities, institutions, cultural tensions.

Read
public
Write (capability)
nodo:gestionar
Operations
GET /api/nodos GET /api/nodos/count GET /api/nodos/:id POST /api/nodos PATCH /api/nodos/:id DELETE /api/nodos/:id

Fields

FieldTypeRequiredLocalisedValues / target
nombre Nametextyesyes—
tipo Typeselectyes—corriente_filosoficadesarrollo_tecnologico_previocomunidad_subculturainstituciontension_cultural
descripcion Descriptiontextarea—yes—

Live example (GET, depth=0, public view)

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

{
  "id": 4,
  "nombre": "Authorship in the age of generative models",
  "tipo": "tension_cultural",
  "descripcion": null,
  "updatedAt": "2026-08-22T18:29:49.224Z",
  "createdAt": "2026-08-21T07:12:36.120Z"
}

Resource

Connections

/api/conexiones

Layer 3. Typed edges of the graph. Machine-provenance ones are born as proposals; only a human confirms them.

Read
public
Write (capability)
conexion:proponer · conexion:confirmar
Operations
GET /api/conexiones GET /api/conexiones/count GET /api/conexiones/:id POST /api/conexiones PATCH /api/conexiones/:id DELETE /api/conexiones/:id

Fields

FieldTypeRequiredLocalisedValues / target
origen Sourcerelationshipyes—relation to obrasnodos
destino Targetrelationshipyes—relation to obrasnodos
tipoRelacion Relationship typeselectyes—dialoga_conposibilitada_porconversacion_paralela_con
procedencia Provenanceselectyes—maquinahumano
estado Statusselectyes—propuestaconfirmadadescartada
confirmadaPor Confirmed byrelationship——relation to usuarios
confirmadaEn Confirmed atdate———
nota Notetextarea—yes—

Live example (GET, depth=0, public view)

GET https://dwfw.trabemflex.com/api/conexiones?depth=0&locale=en&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"
}

Resource

Meanings

/api/significados

Layer 4. The fixed hermeneutic question and the readings. Always of human provenance (enforced by the server).

Read
public
Write (capability)
significado:asignar
Operations
GET /api/significados GET /api/significados/count GET /api/significados/:id POST /api/significados PATCH /api/significados/:id DELETE /api/significados/:id

Fields

FieldTypeRequiredLocalisedValues / target
obra Workrelationshipyes—relation to obras
tensionSubyacente What tension in contemporary society does this creation respond to?textareayesyes—
lectura Readingsarray———
lectura[].tipo Typeselect——antropologicaeticapoetica
lectura[].texto Texttextarea—yes—
textosTeoricos Associated critical apparatustextarea—yes—
curador Curatorrelationship——relation to usuarios
procedencia Provenance · read-onlytext———

Live example (GET, depth=0, public view)

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

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

Resource

Curatorial movements

/api/movimientos

Curatorial workflow: the seven movements per work, with owner and provenance.

Read
public
Write (capability)
movimiento:avanzar
Operations
GET /api/movimientos GET /api/movimientos/count GET /api/movimientos/:id POST /api/movimientos PATCH /api/movimientos/:id DELETE /api/movimientos/:id

Fields

FieldTypeRequiredLocalisedValues / target
obra Workrelationshipyes—relation to obras
movimiento Movementselectyes—movimiento_1movimiento_2movimiento_3movimiento_4movimiento_5movimiento_6movimiento_7
estado Statusselectyes—pendienteen_cursocompletado
responsable Ownerrelationship——relation to usuarios
procedencia Provenanceselectyes—maquinahumano
notas Notestextarea—yes—

Live example (GET, depth=0, public view)

GET https://dwfw.trabemflex.com/api/movimientos?depth=0&locale=en&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"
}

Resource

Editions

/api/ediciones

Event Stack. Each edition (dates, host city, country in focus).

Read
public
Write (capability)
edicion:gestionar
Operations
GET /api/ediciones GET /api/ediciones/count GET /api/ediciones/:id POST /api/ediciones PATCH /api/ediciones/:id DELETE /api/ediciones/:id

Fields

FieldTypeRequiredLocalisedValues / target
nombre Nametextyesyes—
fechaInicio Start datedateyes——
fechaFin End datedate———
ciudadAnfitriona Host city (Country in Focus)text—yes—
paisEnFoco Country in focustext—yes—

Live example (GET, depth=0, public view)

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

{
  "id": 1,
  "nombre": "DWFW · Edition I",
  "fechaInicio": "2027-03-12T00:00:00.000Z",
  "fechaFin": "2027-03-15T00:00:00.000Z",
  "ciudadAnfitriona": "Madrid",
  "paisEnFoco": "Spain",
  "updatedAt": "2026-08-22T18:29:48.495Z",
  "createdAt": "2026-08-21T07:12:35.921Z",
  "fase": "antes",
  "cuentaAtras": 12328693
}

Resource

Brand chapters

/api/capitulos

Event Stack. Brand chapters of an edition: section, continent, duration, works.

Read
public
Write (capability)
capitulo:gestionar
Operations
GET /api/capitulos GET /api/capitulos/count GET /api/capitulos/:id POST /api/capitulos PATCH /api/capitulos/:id DELETE /api/capitulos/:id

Fields

FieldTypeRequiredLocalisedValues / target
titulo Titletextyesyes—
edicion Editionrelationshipyes—relation to ediciones
autor Authorrelationship——relation to autores
duracionMin Duration (min)number———
seccion Sectionselect——country_in_focusicons_of_designfuture_labconversaciones
continente Continentselect——asiaoceaniaeuropaafricaamerica
obras Worksrelationship[]——relation to obras

Live example (GET, depth=0, public view)

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

{
  "id": 3,
  "titulo": "Conversations · Authorship and machine",
  "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"
}

Resource

Audiovisual pieces

/api/piezas

Event Stack. Audiovisual pieces (premiere, conversation, making of…) with a streaming reference.

Read
public
Write (capability)
pieza:gestionar
Operations
GET /api/piezas GET /api/piezas/count GET /api/piezas/:id POST /api/piezas PATCH /api/piezas/:id DELETE /api/piezas/:id

Fields

FieldTypeRequiredLocalisedValues / target
titulo Titletextyesyes—
tipo Typeselect——premiereconversacionmaking_ofhero_lookreemisionclip
capitulo Chapterrelationship——relation to capitulos
obra Workrelationship——relation to obras
urlStreaming Streaming URLtext———

Live example (GET, depth=0, public view)

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

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

Resource

Technical assets

/api/media

Technical assets: images (≤50 MB), video (≤2 GB), 3D models .glb/.gltf/.usdz/.fbx/.obj (≤500 MB) and PDF (≤20 MB). Private by default (blind curation): visible to the uploader and the curatorial team; published when the work reaches movement 7. Stored in Cloudflare R2; video and 3D upload directly to the bucket. Sheet: description, credits, materials, AI use, licence, software.

Read
public for the archive only (mov. 7); the rest by capability
Write (capability)
obra:crear (subir) · propio: editar / borrar si no publicado · obra:editar: todo y publicar
Operations
GET /api/media GET /api/media/count GET /api/media/:id POST /api/media PATCH /api/media/:id DELETE /api/media/:id

Fields

FieldTypeRequiredLocalisedValues / target
titulo Titletext—yes—
alt Alt texttext—yes—
categoria Category · read-onlyselect——imagenvideomodelo3ddocumento
obra Workrelationship——relation to obras
publico Publiccheckbox———
subidoPor Uploaded by · read-onlyrelationship——relation to usuarios
ficha.descripcion Descriptiontextarea—yes—
ficha.creditos Credits / authorshiptext———
ficha.materiales Materials / techniquetext—yes—
ficha.usoIA AI useselect——ningunoasistidogenerativo
ficha.licencia Licenceselect——todos_los_derechoscc_bycc_by_nccc_by_nc_nd
ficha.software Software / pipelinetext———

Live example (GET, depth=0, public view)

No documents visible without a session.

Resource

Users

/api/usuarios

Accounts with roles and extra capabilities (grants with expiry), optionally linked to an author.

Read
signed in (each user sees themself; usuario:gestionar sees everyone)
Write (capability)
usuario:gestionar
Operations
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

Fields

FieldTypeRequiredLocalisedValues / target
nombre Nametextyes——
roles Rolesselect[]yes—direccionfounding_curatorcuradorinvestigadorcreadorpartnermediaadvisory
concesiones Extra capabilities (temporary or permanent)array———
concesiones[].capacidad Capabilityselectyes—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 Expiresdate———
concesiones[].motivo Reasontext———
scopeEdiciones Limited to these editionsrelationship[]——relation to ediciones
autor Authorrelationship——relation to autores
enableAPIKey checkbox———
apiKey text———
email emailyes——

Live example (GET, depth=0, public view)

No documents visible without a session.

09

Errors

Errors come back as JSON with the matching HTTP code and an errors array. Our hooks use APIError with an explicit code and a message in the request language (Accept-Language).

CodeWhen
200 · 201OK. POST returns 201 with { message, doc }; PATCH and DELETE, 200.
400Validation or malformed request. `errors[0].data.errors` lists field and reason.
401No session (or expired token) on an operation that requires one.
403Session without the required capability; also doctrine rules (confirming a connection without conexion:confirmar).
404The document does not exist — or is not visible to this session (RBAC does not reveal existence).
413 · 415Upload too large or file type not allowed (Media).
500Internal error. Payload returns "Something went wrong"; our hooks use APIError with an explicit code so you never get here.
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, types and integration

The OpenAPI 3.0 specification is generated from the same config: import it into Postman, Insomnia or Stoplight, or generate a client with openapi-generator. The TypeScript types of the schema live in src/payload-types.ts (regenerated with pnpm generate:types).

Download openapi.jsonpayload-types.ts · GitHub

// Minimal JavaScript client (browser or 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', // in the browser the cookie is enough; on a server use the header
})
const { token } = await res.json()

// Read with the JWT in the header
const obras = await fetch(`${API}/obras?locale=ja&depth=1&limit=20`, {
  headers: { Authorization: `JWT ${token}` },
}).then((r) => r.json())

// TypeScript types of the schema (src/payload-types.ts)
import type { Obra, Autor } from './payload-types'

11

Best practices

  • Ask only for what you need: depth=0 or 1 and select[...] shrink responses a lot.
  • Always paginate public listings (limit ≤ 100) and sort by a stable field.
  • Filter polymorphic relationships by value and relationTo together.
  • For role-aware interfaces, query /api/access once per session instead of hard-coding roles.
  • Treat select values and capabilities as stable identifiers; translate on the client with the labels, never rename values.
  • On servers use the Authorization: JWT header and renew with refresh-token; in browsers rely on the cookie.
Provenance in every layer · Algorithmic epoché · The seven movements