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/approvalsBASE_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:readPara 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:
customIdlocation, departmenttag, linkinfo, descriptionlevel, categorystatus, dateApproved, dateRejectedremark, pinid, 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
dateImportedAprobaciones - 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}/decisionLas 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}/decisionUna 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:
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:
authentication_failedapproval_approver_requiredapproval_not_foundapproval_unique_constraint o approval_concurrency_conflictif_match_failed, if_match_requiredvalidation_failed, approval_decision_rejected, approval_pin_rejectedrate_limit_exceededRetry-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.
