Solicitudes en Codenica API

Para trabajar con las solicitudes mediante Codenica API, empieza por crear una clave API en los ajustes de Codenica. Si todavía no has creado ninguna, abre en una pestaña nueva Codenica API - introducción. Allí se explican las reglas comunes para crear la clave, guardar el secreto y autenticar las solicitudes.

El nombre técnico de la colección en la API es requesteditems y el tipo de un objeto individual es requesteditem. Una solicitud sirve para registrar una necesidad de comprar, entregar, preparar o realizar un artículo concreto. Además de la descripción, puede incluir una fecha límite, cantidad, precio, coste, valor, impuesto, presupuesto, prioridad y estado.

Los ejemplos utilizan el prefijo PUBLIC-API-REQUESTEDITEM-20260906053922. En tu integración, sustitúyelo por tu propio identificador y adapta las direcciones, los UUID y los valores de los campos a los datos de tu base de datos.


Solicitudes - dirección de la API y elección de la instalación

Todas las rutas relacionadas con las solicitudes empiezan por:

{BASE_URL}/api/v1/requesteditems

BASE_URL es la dirección del servidor Codenica sin el sufijo /api/v1. En la versión Cloud, utiliza el dominio público asignado a la empresa correspondiente:

export BASE_URL="https://tu-empresa.codenica.com"

En una instalación On-Premise predeterminada, la dirección que Codenica Discovery registra localmente es:

export BASE_URL="http://codenica.local:5150"

Si el administrador ha publicado la instalación con un dominio corporativo, mediante un proxy inverso, con HTTPS o en otro puerto, utiliza la dirección exacta indicada para esa instalación:

export BASE_URL="https://api.tu-empresa.example"

No utilices localhost si la aplicación de integración se ejecuta en un ordenador distinto del de la API. La base de datos correcta se selecciona según la dirección a la que se conecta la integración. No envíes tenantId en el body, en la cadena de consulta ni en un encabezado adicional.


Solicitudes - scopes de la clave API

La clave API utilizada para trabajar con solicitudes solo debe incluir los scopes que necesite esa integración concreta. El conjunto completo de scopes de este módulo es:

requesteditems:read
requesteditems:write
requesteditems:delete
requesteditems:schema
requesteditems:stats
requesteditems:relationships:read
requesteditems:relationships:write
requesteditems:users:read
requesteditems:files:read
requesteditems:files:write
requesteditems:technical:read
requesteditems:technical:write
requesteditems:pin:write

Para leer listas y registros, selecciona requesteditems:read; para consultar el catálogo de campos, añade también requesteditems:schema. Crear y editar requiere requesteditems:write, mientras que eliminar requiere requesteditems:delete. Añade scopes de relaciones, archivos, estadísticas, solicitante y fijación solo cuando la integración vaya a utilizar esas operaciones.

Si la integración busca objetivos de relaciones, la clave también necesita los scopes de lectura correspondientes, por ejemplo assets:read, clients:read, vendors:read, documents:read, tickets:read, changes:read, problems:read, releases:read, notes:read, approvals:read o worktasks:read. El scope de la clave no sustituye los permisos del usuario.


Solicitudes - autenticación

Autentica cada solicitud a Codenica API con dos encabezados:

export CLIENT_ID="cna_tu_cliente_id"
export CLIENT_SECRET="cns_tu_secreto_cliente"

curl --request GET --url "$BASE_URL/api/v1/requesteditems?page=1&pageSize=25" \
  --header "Accept: application/json" \
  --header "X-Codenica-Client-Id: $CLIENT_ID" \
  --header "X-Codenica-Client-Secret: $CLIENT_SECRET"

Una integración externa no necesita el JWT del administrador ni las cookies del panel de Codenica. Guarda el secreto en un almacén de secretos del lado del servidor. No lo incluyas en código que se entregue al navegador, repositorios, URL, historial de comandos ni registros. Fuera de las pruebas locales, utiliza HTTPS.

Guarda meta.requestId de las respuestas. Este identificador ayuda a localizar una solicitud concreta en los registros, pero no sustituye el UUID de la solicitud ni es un secreto.


Solicitudes - comprobación del contexto de conexión

Antes de realizar la primera escritura, recupera el contexto. Así comprobarás que la dirección apunta a la base de datos correcta y que la clave dispone de los scopes y límites necesarios:

