Aprobaciones en Codenica API

Para empezar a trabajar con aprobaciones mediante Codenica API, crea una clave API en los ajustes de Codenica. Si todavía no tienes una, abre Codenica API - introducción en una pestaña nueva. Allí se explican la creación común de claves, el almacenamiento del secreto y las reglas de autenticación.

El nombre técnico del objeto es approval y el nombre de su colección en la API es approvals. Una aprobación contiene una solicitud, la persona responsable de la decisión, datos descriptivos y enlaces a objetos del proceso. También puede tener archivos y un nivel de fijación.

La diferencia importante frente a una actualización normal es que el resultado de la decisión no se escribe directamente en status. La aprobación se aprueba o se rechaza mediante un endpoint de decisión específico. Así la API puede comprobar que actúa el aprobador correcto y que el registro no ha cambiado desde su lectura.

Los ejemplos utilizan el identificador PUBLIC-API-APPROVAL-20260906060644. Sustitúyelo por un identificador de tu aplicación de integración y adapta los UUID y los valores de los campos a tu base de datos.


Aprobaciones - dirección de la API y tipo de instalación

Todas las rutas de aprobaciones comienzan por:

{BASE_URL}/api/v1/approvals

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

export BASE_URL="https://{actual-company-domain}"

En la instalación On-Premise predeterminada, la dirección registrada localmente por Codenica Discovery es:

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

Si un administrador ha publicado la instalación mediante un dominio de empresa, un proxy inverso, HTTPS u otro puerto, utiliza la dirección exacta proporcionada para esa instalación:

export BASE_URL="https://{actual-installation-address}"

No utilices localhost cuando la aplicación de integración se ejecute en otro ordenador distinto de la API. La base de datos de destino se selecciona a partir del host de la solicitud. No envíes tenantId en el cuerpo, la cadena de consulta ni en una cabecera adicional.


Aprobaciones - scopes de la clave API

La clave utilizada para las aprobaciones debe contener únicamente los scopes que necesita esa integración. El conjunto completo de scopes del módulo es:

approvals:read
approvals:write
approvals:delete
approvals:schema
approvals:stats
approvals:relationships:read
approvals:relationships:write
approvals:users:read
approvals:files:read
approvals:files:write
approvals:technical:read
approvals:technical:write
approvals:pin:write
approvals:decision:write
users:read

Para listas y lecturas de registros, selecciona approvals:read. La creación y edición requieren approvals:write, y la eliminación requiere approvals:delete. Añade scopes de relaciones, archivos, estadísticas, datos técnicos, fijación y decisiones solo si la integración utilizará esas operaciones.

Si la integración busca destinos para relaciones, también necesita los scopes de lectura correspondientes a las colecciones de las que se eligen los destinos, por ejemplo notes:read, worktasks:read, requesteditems:read, tickets:read, changes:read, problems:read o releases:read. El scope de una clave no sustituye a los permisos del usuario.


Aprobaciones - autenticación

Autentica cada solicitud a Codenica API con dos cabeceras:

export CLIENT_ID="cna_your_client_id"
export CLIENT_SECRET="cns_your_client_secret"

curl --request GET --url "$BASE_URL/api/v1/approvals?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 un JWT de administrador ni cookies del panel de Codenica. Guarda el secreto en un almacén de secretos del servidor. No lo incluyas en código enviado al navegador, un repositorio, una URL, el historial del shell ni los registros. Utiliza HTTPS fuera de las pruebas locales.

Guarda meta.requestId de las respuestas. Sirve para localizar una solicitud concreta en los registros, pero no es el UUID de la aprobación ni un secreto.


Aprobaciones - comprobar el contexto de conexión

Antes de la primera escritura, recupera el contexto. Así confirmarás que la dirección llega a la base de datos correcta y que la clave tiene 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"

Comprueba data.apiVersion, data.contractVersion, data.tenant, que data.caller.authentication sea igual a api_key, que approvals aparezca en data.capabilities.resources, además de los scopes y límites.

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


Aprobaciones - esquema y destinos de relaciones

El esquema es la referencia para conocer los campos actuales, sus tipos, si se pueden escribir y qué destinos de relaciones están permitidos:

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