curl --request GET --url "$BASE_URL/api/v1/context" \
  --header "Accept: application/json" \
  --header "X-Codenica-Client-Id: $CLIENT_ID" \
  --header "X-Codenica-Client-Secret: $CLIENT_SECRET"

En la respuesta, verifica data.apiVersion, data.contractVersion, los datos de data.tenant, que data.caller.authentication sea api_key, que requesteditems aparezca en data.capabilities.resources, además de los scopes de la clave y los límites de solicitudes.

Si el contexto muestra otra empresa o no incluye el scope necesario, corrige la dirección o crea una clave con los permisos adecuados. No intentes dirigir la solicitud a otra base de datos enviando un tenantId ajeno.


Solicitudes - esquema y objetivos de las relaciones

El esquema es la fuente de información sobre los campos actuales, sus tipos, si se pueden escribir y los objetivos de relación permitidos:

curl --request GET --url "$BASE_URL/api/v1/requesteditems/schema" \
  --header "Accept: application/json" \
  --header "X-Codenica-Client-Id: $CLIENT_ID" \
  --header "X-Codenica-Client-Secret: $CLIENT_SECRET"

La respuesta incluye, entre otros datos, data.itemType, data.fields y data.relationshipTargets. En este módulo, itemType tiene el valor requesteditem. Para cada campo, comprueba readable, writable, required, technical, unique y maxLength.

Fragmento de un esquema:

{
  "data": {
    "itemType": "requesteditem",
    "fields": [
      { "name": "title", "type": "string", "writable": true },
      { "name": "dateDue", "type": "dateTime", "writable": true },
      { "name": "quantity", "type": "integer", "writable": true },
      { "name": "value", "type": "number", "writable": true },
      { "name": "pin", "type": "integer", "writable": false }
    ],
    "relationshipTargets": [
      { "targetDataSet": "assets", "targetItemType": "asset" },
      { "targetDataSet": "documents", "targetItemType": "document" },
      { "targetDataSet": "worktasks", "targetItemType": "worktask" }
    ]
  }
}

No construyas el mapeo únicamente a partir de este ejemplo. Antes de iniciar la integración, recupera el esquema de la base de datos correcta y utiliza solo los campos y objetivos que devuelva.


Solicitudes - campos de negocio y del sistema

Los campos más importantes de una solicitud son:

Campo
Tipo
Límite o uso
customId
string
500 caracteres, identificador de la integración
dateDue, dateEnd
dateTime
fecha límite y fecha final
location, department
string
300 caracteres cada uno
tag, link
string
2000 caracteres cada uno
title
string
1000 caracteres
status, priority, category, budget, currency
string
valores que describen el proceso y la liquidación
tax, quantity
integer
número entero
cost, price, value
number
coste, precio unitario y valor
description
string
10000 caracteres

id, itemType, creator, updater, dateCreated, dateUpdated, importId, importSource y dateImported los establece el sistema o están destinados a la lectura técnica. No los envíes en attributes.

appUserRequesterId
clientRequesterId
catalogId
catalogItemId
creator
updater
dateCreated
dateUpdated
importId
importSource
dateImported

Solicitudes - endpoints disponibles

Las rutas principales del módulo requesteditems son:

GET    /api/v1/requesteditems
POST   /api/v1/requesteditems
GET    /api/v1/requesteditems/{REQUESTED_ITEM_ID}
PATCH  /api/v1/requesteditems/{REQUESTED_ITEM_ID}
DELETE /api/v1/requesteditems/{REQUESTED_ITEM_ID}
GET    /api/v1/requesteditems/schema
GET    /api/v1/requesteditems/stats
GET    /api/v1/requesteditems/values
POST   /api/v1/requesteditems:batch
GET    /api/v1/requesteditems/{REQUESTED_ITEM_ID}/relationships
POST   /api/v1/requesteditems/{REQUESTED_ITEM_ID}/relationships
POST   /api/v1/requesteditems/{REQUESTED_ITEM_ID}/relationships:batch
DELETE /api/v1/requesteditems/{REQUESTED_ITEM_ID}/relationships/{DATASET}/{TARGET_ID}
GET    /api/v1/requesteditems/{REQUESTED_ITEM_ID}/user-relationships
GET    /api/v1/requesteditems/{REQUESTED_ITEM_ID}/files
POST   /api/v1/requesteditems/{REQUESTED_ITEM_ID}/files
POST   /api/v1/requesteditems/{REQUESTED_ITEM_ID}/files/{FILE_ID}
DELETE /api/v1/requesteditems/{REQUESTED_ITEM_ID}/files/{FILE_ID}
GET    /api/v1/requesteditems/{REQUESTED_ITEM_ID}/files/{FILE_ID}/content
POST   /api/v1/requesteditems/{REQUESTED_ITEM_ID}/pin

Las lecturas requieren scopes read; cada mutación requiere además los scopes correspondientes según la tabla de permisos. Toda solicitud que modifique datos también necesita Idempotency-Key.


Solicitudes - listas y paginación

Obtén la lista de solicitudes por páginas. Ejemplo:

curl --request GET --url "$BASE_URL/api/v1/requesteditems?itemType=requesteditem&page=1&pageSize=25" \
  --header "Accept: application/json" \
  --header "X-Codenica-Client-Id: $CLIENT_ID" \
  --header "X-Codenica-Client-Secret: $CLIENT_SECRET"

La respuesta contiene la colección data.items y la información de la página:

{
  "data": {
    "items": [
      {
        "id": "requested-item-uuid",
        "itemType": "requesteditem",
        "attributes": {
          "customId": "ERP-REQ-2026-0042",
          "title": "Tres monitores para el nuevo puesto",
          "status": "Open",
          "quantity": 3,
          "value": 3136.5
        },
        "meta": {
          "etag": "\"etag-value\""
        }
      }
    ],
    "page": 1,
    "pageSize": 25,
    "totalItems": 1,
    "totalPages": 1,
    "hasNextPage": false
  },
  "meta": {
    "requestId": "request-id"
  }
}

Avanza a la página siguiente según hasNextPage. No des por hecho que la última página siempre contiene menos elementos que el pageSize elegido. Comprueba el tamaño máximo en data.capabilities.limits o en el contrato vigente.


Solicitudes - búsqueda y filtros

Usa el parámetro search para buscar texto. Para sincronizar datos, es preferible utilizar un customId estable, un UUID o un filtro explícito:

curl --silent --show-error -G \
  --data-urlencode "search=monitores" \
  --data-urlencode "page=1" \
  --data-urlencode "pageSize=20" \
  --header "X-Codenica-Client-Id: $CLIENT_ID" \
  --header "X-Codenica-Client-Secret: $CLIENT_SECRET" \
  "$BASE_URL/api/v1/requesteditems"

Los filtros sencillos pueden utilizar nombres de campos:

curl --silent --show-error -G \
  --data-urlencode "status=Open" \
  --data-urlencode "priority=High" \
  --data-urlencode "customId=ERP-REQ-2026-0042" \
  --header "X-Codenica-Client-Id: $CLIENT_ID" \
  --header "X-Codenica-Client-Secret: $CLIENT_SECRET" \
  "$BASE_URL/api/v1/requesteditems"

Un filtro estructural tiene la forma field:operator:value:

status:eq:Open
priority:ne:Low
quantity:gte:2
price:lt:1000
title:contains:laptop
customId:startswith:ERP-
link:notempty:

Los operadores admitidos son eq, ne, gt, gte, lt, lte, contains, startswith, endswith y notempty. Codifica el valor del filtro para la URL, sobre todo si contiene espacios, dos puntos o caracteres especiales.


Solicitudes - selección de campos e inclusión de datos

Si solo necesitas una parte de la respuesta, utiliza fields. Incluye archivos, relaciones y usuarios mediante include:

curl --silent --show-error -G \
  --data-urlencode "fields=id,itemType,customId,title,status,priority,dateDue,quantity,value" \
  --data-urlencode "include=files,relationships,users" \
  --data-urlencode "ids=7512ef99-0010-4962-9453-99383a377e4b" \
  --header "X-Codenica-Client-Id: $CLIENT_ID" \
  --header "X-Codenica-Client-Secret: $CLIENT_SECRET" \
  "$BASE_URL/api/v1/requesteditems"

Los valores disponibles de include son files, relationships y users. Cada uno requiere el scope correspondiente. fields=* no evita los permisos de los campos técnicos ni devuelve los campos del sistema reservados para la envoltura de respuesta.

También puedes filtrar con createdAfter, createdBefore, updatedAfter, updatedBefore, sort y direction. Comprueba los nombres de los campos de fields, sort y filter en el esquema vigente.


Solicitudes - estadísticas y valores de los campos