La respuesta contiene data.itemType, data.fields y data.relationshipTargets. En este módulo, itemType es approval. Para cada campo, comprueba readable, writable, required, technical, unique, maxLength y hasAutoGeneration.

Ejemplo de un fragmento del esquema:

{
  "data": {
    "itemType": "approval",
    "fields": [
      { "name": "description", "type": "string", "writable": true },
      { "name": "level", "type": "string", "writable": true },
      { "name": "status", "type": "string", "writable": false },
      { "name": "pin", "type": "integer", "writable": false }
    ],
    "relationshipTargets": [
      { "targetDataSet": "notes", "targetItemType": "note" },
      { "targetDataSet": "tickets", "targetItemType": "ticket" }
    ]
  }
}

No construyas el mapeo únicamente a partir de este ejemplo. Recupera el esquema de la base de datos real antes de iniciar la integración y utiliza solo los campos y destinos que devuelva.


Aprobaciones - campos de negocio y campos del sistema

Los campos principales de una aprobación son:

Campo
Tipo
Finalidad
customId
string
Identificador de la aplicación de integración
location, department
string
Ubicación y departamento responsables del proceso
tag, link
string
Etiquetas y enlace a la fuente
info, description
string
Información adicional y descripción de la solicitud
level, category
string
Nivel de aprobación y categoría del proceso
status, dateApproved, dateRejected
solo lectura
Estado y fechas del resultado de la decisión
remark, pin
gestionado por el sistema
Comentario de la decisión y nivel de fijación

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

creator
updater
dateCreated
dateUpdated
importId
importSource
dateImported

Aprobaciones - endpoints disponibles

Las rutas principales del módulo approvals son:

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

Las operaciones de lectura requieren scopes read, mientras que cada modificación requiere los scopes adicionales descritos arriba. Toda solicitud que cambie datos también necesita Idempotency-Key, y las operaciones sobre un registro existente necesitan además el If-Match actual.


Aprobaciones - listas y paginación

Recupera las listas de aprobaciones página a página:

curl --request GET --url "$BASE_URL/api/v1/approvals?itemType=approval&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 data.items y la información de paginación:

{
  "data": {
    "items": [
      {
        "id": "approval-uuid",
        "itemType": "approval",
        "attributes": {
          "customId": "ERP-APPROVAL-2026-0042",
          "category": "Procurement",
          "status": "Open",
          "level": "Supervisor"
        },
        "meta": {
          "etag": "\"etag-value\""
        }
      }
    ],
    "page": 1,
    "pageSize": 25,
    "totalItems": 1,
    "totalPages": 1,
    "hasNextPage": false
  },
  "meta": {
    "requestId": "request-id"
  }
}

Pasa a la página siguiente según hasNextPage. Lee el tamaño máximo de página en data.capabilities.limits.maxPageSize en lugar de fijarlo en el código.


Aprobaciones - búsqueda y filtros

Utiliza search para buscar texto. Para sincronizar datos, es preferible un customId o UUID estable:

curl --silent --show-error -G \
  --data-urlencode "search=purchase" \
  --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/approvals"

Los filtros simples pueden utilizar nombres de campos:

curl --silent --show-error -G \
  --data-urlencode "status=Open" \
  --data-urlencode "category=Procurement" \
  --data-urlencode "customId=ERP-APPROVAL-2026-0042" \
  --header "X-Codenica-Client-Id: $CLIENT_ID" \
  --header "X-Codenica-Client-Secret: $CLIENT_SECRET" \
  "$BASE_URL/api/v1/approvals"

Los filtros estructurales utilizan el formato field:operator:value:

category:eq:Procurement
level:ne:Assistant
description:contains:monitor
customId:startswith:ERP-
link:notempty:

Los operadores admitidos incluyen eq, ne, gt, gte, lt, lte, contains, startswith, endswith y notempty. Codifica los valores en la URL, sobre todo si contienen espacios, dos puntos o caracteres especiales.


Aprobaciones - selección de campos y datos incluidos

Utiliza fields cuando solo necesites una parte de la respuesta. Incluye archivos, relaciones y usuarios con include:

curl --silent --show-error -G \
  --data-urlencode "fields=id,itemType,customId,location,department,level,category,status" \
  --data-urlencode "include=files,relationships,users" \
  --data-urlencode "ids={APPROVAL_ID}" \
  --header "X-Codenica-Client-Id: $CLIENT_ID" \
  --header "X-Codenica-Client-Secret: $CLIENT_SECRET" \
  "$BASE_URL/api/v1/approvals"

Los valores disponibles de include son files, relationships y users. Cada uno puede requerir un scope independiente. fields=* no permite acceder a campos técnicos ni cambia las reglas de acceso.

También puedes filtrar por createdAfter, createdBefore, updatedAfter, updatedBefore, sort y direction. Comprueba los nombres de los campos en el esquema actual.


Aprobaciones - estadísticas y valores de campos

El endpoint stats muestra la distribución de los datos, mientras que values devuelve valores útiles para crear filtros:

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

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

Ambas solicitudes son de solo lectura y no modifican las aprobaciones. El campo debe estar permitido por el esquema y los valores dependen de los registros visibles para el usuario.


Aprobaciones - creación mínima

Un registro mínimo útil contiene el tipo, un identificador de la aplicación de integración, una categoría, una descripción y el usuario que debe tomar la decisión:

{
  "itemType": "approval",
  "attributes": {
    "customId": "ERP-APPROVAL-0001",
    "category": "Procurement",
    "description": "Aprobación para comprar un monitor"
  },
  "approverId": "{APPROVER_USER_ID}"
}

Envía los datos como JSON:

curl --fail-with-body --silent --show-error \
  --request POST \
  --header "Content-Type: application/json" \
  --header "Accept: application/json" \
  --header "X-Codenica-Client-Id: $CLIENT_ID" \
  --header "X-Codenica-Client-Secret: $CLIENT_SECRET" \
  --header "Idempotency-Key: erp-approval-create-0001" \
  --data-binary @approval.json \
  "$BASE_URL/api/v1/approvals"

approverId identifica al usuario activo de Codenica que tomará la decisión. No es el identificador de un cliente ni una dirección de correo electrónico arbitraria.


Aprobaciones - ejemplo de creación completa

Este ejemplo contiene información del proceso, etiquetas, un nivel de aprobación y una regla de valor técnico:

{
  "itemType": "approval",
  "attributes": {
    "customId": "PUBLIC-API-APPROVAL-20260906060644-SOURCE",
    "location": "Warsaw",
    "department": "IT",
    "tag": "public-api,approvals,PUBLIC-API-APPROVAL-20260906060644",
    "link": "https://codenica.com",
    "info": "Solicitud de aprobación creada por el flujo completo de la API pública.",
    "level": "Supervisor",
    "category": "Procurement",
    "description": "Aprobación creada mediante Codenica Public API."
  },
  "approverId": "{APPROVER_USER_ID}",
  "customValues": [
    {
      "name": "description",
      "valuePattern": "[approval-example] PUBLIC-API-APPROVAL-20260906060644"
    }
  ]
}

approverId requiere approvals:technical:write. customValues es opcional y también requiere el scope técnico. Utilízalo solo para campos permitidos por el esquema.

En una integración real, elige al aprobador según el proceso de la empresa. El usuario que crea el registro se convierte en el solicitante.


Aprobaciones - respuesta de creación y clave de idempotencia

Una creación correcta devuelve 201 Created, el identificador del registro y el ETag inicial. Guarda los tres datos en la integración:

{
  "data": {
    "id": "{APPROVAL_ID}",
    "itemType": "approval",
    "attributes": {
      "customId": "PUBLIC-API-APPROVAL-20260906060644-SOURCE",
      "level": "Supervisor",
      "category": "Procurement",
      "status": "Open"
    },
    "meta": {
      "customId": "PUBLIC-API-APPROVAL-20260906060644-SOURCE",
      "etag": "\"{ETAG_AFTER_CREATE}\""
    }
  },
  "meta": {
    "requestId": "{REQUEST_ID}",
    "etag": "\"{ETAG_AFTER_CREATE}\""
  }
}

Cada solicitud que modifique datos debe tener su propia Idempotency-Key. Si el cliente no sabe si la primera solicitud llegó al servidor, vuelve a enviar el mismo cuerpo con la misma clave:

curl --fail-with-body --silent --show-error \
  --request POST \
  --header "Content-Type: application/json" \
  --header "X-Codenica-Client-Id: $CLIENT_ID" \
  --header "X-Codenica-Client-Secret: $CLIENT_SECRET" \
  --header "Idempotency-Key: public-api-approval-source-create-20260906060644" \
  --data-binary @approval.json \
  "$BASE_URL/api/v1/approvals"

Repetir la misma solicitud no crea una segunda aprobación. Si cambias el cuerpo y mantienes la misma clave, la solicitud se rechaza porque una clave solo puede representar una operación.


Aprobaciones - leer un registro y su ETag

Después de crear el registro y antes de cada cambio posterior, léelo por UUID:

curl --fail-with-body --silent --show-error \
  --request GET \
  --header "Accept: application/json" \
  --header "X-Codenica-Client-Id: $CLIENT_ID" \
  --header "X-Codenica-Client-Secret: $CLIENT_SECRET" \
  "$BASE_URL/api/v1/approvals/{APPROVAL_ID}?fields=*"

La respuesta contiene data.attributes y data.meta.etag. El servidor devuelve el mismo valor en una cabecera HTTP:

HTTP/1.1 200 OK
ETag: "{ETAG_AFTER_GET}"

Después de cada modificación correcta, el ETag puede cambiar, también después de modificar relaciones, fijar el registro, tomar una decisión o realizar operaciones con archivos. Guarda siempre el valor devuelto por la última operación correcta.


Aprobaciones - editar campos con If-Match

Una actualización normal modifica únicamente campos de negocio. No la utilices para escribir el estado, las fechas de decisión ni la fijación:

curl --fail-with-body --silent --show-error \
  --request PATCH \
  --header "Content-Type: application/json" \
  --header "Accept: application/json" \
  --header "X-Codenica-Client-Id: $CLIENT_ID" \
  --header "X-Codenica-Client-Secret: $CLIENT_SECRET" \
  --header 'If-Match: "{ETAG_AFTER_GET}"' \
  --header "Idempotency-Key: public-api-approval-update-20260906060644" \
  --data '{
    "attributes": {
      "info": "Información de aprobación actualizada desde la integración.",
      "level": "Manager",
      "category": "Approved procurement",
      "description": "Aprobación editada mediante Codenica Public API."
    }
  }' \
  "$BASE_URL/api/v1/approvals/{APPROVAL_ID}"

Si todo es correcto, devuelve 200 OK y un ETag nuevo. Actualiza solo los campos que realmente hayan cambiado. Esto facilita resolver conflictos y reduce el riesgo de sobrescribir datos.


Aprobaciones - If-Match obligatorio y protección contra conflictos

Para cambiar un registro existente necesitas el ETag actual. Un PATCH sin esta cabecera devuelve:

{
  "type": "https://docs.codenica.com/errors/if_match_required",
  "title": "Precondition required.",
  "status": 428,
  "code": "if_match_required",
  "detail": "Send the ETag returned by GET in the If-Match header."
}

Si el ETag enviado está obsoleto, la API devuelve 412 Precondition Failed y el código if_match_failed:

{
  "type": "https://docs.codenica.com/errors/if_match_failed",
  "title": "Precondition failed.",
  "status": 412,
  "code": "if_match_failed",
  "detail": "The supplied ETag is not the current approval version."
}

Una solicitud rechazada con 412 no guarda cambios. Vuelve a leer la aprobación, compara los datos y prepara después una actualización deliberada. No sobrescribas automáticamente cambios realizados por otra persona o integración.


Aprobaciones - fijación

El campo pin es de solo lectura y se cambia mediante un endpoint específico. Los valores permitidos son enteros de 0 a 3:

curl --fail-with-body --silent --show-error \
  --request POST \
  --header "Content-Type: application/json" \
  --header "X-Codenica-Client-Id: $CLIENT_ID" \
  --header "X-Codenica-Client-Secret: $CLIENT_SECRET" \
  --header 'If-Match: "{CURRENT_ETAG}"' \
  --header "Idempotency-Key: public-api-approval-pin-20260906060644" \
  --data '{"pin":3}' \
  "$BASE_URL/api/v1/approvals/{APPROVAL_ID}/pin"

Para quitar la fijación, envía null mediante el mismo endpoint:

curl --fail-with-body --silent --show-error \
  --request POST \
  --header "Content-Type: application/json" \
  --header "X-Codenica-Client-Id: $CLIENT_ID" \
  --header "X-Codenica-Client-Secret: $CLIENT_SECRET" \
  --header 'If-Match: "{ETAG_AFTER_PIN}"' \
  --header "Idempotency-Key: public-api-approval-unpin-20260906060644" \
  --data '{"pin":null}' \
  "$BASE_URL/api/v1/approvals/{APPROVAL_ID}/pin"

Ambas operaciones requieren el ETag actual y approvals:pin:write. No envíes pin en un PATCH normal.


Aprobaciones - ejecutar una decisión Approved o Rejected

Las decisiones utilizan un endpoint específico:

/api/v1/approvals/{APPROVAL_ID}/decision

Una decisión positiva establece status=Approved, dateApproved y dateEnd:

curl --fail-with-body --silent --show-error \
  --request POST \
  --header "Content-Type: application/json" \
  --header "Accept: application/json" \
  --header "X-Codenica-Client-Id: $CLIENT_ID" \
  --header "X-Codenica-Client-Secret: $CLIENT_SECRET" \
  --header 'If-Match: "{CURRENT_ETAG}"' \
  --header "Idempotency-Key: public-api-approval-decision-approved-20260906060644" \
  --data '{"approved":true,"remark":"Aprobada mediante la integración de la API pública."}' \
  "$BASE_URL/api/v1/approvals/{APPROVED_APPROVAL_ID}/decision"

Una decisión negativa establece status=Rejected, dateRejected y dateEnd:

curl --fail-with-body --silent --show-error \
  --request POST \
  --header "Content-Type: application/json" \
  --header "Accept: application/json" \
  --header "X-Codenica-Client-Id: $CLIENT_ID" \
  --header "X-Codenica-Client-Secret: $CLIENT_SECRET" \
  --header 'If-Match: "{CURRENT_ETAG}"' \
  --header "Idempotency-Key: public-api-approval-decision-rejected-20260906060644" \
  --data '{"approved":false,"remark":"Rechazada mediante la integración de la API pública."}' \
  "$BASE_URL/api/v1/approvals/{REJECTED_APPROVAL_ID}/decision"

No cambies el estado mediante PATCH para evitar este procedimiento. Una decisión requiere approvals:decision:write, el permiso de negocio adecuado (Approval_Accept o Approval_Reject), un ETag actual y una llamada realizada exactamente por el usuario asignado como aprobador.


Aprobaciones - solicitante y aprobador

Lee las relaciones de usuarios mediante un endpoint separado:

curl --fail-with-body --silent --show-error \
  --request GET \
  --header "X-Codenica-Client-Id: $CLIENT_ID" \
  --header "X-Codenica-Client-Secret: $CLIENT_SECRET" \
  "$BASE_URL/api/v1/approvals/{APPROVAL_ID}/user-relationships?page=1&pageSize=20"

La respuesta contiene una relación requester y, cuando se ha indicado un aprobador, una relación approver:

{
  "data": {
    "items": [
      {
        "userId": "{REQUESTER_USER_ID}",
        "relationshipType": "requester"
      },
      {
        "userId": "{APPROVER_USER_ID}",
        "relationshipType": "approver"
      }
    ]
  }
}

El solicitante se asigna automáticamente al usuario que crea la aprobación. Ambas relaciones son de solo lectura. No intentes cambiar el aprobador mediante POST en user-relationships; la asignación del aprobador pertenece a la creación controlada o al proceso del sistema.


Aprobaciones - relaciones de objetos permitidas

El catálogo de destinos de relaciones de una aprobación está limitado deliberadamente:

targetDataSet
targetItemType
notes
note
worktasks
worktask
requesteditems
requesteditem
tickets
ticket
changes
change
problems
problem
releases
release

Este catálogo no incluye relaciones con assets, clients, vendors, documents, confirmations ni con la propia aprobación.

{
  "targetId": "{TARGET_ID}",
  "targetDataSet": "notes",
  "targetItemType": "note"
}

Primero selecciona un registro de destino en la lista de la colección correspondiente. No des por hecho que todas las bases de datos contienen un registro en las siete colecciones.