El endpoint stats ayuda a comprobar la distribución de los datos, mientras que values devuelve valores útiles para construir filtros:

curl --silent --show-error -G \
  --data-urlencode "field=status" \
  --data-urlencode "limit=20" \
  --header "X-Codenica-Client-Id: $CLIENT_ID" \
  --header "X-Codenica-Client-Secret: $CLIENT_SECRET" \
  "$BASE_URL/api/v1/requesteditems/stats"

curl --silent --show-error -G \
  --data-urlencode "field=category" \
  --data-urlencode "search=hard" \
  --data-urlencode "limit=20" \
  --header "X-Codenica-Client-Id: $CLIENT_ID" \
  --header "X-Codenica-Client-Secret: $CLIENT_SECRET" \
  "$BASE_URL/api/v1/requesteditems/values"

Ejemplo de respuesta de values:

{
  "data": {
    "field": "category",
    "values": ["Hardware", "Office"]
  },
  "meta": {
    "requestId": "request-id"
  }
}

Las estadísticas y los valores son operaciones de lectura y no modifican las solicitudes. No los consultes en un bucle continuo sin necesidad - el esquema y los valores de los campos se pueden guardar en caché durante un periodo adecuado para la integración.


Solicitudes - creación mínima

Crea un registro mediante POST /api/v1/requesteditems. Incluye itemType en el body y los campos dentro de attributes:

curl --request POST --url "$BASE_URL/api/v1/requesteditems" \
  --header "Content-Type: application/json" \
  --header "X-Codenica-Client-Id: $CLIENT_ID" \
  --header "X-Codenica-Client-Secret: $CLIENT_SECRET" \
  --header "Idempotency-Key: requesteditem-create-unique-001" \
  --data-raw '{
    "itemType": "requesteditem",
    "attributes": {
      "customId": "ERP-REQ-2026-0042",
      "title": "Compra de material de oficina",
      "category": "Office",
      "quantity": 10,
      "currency": "PLN",
      "status": "Open",
      "description": "Elemento creado por la integración."
    }
  }'

El valor de itemType debe ser requesteditem. Ajusta los nombres y tipos de los campos a la respuesta del esquema. Envía las fechas en formato ISO 8601 y los números como números JSON, no como cadenas con formato.

Una respuesta correcta tiene el estado 201 Created. Guarda data.id, el ETag del encabezado HTTP y el ETag de data.meta.etag.


Solicitudes - creación completa con campos económicos

El ejemplo siguiente corresponde a un registro del flujo completo de demostración. Muestra una fecha límite, una ubicación, un estado, una prioridad y datos económicos:

curl --request POST --url "$BASE_URL/api/v1/requesteditems" \
  --header "Content-Type: application/json" \
  --header "X-Codenica-Client-Id: $CLIENT_ID" \
  --header "X-Codenica-Client-Secret: $CLIENT_SECRET" \
  --header "Idempotency-Key: requesteditem-create-20260906053922-source" \
  --data-raw '{
    "itemType": "requesteditem",
    "attributes": {
      "customId": "PUBLIC-API-REQUESTEDITEM-20260906053922-SOURCE",
      "dateDue": "2026-12-31T17:00:00Z",
      "dateEnd": "2027-01-15T17:00:00Z",
      "location": "Warsaw",
      "department": "IT",
      "tag": "public-api,requesteditems,demo",
      "link": "https://codenica.com",
      "title": "Flujo completo de la API de solicitudes",
      "status": "Open",
      "priority": "High",
      "category": "Hardware",
      "budget": "IT-2026",
      "currency": "PLN",
      "tax": 23,
      "quantity": 3,
      "cost": 300,
      "price": 100,
      "value": 369,
      "description": "Solicitud de demostración creada mediante la API pública."
    },
    "customValues": [
      {
        "name": "description",
        "valuePattern": "[requested-item-demo] Public API"
      }
    ]
  }'

customValues es opcional. Elimina esta propiedad si la integración no utiliza reglas de valores adicionales. No envíes campos técnicos solo porque hayan aparecido en una respuesta.


Solicitudes - repetición segura de la creación

Si se produce un timeout después de enviar la solicitud y no sabes si el registro se guardó, repite exactamente la misma operación con el mismo Idempotency-Key y un body idéntico:

curl --request POST --url "$BASE_URL/api/v1/requesteditems" \
  --header "Content-Type: application/json" \
  --header "X-Codenica-Client-Id: $CLIENT_ID" \
  --header "X-Codenica-Client-Secret: $CLIENT_SECRET" \
  --header "Idempotency-Key: requesteditem-create-20260906053922-source" \
  --data-binary @requesteditem-create.json

El archivo requesteditem-create.json debe contener exactamente el body de la primera solicitud. La repetición devolverá el mismo registro en lugar de crear un duplicado. Una nueva intención de negocio, un body modificado o una ruta distinta requieren una clave nueva. Repetir la solicitud con otro body devuelve 422 idempotency_key_reused.

Guarda la clave de idempotencia en la integración junto con el estado de la operación. No utilices el secreto de cliente para este fin.


Solicitudes - lectura de un registro y ETag

Después de crear o localizar una solicitud, recupérala mediante su UUID:

export REQUESTED_ITEM_ID="7512ef99-0010-4962-9453-99383a377e4b"

curl --request GET --url "$BASE_URL/api/v1/requesteditems/$REQUESTED_ITEM_ID?include=files,relationships,users" \
  --header "Accept: application/json" \
  --header "X-Codenica-Client-Id: $CLIENT_ID" \
  --header "X-Codenica-Client-Secret: $CLIENT_SECRET"

Encontrarás el ETag actual en el encabezado HTTP y normalmente también en data.meta.etag y en la envoltura principal meta.etag:

ETag: "etag-value"

Un ETag es una versión opaca de un registro concreto. No quites las comillas que devuelve el encabezado ni calcules este valor por tu cuenta. Antes de modificar el registro, una relación o un archivo, recupera un ETag nuevo si otra persona o integración ha podido cambiar el registro.


Solicitudes - actualización parcial con If-Match

PATCH solo cambia los campos enviados en el body. Requiere el ETag actual y una clave de idempotencia nueva:

export REQUESTED_ITEM_ETAG='"etag-value-from-get"'

curl --request PATCH --url "$BASE_URL/api/v1/requesteditems/$REQUESTED_ITEM_ID" \
  --header "Accept: application/json" \
  --header "Content-Type: application/json" \
  --header "X-Codenica-Client-Id: $CLIENT_ID" \
  --header "X-Codenica-Client-Secret: $CLIENT_SECRET" \
  --header "If-Match: $REQUESTED_ITEM_ETAG" \
  --header "Idempotency-Key: requesteditem-update-20260906-0001" \
  --data-raw '{
    "attributes": {
      "dateDue": "2027-01-31T17:00:00Z",
      "title": "Requested Item API - flujo completo - actualizado",
      "status": "In progress",
      "priority": "Normal",
      "quantity": 4,
      "price": 125,
      "value": 615
    }
  }'

No tienes que enviar el objeto completo. Los campos omitidos en el body conservan su valor. Después de recibir 200 OK, sustituye el ETag guardado por el valor devuelto por la API. Cada mutación posterior debe utilizar la versión más reciente.


Solicitudes - ETag obsoleto y ausencia de If-Match

La ausencia del encabezado If-Match se rechaza para evitar que una integración sobrescriba cambios realizados por otra persona:

curl --request PATCH --url "$BASE_URL/api/v1/requesteditems/$REQUESTED_ITEM_ID" \
  --header "Content-Type: application/json" \
  --header "X-Codenica-Client-Id: $CLIENT_ID" \
  --header "X-Codenica-Client-Secret: $CLIENT_SECRET" \
  --header "Idempotency-Key: requesteditem-update-without-etag-0001" \
  --data-raw '{"attributes":{"status":"Approved"}}'

El resultado esperado es 428 Precondition Required con el código if_match_required. Si envías un ETag antiguo, recibirás 412 Precondition Failed con el código if_match_failed:

HTTP 412 Precondition Failed
code: if_match_failed

Después de un error 412, recupera de nuevo el registro, compara tu cambio con los datos actuales y solo entonces envía otro PATCH. No ejecutes un bucle ciego que sobrescriba los cambios del usuario.


Solicitudes - fijar y quitar la fijación

pin es un campo de solo lectura en attributes. Establécelo mediante un endpoint independiente y con el ETag actual:

curl --request POST --url "$BASE_URL/api/v1/requesteditems/$REQUESTED_ITEM_ID/pin" \
  --header "Content-Type: application/json" \
  --header "X-Codenica-Client-Id: $CLIENT_ID" \
  --header "X-Codenica-Client-Secret: $CLIENT_SECRET" \
  --header "If-Match: $REQUESTED_ITEM_ETAG" \
  --header "Idempotency-Key: requesteditem-pin-0001" \
  --data '{"pin":3}'

Se permiten valores del 0 al 3. Para quitar la fijación, utiliza null:

curl --request POST --url "$BASE_URL/api/v1/requesteditems/$REQUESTED_ITEM_ID/pin" \
  --header "Content-Type: application/json" \
  --header "X-Codenica-Client-Id: $CLIENT_ID" \
  --header "X-Codenica-Client-Secret: $CLIENT_SECRET" \
  --header "If-Match: $REQUESTED_ITEM_ETAG" \
  --header "Idempotency-Key: requesteditem-unpin-0001" \
  --data '{"pin":null}'

Guarda el nuevo ETag después de cada operación. No intentes cambiar pin mediante un PATCH normal.


Solicitudes - relación del solicitante

Cada solicitud puede tener una relación de sistema requester. Léela mediante:

curl --request GET --url "$BASE_URL/api/v1/requesteditems/$REQUESTED_ITEM_ID/user-relationships" \
  --header "Accept: application/json" \
  --header "X-Codenica-Client-Id: $CLIENT_ID" \
  --header "X-Codenica-Client-Secret: $CLIENT_SECRET"

El solicitante lo establece un flujo existente del sistema y puede apuntar a appUserRequesterId o clientRequesterId. La API pública permite leerla, pero no ofrece un POST ni un DELETE independiente para cambiar esta relación. No intentes establecer el solicitante mediante un campo no documentado de attributes. La lectura requiere requesteditems:users:read y los permisos adecuados sobre los datos.


Solicitudes - relaciones de objetos permitidas

El catálogo actual de objetivos de relación de las solicitudes incluye:

Colección
Ejemplo de itemType
assets
asset
clients, vendors
client, vendor
documents, tickets
document, ticket
changes, problems, releases
change, problem, release
notes, approvals
note, approval
worktasks
worktask

No existe ninguna relación con la colección requesteditems en sí ni con confirmations. El objetivo debe ser visible para el usuario asignado a la clave y coincidir con la lista relationshipTargets devuelta por el esquema.


Solicitudes - añadir, leer y eliminar relaciones

Una relación entre objetos no almacena relationshipType. En el payload, envía targetId, targetDataSet y un targetItemType compatible:

{
  "targetId": "5ddc5b5a-5bdc-46f9-ab7e-5ad8f6775b3a",
  "targetDataSet": "assets",
  "targetItemType": "asset"
}

Para añadir una relación necesitas el ETag actual del origen, el scope requesteditems:relationships:write y una clave de idempotencia independiente:

curl --request POST --url "$BASE_URL/api/v1/requesteditems/$REQUESTED_ITEM_ID/relationships" \
  --header "Content-Type: application/json" \
  --header "X-Codenica-Client-Id: $CLIENT_ID" \
  --header "X-Codenica-Client-Secret: $CLIENT_SECRET" \
  --header "If-Match: $REQUESTED_ITEM_ETAG" \
  --header "Idempotency-Key: requesteditem-relationship-asset-0001" \
  --data '{
    "targetId": "5ddc5b5a-5bdc-46f9-ab7e-5ad8f6775b3a",
    "targetDataSet": "assets",
    "targetItemType": "asset"
  }'

Lee la lista de relaciones mediante:

curl --request GET --url "$BASE_URL/api/v1/requesteditems/$REQUESTED_ITEM_ID/relationships?page=1&pageSize=50" \
  --header "Accept: application/json" \
  --header "X-Codenica-Client-Id: $CLIENT_ID" \
  --header "X-Codenica-Client-Secret: $CLIENT_SECRET"

Elimina una relación concreta utilizando la colección y el UUID del objetivo:

curl --request DELETE --url "$BASE_URL/api/v1/requesteditems/$REQUESTED_ITEM_ID/relationships/assets/5ddc5b5a-5bdc-46f9-ab7e-5ad8f6775b3a" \
  --header "X-Codenica-Client-Id: $CLIENT_ID" \
  --header "X-Codenica-Client-Secret: $CLIENT_SECRET" \
  --header "If-Match: $REQUESTED_ITEM_ETAG" \
  --header "Idempotency-Key: requesteditem-relationship-asset-delete-0001"