Aprobaciones - una diferencia importante en el formato de las relaciones

Las relaciones de objetos de las aprobaciones no almacenan relationshipType. El cuerpo contiene únicamente el identificador del destino, el nombre de la colección y el tipo técnico del objeto:

{
  "targetId": "7bdda87a-6c37-49ae-9e40-272a7a9b8616",
  "targetDataSet": "notes",
  "targetItemType": "note"
}

No envíes este campo:

{
  "targetId": "{TARGET_ID}",
  "targetDataSet": "notes",
  "targetItemType": "note",
  "relationshipType": "related"
}

relationshipType se utiliza en otros modelos de relaciones y en archivos, pero se rechaza en las relaciones de aprobaciones con objetos de proceso.

curl --fail-with-body --silent --show-error \
  --request GET \
  --header "X-Codenica-Client-Id: $CLIENT_ID" \
  --header "X-Codenica-Client-Secret: $CLIENT_SECRET" \
  "$BASE_URL/api/v1/notes?page=1&pageSize=10"

Para seleccionar un destino necesitas el scope read de la colección, por ejemplo notes:read.


Aprobaciones - añadir, leer y eliminar una relación

Para crear directamente una relación necesitas el ETag actual de la aprobación y approvals:relationships:write:

curl --fail-with-body --silent --show-error \
  --request POST \
  --header "Content-Type: application/json" \
  --header "X-Codenica-Client-Id: $CLIENT_ID" \
  --header "X-Codenica-Client-Secret: $CLIENT_SECRET" \
  --header 'If-Match: "{CURRENT_ETAG}"' \
  --header "Idempotency-Key: public-api-approval-relation-add-0001" \
  --data '{"targetId":"{NOTE_ID}","targetDataSet":"notes","targetItemType":"note"}' \
  "$BASE_URL/api/v1/approvals/{APPROVAL_ID}/relationships"

Lee las relaciones después de añadir una:

curl --fail-with-body --silent --show-error \
  --request GET \
  --header "X-Codenica-Client-Id: $CLIENT_ID" \
  --header "X-Codenica-Client-Secret: $CLIENT_SECRET" \
  "$BASE_URL/api/v1/approvals/{APPROVAL_ID}/relationships?targetDataSet=notes&page=1&pageSize=100"

Elimina una relación utilizando el ETag nuevo que se devolvió después de añadirla:

curl --fail-with-body --silent --show-error \
  --request DELETE \
  --header "X-Codenica-Client-Id: $CLIENT_ID" \
  --header "X-Codenica-Client-Secret: $CLIENT_SECRET" \
  --header 'If-Match: "{ETAG_AFTER_RELATION_ADD}"' \
  --header "Idempotency-Key: public-api-approval-relation-delete-0001" \
  "$BASE_URL/api/v1/approvals/{APPROVAL_ID}/relationships/notes/{NOTE_ID}"

Después de cada escritura, vuelve a recuperar la colección de relaciones y confirma que el destino se ha añadido o eliminado.


Aprobaciones - lotes de relaciones y relaciones en PATCH

Cambia varias relaciones con una sola solicitud:

{
  "add": [
    {
      "targetId": "{NOTE_ID}",
      "targetDataSet": "notes",
      "targetItemType": "note"
    }
  ],
  "remove": []
}
curl --fail-with-body --silent --show-error \
  --request POST \
  --header "Content-Type: application/json" \
  --header "X-Codenica-Client-Id: $CLIENT_ID" \
  --header "X-Codenica-Client-Secret: $CLIENT_SECRET" \
  --header 'If-Match: "{CURRENT_ETAG}"' \
  --header "Idempotency-Key: public-api-approval-relationship-batch-0001" \
  --data-binary @relationship-batch.json \
  "$BASE_URL/api/v1/approvals/{APPROVAL_ID}/relationships:batch"

La respuesta contiene los contadores added, removed y skipped. Un PATCH también puede contener relationshipsToAdd y relationshipsToRemove:

{
  "attributes": {
    "info": "Información actualizada junto con una relación."
  },
  "relationshipsToAdd": [
    {
      "targetId": "{TICKET_ID}",
      "targetDataSet": "tickets",
      "targetItemType": "ticket"
    }
  ],
  "relationshipsToRemove": []
}