Después de añadir o eliminar una relación, vuelve a leer los datos del origen y guarda el nuevo ETag. Si el esquema no devuelve un objetivo, no lo utilices en la integración.


Solicitudes - cambios de relaciones por lotes

Para realizar varios cambios en una sola solicitud, utiliza relationships:batch. En las relaciones de solicitudes, sigue omitiendo relationshipType:

curl --request POST --url "$BASE_URL/api/v1/requesteditems/$REQUESTED_ITEM_ID/relationships:batch" \
  --header "Content-Type: application/json" \
  --header "X-Codenica-Client-Id: $CLIENT_ID" \
  --header "X-Codenica-Client-Secret: $CLIENT_SECRET" \
  --header "If-Match: $REQUESTED_ITEM_ETAG" \
  --header "Idempotency-Key: requesteditem-relationship-batch-0001" \
  --data-raw '{
    "add": [
      {
        "targetId": "5ddc5b5a-5bdc-46f9-ab7e-5ad8f6775b3a",
        "targetDataSet": "assets",
        "targetItemType": "asset"
      },
      {
        "targetId": "88b93fb8-8848-4669-9d87-ed4795e13bcc",
        "targetDataSet": "documents",
        "targetItemType": "document"
      }
    ],
    "remove": [
      {
        "targetId": "8f42dc16-167b-4e4a-983f-862ae85f3c7a",
        "targetDataSet": "worktasks",
        "targetItemType": "worktask"
      }
    ]
  }'

La respuesta contiene contadores:

{
  "data": {
    "added": 2,
    "removed": 1,
    "skipped": 0
  },
  "meta": {
    "requestId": "request-id"
  }
}

No trates automáticamente skipped como un éxito de negocio. Después del batch, lee la colección de relaciones y comprueba el resultado de cada cambio.


Solicitudes - listar y subir archivos

Los archivos se gestionan por separado de los campos de la solicitud. Lee primero la lista actual:

curl --request GET --url "$BASE_URL/api/v1/requesteditems/$REQUESTED_ITEM_ID/files?page=1&pageSize=50" \
  --header "Accept: application/json" \
  --header "X-Codenica-Client-Id: $CLIENT_ID" \
  --header "X-Codenica-Client-Secret: $CLIENT_SECRET"

Envía el archivo como multipart/form-data. La función del archivo se transmite en la cadena de consulta:

curl --request POST --url "$BASE_URL/api/v1/requesteditems/$REQUESTED_ITEM_ID/files?relationshipType=request-form" \
  --header "X-Codenica-Client-Id: $CLIENT_ID" \
  --header "X-Codenica-Client-Secret: $CLIENT_SECRET" \
  --header "If-Match: $REQUESTED_ITEM_ETAG" \
  --header "Idempotency-Key: requesteditem-file-upload-0001" \
  --form "[email protected];type=application/pdf"

Un elemento de archivo contiene, entre otros valores, id, fileName, contentType, size, relationshipType, isMain y downloadUrl. Comprueba el tamaño antes de enviarlo y establece conscientemente el tipo MIME.


Solicitudes - descargar, adjuntar y eliminar archivos

Descarga el contenido del archivo mediante el endpoint content y guárdalo en modo binario:

curl --request GET --url "$BASE_URL/api/v1/requesteditems/$REQUESTED_ITEM_ID/files/$FILE_ID/content" \
  --header "X-Codenica-Client-Id: $CLIENT_ID" \
  --header "X-Codenica-Client-Secret: $CLIENT_SECRET" \
  --output solicitud-descargada.pdf

Si el archivo ya existe en el sistema, puedes adjuntarlo sin volver a subir una copia:

curl --request POST --url "$BASE_URL/api/v1/requesteditems/$REQUESTED_ITEM_ID/files/$FILE_ID?relationshipType=quotation" \
  --header "X-Codenica-Client-Id: $CLIENT_ID" \
  --header "X-Codenica-Client-Secret: $CLIENT_SECRET" \
  --header "If-Match: $REQUESTED_ITEM_ETAG" \
  --header "Idempotency-Key: requesteditem-file-attach-0001"

Eliminar un archivo de una solicitud:

curl --request DELETE --url "$BASE_URL/api/v1/requesteditems/$REQUESTED_ITEM_ID/files/$FILE_ID" \
  --header "X-Codenica-Client-Id: $CLIENT_ID" \
  --header "X-Codenica-Client-Secret: $CLIENT_SECRET" \
  --header "If-Match: $REQUESTED_ITEM_ETAG" \
  --header "Idempotency-Key: requesteditem-file-delete-0001"

Attach crea una relación con un archivo existente, pero no sube una copia nueva. El modelo actual de solicitudes no tiene un endpoint para el archivo principal: cada elemento tiene isMain=false. No utilices /files/{FILE_ID}/main ni makeMain para este objeto.


Solicitudes - operaciones por lotes

El endpoint /api/v1/requesteditems:batch permite crear, editar y eliminar varios registros. No sustituye las operaciones de archivos, la fijación ni el batch de relaciones:

curl --request POST --url "$BASE_URL/api/v1/requesteditems:batch" \
  --header "Content-Type: application/json" \
  --header "X-Codenica-Client-Id: $CLIENT_ID" \
  --header "X-Codenica-Client-Secret: $CLIENT_SECRET" \
  --header "Idempotency-Key: requesteditems-batch-20260906-0001" \
  --data-raw '{
    "items": [
      {
        "operation": "create",
        "create": {
          "itemType": "requesteditem",
          "attributes": {
            "customId": "ERP-REQ-BATCH-001",
            "title": "Ratón y teclado para el equipo",
            "category": "Hardware",
            "quantity": 5,
            "price": 150,
            "currency": "PLN",
            "status": "Open"
          }
        }
      },
      {
        "operation": "update",
        "id": "7dc877ec-4766-42cc-a34a-900e55ab3f46",
        "ifMatch": "\"etag-from-get\"",
        "update": {
          "attributes": {
            "status": "Approved",
            "quantity": 6
          }
        }
      },
      {
        "operation": "delete",
        "id": "476b8c2e-6da9-409d-bb35-98039619ccfe",
        "ifMatch": "\"etag-after-update\""
      }
    ]
  }'

Cada elemento update y delete tiene su propio ETag. Un batch no es una transacción de todo o nada. Recorre los items de la respuesta y guarda el estado, el UUID y el error de cada elemento. Un resultado parcial puede devolver 207 Multi-Status.


Solicitudes - eliminar un registro

Antes de eliminarlo, recupera de nuevo el registro, comprueba su UUID y ETag actual y asegúrate de que el proceso de negocio permite eliminarlo:

curl --request DELETE --url "$BASE_URL/api/v1/requesteditems/$REQUESTED_ITEM_ID" \
  --header "X-Codenica-Client-Id: $CLIENT_ID" \
  --header "X-Codenica-Client-Secret: $CLIENT_SECRET" \
  --header "If-Match: $REQUESTED_ITEM_ETAG" \
  --header "Idempotency-Key: requesteditem-delete-20260906-0001"

Una respuesta correcta devuelve 200 OK y data=true. Después de eliminarlo, volver a leer el UUID debería devolver 404 Not Found con el código requestedItem_not_found. También puedes realizar una comprobación final con una lista filtrada por customId y esperar totalItems=0.

Eliminar un registro no conserva el historial del proceso. Si los datos tienen valor para una auditoría, guarda la información necesaria en el sistema de origen antes de ejecutar DELETE.


Solicitudes - errores, límites y orden de trabajo seguro

Los errores utilizan el formato Problem Details. Guarda status, code y requestId en los registros, pero nunca guardes el secreto de cliente ni los encabezados completos:

HTTP
Código
Respuesta
401
authentication_failed
Comprueba el host y los dos encabezados.
403
scope_or_access_denied
Comprueba el scope y los permisos del usuario.
404
requestedItem_not_found
El registro no existe o no es visible.
412
if_match_failed
Obtén un ETag nuevo y resuelve el conflicto.
428
if_match_required o idempotency_key_required
Añade el encabezado requerido.
422
validation_failed
Corrige el body según el esquema.
429
rate_limit_exceeded
Aplica una espera creciente y, si se proporciona, utiliza Retry-After.

Lee X-RateLimit-Limit y X-RateLimit-Remaining. Limita la concurrencia, guarda en caché el esquema y los valores y aplica backoff tras un 429. Una secuencia segura es: context, schema, lista o consulta por UUID, creación con Idempotency-Key, guardar el UUID y el ETag, archivos o relaciones, modificación con If-Match, lectura de verificación y eliminación solo al final. El mismo patrón se puede utilizar en n8n si las credenciales se guardan como credential y los UUID, ETag y claves de idempotencia se pasan entre nodos.