En ambas variantes se requieren el ETag actual, la idempotencia y el scope de relaciones.


Aprobaciones - lista y carga de archivos

Los archivos son recursos separados vinculados a una aprobación. Primero lee la lista actual:

curl --fail-with-body --silent --show-error \
  --request GET \
  --header "X-Codenica-Client-Id: $CLIENT_ID" \
  --header "X-Codenica-Client-Secret: $CLIENT_SECRET" \
  "$BASE_URL/api/v1/approvals/{APPROVAL_ID}/files?page=1&pageSize=100"

Carga un archivo como multipart/form-data. El papel del archivo se envía en la cadena de consulta:

curl --fail-with-body --silent --show-error \
  --request POST \
  --header "X-Codenica-Client-Id: $CLIENT_ID" \
  --header "X-Codenica-Client-Secret: $CLIENT_SECRET" \
  --header 'If-Match: "{CURRENT_ETAG}"' \
  --header "Idempotency-Key: public-api-approval-file-upload-0001" \
  --form "[email protected];type=application/pdf" \
  "$BASE_URL/api/v1/approvals/{APPROVAL_ID}/files?relationshipType=decision-form"

El nombre debe ser un único nombre de archivo sin ruta. Comprueba el tamaño antes de enviarlo y establece de forma deliberada el tipo MIME. La carga requiere approvals:files:write.

{
  "data": {
    "id": "{FILE_ID}",
    "fileName": "approval-decision-form.pdf",
    "contentType": "application/pdf",
    "size": 48231,
    "relationshipType": "decision-form",
    "isMain": false,
    "downloadUrl": "/api/v1/approvals/{APPROVAL_ID}/files/{FILE_ID}/content"
  }
}

La API de aprobaciones no ofrece una operación para establecer un archivo principal. Los archivos devueltos tienen isMain=false. No construyas una integración que espere un endpoint /main para este objeto.


Aprobaciones - descargar, adjuntar y eliminar archivos

Descarga el contenido mediante el endpoint content y guárdalo como archivo binario:

curl --fail-with-body --silent --show-error \
  --output downloaded-approval-form.pdf \
  --header "X-Codenica-Client-Id: $CLIENT_ID" \
  --header "X-Codenica-Client-Secret: $CLIENT_SECRET" \
  "$BASE_URL/api/v1/approvals/{APPROVAL_ID}/files/{FILE_ID}/content"

Si el archivo ya existe en el sistema y tienes su File ID, adjúntalo a una aprobación sin cargar otra copia:

curl --fail-with-body --silent --show-error \
  --request POST \
  --header "X-Codenica-Client-Id: $CLIENT_ID" \
  --header "X-Codenica-Client-Secret: $CLIENT_SECRET" \
  --header 'If-Match: "{TARGET_APPROVAL_ETAG}"' \
  --header "Idempotency-Key: public-api-approval-file-attach-0001" \
  "$BASE_URL/api/v1/approvals/{TARGET_APPROVAL_ID}/files/{FILE_ID}?relationshipType=reference"

Elimina un archivo de una aprobación:

curl --fail-with-body --silent --show-error \
  --request DELETE \
  --header "X-Codenica-Client-Id: $CLIENT_ID" \
  --header "X-Codenica-Client-Secret: $CLIENT_SECRET" \
  --header 'If-Match: "{CURRENT_ETAG}"' \
  --header "Idempotency-Key: public-api-approval-file-delete-0001" \
  "$BASE_URL/api/v1/approvals/{APPROVAL_ID}/files/{FILE_ID}"

Adjuntar crea un enlace a un archivo existente y no carga una copia nueva. La carga, la asociación y la eliminación cambian el ETag de la aprobación. La descarga es una operación de solo lectura.


Aprobaciones - operaciones por lotes

El endpoint /api/v1/approvals:batch crea, edita y elimina varios registros. No sustituye las operaciones de decisión, fijación, relaciones ni archivos:

{
  "items": [
    {
      "operation": "create",
      "create": {
        "itemType": "approval",
        "approverId": "{APPROVER_USER_ID}",
        "attributes": {
          "customId": "PUBLIC-API-APPROVAL-BATCH-A",
          "location": "Warsaw",
          "department": "IT",
          "level": "Supervisor",
          "category": "Procurement",
          "description": "Aprobación A creada por lotes"
        }
      }
    },
    {
      "operation": "create",
      "create": {
        "itemType": "approval",
        "approverId": "{APPROVER_USER_ID}",
        "attributes": {
          "customId": "PUBLIC-API-APPROVAL-BATCH-B",
          "location": "Warsaw",
          "department": "IT",
          "level": "Manager",
          "category": "Procurement",
          "description": "Aprobación B creada por lotes"
        }
      }
    }
  ]
}
curl --fail-with-body --silent --show-error \
  --request POST \
  --header "Content-Type: application/json" \
  --header "X-Codenica-Client-Id: $CLIENT_ID" \
  --header "X-Codenica-Client-Secret: $CLIENT_SECRET" \
  --header "Idempotency-Key: public-api-approval-batch-create-0001" \
  --data-binary @approvals-batch-create.json \
  "$BASE_URL/api/v1/approvals:batch"

Un lote puede combinar create, update y delete. Cada actualización y eliminación debe incluir su propio id y el ifMatch actual.

{
  "data": {
    "items": [
      {
        "index": 0,
        "operation": "create",
        "status": 201,
        "id": "{BATCH_ID_A}",
        "data": {
          "id": "{BATCH_ID_A}",
          "meta": { "etag": "{ETAG_A}" }
        }
      }
    ],
    "succeeded": 2,
    "failed": 0
  }
}

Un lote no es una transacción de todo o nada. Un resultado parcial puede devolver 207 Multi-Status. Analiza cada elemento de la respuesta y no repitas operaciones que ya hayan terminado correctamente.


Aprobaciones - eliminar un registro

Antes de eliminarlo, vuelve a leer el registro, comprueba el UUID y el ETag actual y confirma que el proceso de negocio permite eliminarlo:

curl --fail-with-body --silent --show-error \
  --request DELETE \
  --header "X-Codenica-Client-Id: $CLIENT_ID" \
  --header "X-Codenica-Client-Secret: $CLIENT_SECRET" \
  --header 'If-Match: "{CURRENT_ETAG}"' \
  --header "Idempotency-Key: public-api-approval-delete-0001" \
  "$BASE_URL/api/v1/approvals/{APPROVAL_ID}"

Una respuesta correcta devuelve 200 OK y data=true. Después de eliminarlo, la lectura del mismo UUID debe devolver 404 Not Found con approval_not_found. También puedes verificar el resultado mediante una lista filtrada por customId y esperar totalItems=0.

No elimines una aprobación sin comprobar su ETag. Esto protege la integración frente a la eliminación de una versión más reciente mientras trabaja con una copia antigua.


Aprobaciones - errores, límites y secuencia segura

Los errores utilizan el formato Problem Details. Registra status, code y requestId, pero nunca registres el Client Secret ni las cabeceras completas:

HTTP
Código
Respuesta recomendada
401
authentication_failed
Comprueba la dirección y las dos cabeceras.
403
approval_approver_required
La decisión debe tomarla el aprobador asignado.
404
approval_not_found
El registro no existe o no está visible.
409
approval_unique_constraint o approval_concurrency_conflict
Elimina el duplicado o lee el registro y su nuevo ETag.
412 / 428
if_match_failed, if_match_required
Lee el ETag actual y añade la cabecera obligatoria.
422
validation_failed, approval_decision_rejected, approval_pin_rejected
Corrige el cuerpo o comprueba las reglas del proceso.
429
rate_limit_exceeded
Aplica un backoff creciente y lee Retry-After.

Lee X-RateLimit-Limit y X-RateLimit-Remaining. Guarda en caché el esquema y los valores, limita la concurrencia y aplica un backoff después de un 429.

Una secuencia segura es: context, schema, elegir un aprobador, listar o leer un registro, crear con Idempotency-Key, guardar su UUID y ETag, añadir relaciones o archivos, editar con If-Match, tomar la decisión mediante /decision, volver a leer para verificar y eliminar solo cuando sea necesario. La misma secuencia puede utilizarse en n8n pasando UUID, ETag y claves de idempotencia entre los pasos.